Repository: sfu-db/dataprep (language: python)

Issue:
Implement how-to guide for plot(df, *)
**Is your feature request related to a problem? Please describe.**
Implement a how-to guide for `plot(df, *)` so the user can quickly configure our functions. 
Epic is #187 


Discussion / hints:

Fix patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 47c3cb4fb..4b13e753b 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -1,6 +1,4 @@
 version: 2.1
-orbs:
-  coverage-reporter: codacy/coverage-reporter@11.3.8
 executors:
   python37:
     docker:
@@ -55,9 +53,9 @@ jobs:
       - run:
           name: Upload coverage data to codecov
           command: poetry run codecov
-      - coverage-reporter/send_report:
-          coverage-reports: 'coverage.xml'
-          project-token: $CODACY_PROJECT_TOKEN
+      - run:
+          name: Upload coverage data to codacy
+          command: bash <(curl -Ls https://coverage.codacy.com/get.sh) report --coverage-reports coverage.xml -t $CODACY_PROJECT_TOKEN
   docs-build:
     executor: python37
     steps:
diff --git a/dataprep/eda/__init__.py b/dataprep/eda/__init__.py
index 7c6eaf1f4..64ea5b978 100644
--- a/dataprep/eda/__init__.py
+++ b/dataprep/eda/__init__.py
@@ -4,6 +4,7 @@
 """
 from bokeh.io import output_notebook
 
+from ..utils import is_notebook
 from .correlation import compute_correlation, plot_correlation, render_correlation
 from .create_report import create_report
 from .distribution import compute, plot, render
@@ -19,7 +20,6 @@
     Text,
 )
 from .missing import compute_missing, plot_missing, render_missing
-from ..utils import is_notebook
 
 __all__ = [
     "plot_correlation",
diff --git a/dataprep/eda/basic/configs.py b/dataprep/eda/basic/configs.py
deleted file mode 100644
index 405bbb987..000000000
--- a/dataprep/eda/basic/configs.py
+++ /dev/null
@@ -1,85 +0,0 @@
-"""
-This file defines config classes for plots
-"""
-from typing import Optional, Tuple, Dict, Any
-from dataclasses import dataclass
-
-
-@dataclass
-class ConfigBase:
-    """
-    Base class for config
-    """
-
-    name: str = "Base"
-
-    @classmethod
-    def from_dict(cls, _: Dict[str, Any]) -> "ConfigBase":
-        """Create a config from a dict of values"""
-
-
-@dataclass
-class Histogram(ConfigBase):
-    """
-    Histogram config
-    """
-
-    name: str = "Histogram"
-
-    bins: int = 10
-    agg: str = "mean"
-    value_range: Optional[Tuple[float, float]] = None
-
-    @classmethod
-    def from_dict(cls, kwargs: Dict[str, Any]) -> "Histogram":
-        if "agg" in kwargs.keys():
-            if kwargs["agg"] != "mean" and kwargs["agg"] != "freq":
-                raise ValueError("agg must be mean or freq")
-
-        return cls(**kwargs)
-
-
-# pylint: disable=too-many-instance-attributes
-@dataclass
-class BarChart(ConfigBase):
-    """
-    Bar Chart config
-    """
-
-    name: str = "BarChart"
-
-    ngroups: int = 10
-    largest: bool = False
-    nsubgroups: int = 5
-    top_words: int = 30
-    stopword: bool = True
-    lemmatize: bool = False
-    stem: bool = False
-    sort_by: str = "alphabet"
-    sort_ascending: bool = True
-
-    @classmethod
-    def from_dict(cls, kwargs: Dict[str, Any]) -> "BarChart":
-        if "sort_by" in kwargs.keys():
-            if kwargs["sort_by"] != "alphabet" and kwargs["sort_by"] != "frequency":
-                raise ValueError("sort_by must be alphabet or frequency")
-
-        return cls(**kwargs)
-
-
-@dataclass
-class WordCloud(ConfigBase):
-    """
-    Word Cloud config
-    """
-
-    name: str = "WordCloud"
-
-    top_words: int = 30
-    stopword: bool = True
-    lemmatize: bool = False
-    stem: bool = False
-
-    @classmethod
-    def from_dict(cls, kwargs: Dict[str, Any]) -> "WordCloud":
-        return cls(**kwargs)
diff --git a/dataprep/eda/configs.py b/dataprep/eda/configs.py
new file mode 100644
index 000000000..f57aec216
--- /dev/null
+++ b/dataprep/eda/configs.py
@@ -0,0 +1,1040 @@
+"""
+Parameter configurations
+
+This file contains configurations for stats, auto-insights and plots. There are mainly two settings,
+"display" and "config". Display is a list of Tab names which control the Tabs to show.
+Config is a dictionary that contains the customizable parameters and corresponding values.
+There are two types of parameters, global and local. Local parameters are plot-specified and
+the names are separated  by ".". The portion before the first "." is plot name and the portion
+after the first "." is parameter name. e.g. "hist.bins". The "." is also used when the parameter
+name contains more than one word. e.g. "insight.duplicates.threshold". However, in the codebase,
+the "." is replaced with "__" for parameters with long names.e.g. "insight.duplicates__threshold".
+Global parameter is single-word. It applies to all the plots which has that parameter.
+e.g. "bins:50" applies to "hist.bins", "line.bins", "kde.bins", "wordlen.bins" and "box.bins".
+In addition,when global parameter and local parameter are both entered by a user in config,
+the global parameter will be overwrote by local parameters for specific plots.
+"""
+
+# pylint: disable=too-many-lines,no-self-use,blacklisted-name,no-else-raise,too-many-branches,no-name-in-module
+
+from __future__ import annotations
+
+from typing import Any, Dict, List, Optional, Tuple, Union
+
+from pydantic import BaseModel, Field
+
+# This dictionary map the plot names in display to their canonicalized names in Config
+DISPLAY_MAP = {
+    "Bar Chart": "bar",
+    "Pie Chart": "pie",
+    "Word Cloud": "wordcloud",
+    "Word Frequency": "wordfreq",
+    "Word Length": "wordlen",
+    "Histogram": "hist",
+    "KDE Plot": "kde",
+    "Normal Q-Q Plot": "qqnorm",
+    "Box Plot": "box",
+    "Line Chart": "line",
+    "Stats": "stats",
+    "Insights": "insight",
+    "Scatter Plot": "scatter",
+    "Hexbin Plot": "hexbin",
+    "Nested Bar Chart": "nested",
+    "Stacked Bar Chart": "stacked",
+    "Heat Map": "heatmap",
+    "Pearson": "pearson",
+    "Spearman": "spearman",
+    "KendallTau": "kendall",
+    "Spectrum": "spectrum",
+    "Dendrogram": "dendro",
+    "PDF": "pdf",
+    "CDF": "cdf",
+}
+
+
+class Plot(BaseModel):
+    """
+    Class containing global parameters for the plots
+    """
+
+    width: Union[int, None] = None
+    height: Union[int, None] = None
+    bins: Union[int, None] = None
+    ngroups: Union[int, None] = None
+    grid_column: int = 3
+    report: bool = False
+
+
+class Stats(BaseModel):
+    """
+    enable: bool, default True
+        Whether to display the stats section
+    """
+
+    enable: bool = True
+
+
+class Insight(BaseModel):
+    """
+    enable: bool, default True
+        Whether to create this element
+    duplicates__threshold: int, default 1
+        The threshold for duplicated row counts
+    similar_distribution__threshold:int, default 0.05
+        The significance level for Kolmogorov–Smirnov test
+    uniform__threshold: int, default 0.999
+        The p-value threshold for chi-square test
+    missing__threshold: int, default 1
+        The threshold for missing values count
+    skewed__threshold: int, default 1e-5
+        The threshold for skewness statistics
+    infinity__threshold: int, default 1
+        The threshold for infinity count
+    zeros__threshold: int, default 5
+        The threshold for zeros count
+    negatives__threshold: int, default 1
+        The threshold for negatives count
+    normal__threshold: int, default 0.99
+        The p-value threshold for normaltest, it is based on D’Agostino and Pearson’s test that
+        combines skew and kurtosis to produce an omnibus test of normality
+    high_cardinality__threshold: int, default 50
+        The threshold for unique values count, count larger than threshold yields high cardinality
+    constant__threshold: int, default 1
+        The threshold for unique values count, count equals to threshold yields constant value
+    outstanding_no1__threshold: int, default 1.5
+        The threshold for outstanding no1 insight, measures the ratio of the largest category count
+        to the second-largest category count
+    attribution__threshold: int, default 0.5
+        The threshold for the attribution insight, measures the percentage of the top 2 categories
+    high_word_cardinality__threshold: int, default 1000
+        The threshold for the high word cardinality insight, which measures the number of words of
+        that cateogory
+    outstanding_no1_word__threshold: int, default 0
+        The threshold for the outstanding no1 word threshold, which measures the ratio of the most
+        frequent word count to the second most frequent word count
+    outlier__threshold: int, default 0
+        The threshold for the outlier count in the box plot, default 0
+    """
+
+    # pylint: disable=too-many-instance-attributes
+    enable: bool = True
+    duplicates__threshold: int = 1
+    similar_distribution__threshold: float = 0.05
+    uniform__threshold: float = 0.999
+    missing__threshold: int = 1
+    skewed__threshold: float = 1e-5
+    infinity__threshold: int = 1
+    zeros__threshold: int = 5
+    negatives__threshold: int = 1
+    normal__threshold: float = 0.99
+    high_cardinality__threshold: int = 50
+    constant__threshold: int = 1
+    outstanding_no1__threshold: float = 1.5
+    attribution__threshold: float = 0.5
+    high_word_cardinality__threshold: int = 1000
+    outstanding_no1_word__threshold: float = 1.5
+    outlier__threshold: int = 0
+
+
+class Hist(BaseModel):
+    """
+    enable: bool, default True
+        Whether to create this element
+    bins: int, default 50
+        Number of bins in the histogram
+    yscale: str, default "linear"
+        Y-axis scale ("linear" or "log")
+    height: int, default "auto"
+        Height of the plot
+    width: int, default "auto"
+        Width of the plot
+    """
+
+    enable: bool = True
+    bins: int = 50
+    yscale: str = "linear"
+    height: Union[int, None] = None
+    width: Union[int, None] = None
+
+    def how_to_guide(self, height: int, width: int) -> List[Tuple[str, str]]:
+        """
+        how-to guide for plot(df, x)
+        """
+        vals = [self.bins, self.yscale, height, width]
+        names = ["hist.bins", "hist.yscale", "height", "width"]
+        descs = [
+            "Number of bins in the histogram",
+            'Y-axis scale ("linear" or "log")',
+            "Height of the plot",
+            "Width of the plot",
+        ]
+        return [(f"'{name}': {_form(val)}", desc) for name, val, desc in zip(names, vals, descs)]
+
+    def grid_how_to_guide(self) -> List[Tuple[str, str]]:
+        """
+        how-to guide for plot(df)
+        """
+        vals = [self.bins, self.yscale]
+        names = ["hist.bins", "hist.yscale"]
+        descs = ["Number of bins in the histogram", 'Y-axis scale ("linear" or "log")']
+        return [(f"'{name}': {_form(val)}", desc) for name, val, desc in zip(names, vals, descs)]
+
+
+class Bar(BaseModel):
+    """
+    enable: bool, default True
+        Whether to create this element
+    bars: int, default 10
+        Maximum number of bars to display
+    sort_descending: bool, default True
+        Whether to sort the bars in descending order
+    yscale: str, default "linear"
+        Y-axis scale ("linear" or "log")
+    height: int, default "auto"
+        Height of the plot
+    width: int, default "auto"
+        Width of the plot
+    """
+
+    enable: bool = True
+    bars: int = 10
+    sort_descending: bool = True
+    yscale: str = "linear"
+    height: Union[int, None] = None
+    width: Union[int, None] = None
+
+    def how_to_guide(self, height: int, width: int) -> List[Tuple[str, str]]:
+        """
+        how-to guide for plot(df, x)
+        """
+        vals = [self.bars, self.sort_descending, self.yscale, height, width]
+        names = ["bar.bars", "bar.sort_descending", "bar.yscale", "height", "width"]
+        descs = [
+            "Maximum number of bars to display",
+            "Whether to sort the bars in descending order",
+            'Y-axis scale ("linear" or "log")',
+            "Height of the plot",
+            "Width of the plot",
+        ]
+        return [(f"'{name}': {_form(val)}", desc) for name, val, desc in zip(names, vals, descs)]
+
+    def grid_how_to_guide(self) -> List[Tuple[str, str]]:
+        """
+        how-to guide for plot(df)
+        """
+        vals = [self.bars, self.sort_descending, self.yscale]
+        names = ["bar.bars", "bar.sort_descending", "bar.yscale"]
+        descs = [
+            "Maximum number of bars to display",
+            "Whether to sort the bars in descending order",
+            'Y-axis scale ("linear" or "log")',
+        ]
+        return [(f"'{name}': {_form(val)}", desc) for name, val, desc in zip(names, vals, descs)]
+
+    def missing_how_to_guide(self, height: int, width: int) -> List[Tuple[str, str]]:
+        """
+        how-to guide for plot_missing(df, x, [y])
+        """
+        vals = [height, width]
+        names = ["height", "width"]
+        descs = ["Height of the plot", "Width of the plot"]
+        return [(f"'{name}': {val}", desc) for name, val, desc in zip(names, vals, descs)]
+
+
+class KDE(BaseModel):
+    """
+    enable: bool, default True
+        Whether to create this element
+    bins: int, default 50
+        Number of bins in the histogram
+    yscale: str, default "linear"
+        Y-axis scale ("linear" or "log")
+    height: int, default "auto"
+        Height of the plot
+    width: int, default "auto"
+        Width of the plot
+    """
+
+    enable: bool = True
+    bins: int = 50
+    yscale: str = "linear"
+    width: Union[int, None] = None
+    height: Union[int, None] = None
+
+    def how_to_guide(self, height: int, width: int) -> List[Tuple[str, str]]:
+        """
+        how-to guide for plot(df, x)
+        """
+        vals = [self.bins, self.yscale, height, width]
+        names = ["kde.bins", "hist.yscale", "height", "width"]
+        descs = [
+            "Number of bins in the histogram",
+            'Y-axis scale ("linear" or "log")',
+            "Height of the plot",
+            "Width of the plot",
+        ]
+        return [(f"'{name}': {_form(val)}", desc) for name, val, desc in zip(names, vals, descs)]
+
+
+class QQNorm(BaseModel):
+    """
+    enable: bool, default True
+        Whether to create this element
+    height: int, default "auto"
+        Height of the plot
+    width: int, default "auto"
+        Width of the plot
+    """
+
+    enable: bool = True
+    height: Union[int, None] = None
+    width: Union[int, None] = None
+
+    def how_to_guide(self, height: int, width: int) -> List[Tuple[str, str]]:
+        """
+        how-to guide for plot(df, x)
+        """
+        vals = [height, width]
+        names = ["height", "width"]
+        descs = ["Height of the plot", "Width of the plot"]
+        return [(f"'{name}': {val}", desc) for name, val, desc in zip(names, vals, descs)]
+
+
+class Box(BaseModel):
+    """
+    enable: bool, default True
+        Whether to create this element
+    ngroups: int, default 15
+        Maximum number of groups to display
+    bins: int, default 50
+        Number of bins
+    unit: str, default "auto"
+        Defines the time unit to group values over for a datetime column.
+        It can be "year", "quarter", "month", "week", "day", "hour",
+        "minute", "second". With default value "auto", it will use the
+        time unit such that the resulting number of groups is closest to 15
+    sort_descending: bool, default True
+        Whether to sort the boxes in descending order of frequency
+    height: int, default "auto"
+        Height of the plot
+    width: int, default "auto"
+        Width of the plot
+    """
+
+    enable: bool = True
+    ngroups: int = 15
+    bins: int = 50
+    unit: str = "auto"
+    sort_descending: bool = True
+    width: Union[int, None] = None
+    height: Union[int, None] = None
+
+    def univar_how_to_guide(self, height: int, width: int) -> List[Tuple[str, str]]:
+        """
+        how-to guide for plot(df, x)
+        """
+        vals = [height, width]
+        names = ["height", "width"]
+        descs = ["Height of the plot", "Width of the plot"]
+        return [(f"'{name}': {val}", desc) for name, val, desc in zip(names, vals, descs)]
+
+    def nom_cont_how_to_guide(self, height: int, width: int) -> List[Tuple[str, str]]:
+        """
+        how-to guide for plot(df, nominal, continuous)
+        """
+        vals = [self.ngroups, self.sort_descending, height, width]
+        names = ["box.ngroups", ".box.sort_descending", "height", "width"]
+        descs = [
+            "Maximum number of groups to display",
+            "Whether to sort the boxes in descending order of frequency",
+            "Height of the plot",
+            "Width of the plot",
+        ]
+        return [(f"'{name}': {val}", desc) for name, val, desc in zip(names, vals, descs)]
+
+    def two_cont_how_to_guide(self, height: int, width: int) -> List[Tuple[str, str]]:
+        """
+        how-to guide for plot(df, continuous, continuous)
+        """
+        vals = [self.bins, height, width]
+        names = ["box.bins", "height", "width"]
+        descs = ["Number of bins", "Height of the plot", "Width of the plot"]
+        return [(f"'{name}': {val}", desc) for name, val, desc in zip(names, vals, descs)]
+
+
+class Pie(BaseModel):
+    """
+    enable: bool, default True
+        Whether to create this element
+    slices: int, default 10
+        Maximum number of pie slices to display
+    sort_descending: bool, default True
+        Whether to sort the slices in descending order of frequency
+    height: int, default "auto"
+        Height of the plot
+    width: int, default "auto"
+        Width of the plot
+    """
+
+    enable: bool = True
+    slices: int = 10
+    sort_descending: bool = True
+    width: Union[int, None] = None
+    height: Union[int, None] = None
+
+    def how_to_guide(self, height: int, width: int) -> List[Tuple[str, str]]:
+        """
+        how-to guide for plot(df, x)
+        """
+        vals = [self.slices, self.sort_descending, height, width]
+        names = ["pie.slices", "pie.sort_descending", "height", "width"]
+        descs = [
+            "Maximum number of pie slices to display",
+            "Whether to sort the slices in descending order of frequency",
+            "Height of the plot",
+            "Width of the plot",
+        ]
+        return [(f"'{name}': {val}", desc) for name, val, desc in zip(names, vals, descs)]
+
+
+class WordCloud(BaseModel):
+    """
+    enable: bool, default True
+        Whether to create this element
+    top_words: int, default 30
+        Maximum number of most frequent words to display
+    stopword: bool, default True
+        Whether to remove stopwords
+    lemmatize: bool, default False
+        Whether to lemmatize the words
+    stem: bool, default False
+        Whether to apply Potter Stem on the words
+    """
+
+    enable: bool = True
+    top_words: int = 30
+    stopword: bool = True
+    lemmatize: bool = False
+    stem: bool = False
+    height: Union[int, None] = None
+    width: Union[int, None] = None
+
+    def how_to_guide(self, height: int, width: int) -> List[Tuple[str, str]]:
+        """
+        how-to guide for plot(df, x)
+        """
+        vals = [self.top_words, self.stopword, self.lemmatize, self.stem, height, width]
+        names = [
+            "wordcloud.top_words",
+            "wordcloud.stopword",
+            "wordcloud.lemmatize",
+            "wordcloud.stem",
+            "height",
+            "width",
+        ]
+        descs = [
+            "Maximum number of most frequent words to display",
+            "Whether to remove stopwords",
+            "Whether to lemmatize the words",
+            "Whether to apply Potter Stem on the words",
+            "Height of the plot",
+            "Width of the plot",
+        ]
+        return [(f"'{name}': {val}", desc) for name, val, desc in zip(names, vals, descs)]
+
+
+class WordFrequency(BaseModel):
+    """
+    enable: bool, default True
+        Whether to create this element
+    top_words: int, default 30
+        Maximum number of most frequent words to display
+    stopword: bool, default True
+        Whether to remove stopwords
+    lemmatize: bool, default False
+        Whether to lemmatize the words
+    stem: bool, default False
+        Whether to apply Potter Stem on the words
+    """
+
+    enable: bool = True
+    top_words: int = 30
+    stopword: bool = True
+    lemmatize: bool = False
+    stem: bool = False
+    width: Union[int, None] = None
+    height: Union[int, None] = None
+
+    def how_to_guide(self, height: int, width: int) -> List[Tuple[str, str]]:
+        """
+        how-to guide for plot(df, x)
+        """
+        vals = [self.top_words, self.stopword, self.lemmatize, self.stem, height, width]
+        names = [
+            "wordfreq.top_words",
+            "wordfreq.stopword",
+            "wordfreq.lemmatize",
+            "wordfreq.stem",
+            "height",
+            "width",
+        ]
+        descs = [
+            "Maximum number of most frequent words to display",
+            "Whether to remove stopwords",
+            "Whether to lemmatize the words",
+            "Whether to apply Potter Stem on the words",
+            "Height of the plot",
+            "Width of the plot",
+        ]
+        return [(f"'{name}': {val}", desc) for name, val, desc in zip(names, vals, descs)]
+
+
+class WordLength(BaseModel):
+    """
+    enable: bool, default True
+        Whether to create this element
+    bins: int, default 50
+        Number of bins in the histogram
+    yscale: str, default "linear"
+        Y-axis scale ("linear" or "log")
+    height: int, default "auto"
+        Height of the plot
+    width: int, default "auto"
+        Width of the plot
+    """
+
+    enable: bool = True
+    bins: int = 50
+    yscale: str = "linear"
+    width: Union[int, None] = None
+    height: Union[int, None] = None
+
+    def how_to_guide(self, height: int, width: int) -> List[Tuple[str, str]]:
+        """
+        how-to guide for plot(df, x)
+        """
+        vals = [self.bins, self.yscale, height, width]
+        names = ["wordlen.bins", "wordlen.yscale", "height", "width"]
+        descs = [
+            "Number of bins in the histogram",
+            'Y-axis scale ("linear" or "log")',
+            "Height of the plot",
+            "Width of the plot",
+        ]
+        return [(f"'{name}': {_form(val)}", desc) for name, val, desc in zip(names, vals, descs)]
+
+
+class Line(BaseModel):
+    """
+    enable: bool, default True
+        Whether to create this element
+    bins: int, default 50
+        Number of bins
+    ngroups: int, default 10
+        Maximum number of groups to display
+    sort_descending: bool, default True
+        Whether to sort the groups in descending order of frequency
+    yscale: str, default "linear"
+        The scale to show on the y axis. Can be "linear" or "log".
+    unit: str, default "auto"
+        Defines the time unit to group values over for a datetime column.
+        It can be "year", "quarter", "month", "week", "day", "hour",
+        "minute", "second". With default value "auto", it will use the
+        time unit such that the resulting number of groups is closest to 15
+    agg: str, default "mean"
+        Specify the aggregate to use when aggregating over a numeric column
+    height: int, default "auto"
+        Height of the plot
+    width: int, default "auto"
+        Width of the plot
+    """
+
+    # pylint: disable=too-many-instance-attributes
+
+    enable: bool = True
+    bins: int = 50
+    ngroups: int = 10
+    sort_descending: bool = True
+    yscale: str = "linear"
+    unit: str = "auto"
+    agg: str = "mean"
+    height: Union[int, None] = None
+    width: Union[int, None] = None
+
+    def nom_cont_how_to_guide(self, height: int, width: int) -> List[Tuple[str, str]]:
+        """
+        how-to guide for plot(df, nominal, continuous)
+        """
+        vals = [self.ngroups, self.sort_descending, height, width]
+        names = ["line.ngroups", "line.sort_descending", "height", "width"]
+        descs = [
+            "Maximum number of groups to display",
+            "Whether to sort the groups in descending order of frequency",
+            "Height of the plot",
+            "Width of the plot",
+        ]
+        return [(f"'{name}': {val}", desc) for name, val, desc in zip(names, vals, descs)]
+
+
+class Scatter(BaseModel):
+    """
+    enable: bool, default True
+        Whether to create this element
+    sample_size: int, default 1000
+        Number of points to randomly sample per partition
+    height: int, default "auto"
+        Height of the plot
+    width: int, default "auto"
+        Width of the plot
+    """
+
+    enable: bool = True
+    sample_size: int = 1000
+    height: Union[int, None] = None
+    width: Union[int, None] = None
+
+    def how_to_guide(self, height: int, width: int) -> List[Tuple[str, str]]:
+        """
+        how-to guide
+        """
+        vals = [self.sample_size, height, width]
+        names = ["scatter.sample_size", "height", "width"]
+        descs = [
+            "Number of points to randomly sample per partition",
+            "Height of the plot",
+            "Width of the plot",
+        ]
+        return [(f"'{name}': {val}", desc) for name, val, desc in zip(names, vals, descs)]
+
+
+class Hexbin(BaseModel):
+    """
+    enable: bool, default True
+        Whether to create this element
+    tile_size: float, default "auto"
+        The size of the tile in the hexbin plot. Measured from the middle
+        of a hexagon to its left or right corner.
+    height: int, default "auto"
+        Height of the plot
+    width: int, default "auto"
+        Width of the plot
+    """
+
+    enable: bool = True
+    tile_size: str = "auto"
+    height: Union[int, None] = None
+    width: Union[int, None] = None
+
+    def how_to_guide(self, tile_size: float, height: int, width: int) -> List[Tuple[str, str]]:
+        """
+        how-to guide
+        """
+        vals = [tile_size, height, width]
+        names = ["hexbin.tile_size", "height", "width"]
+        descs = [
+            "Tile size, measured from the middle of the hexagon to the left or right corner",
+            "Height of the plot",
+            "Width of the plot",
+        ]
+        return [(f"'{name}': {val}", desc) for name, val, desc in zip(names, vals, descs)]
+
+
+class Nested(BaseModel):
+    """
+    enable: bool, default True
+        Whether to create this element
+    ngroups: int, default 10
+        Maximum number of most frequent values from the first column to display
+    nsubgroups: int, default 5
+        Maximum number of most frequent values from the second column to display (computed
+        on the filtered data consisting of the most frequent values from the first column)
+    height: int, default "auto"
+        Height of the plot
+    width: int, default "auto"
+        Width of the plot
+    """
+
+    enable: bool = True
+    ngroups: int = 10
+    nsubgroups: int = 5
+    width: Union[int, None] = None
+    height: Union[int, None] = None
+
+    def how_to_guide(self, x: str, y: str, height: int, width: int) -> List[Tuple[str, str]]:
+        """
+        how-to guide
+        """
+        vals = [self.ngroups, self.nsubgroups, height, width]
+        names = ["nested.ngroups", "nested.nsubgroups", "height", "width"]
+        descs = [
+            f"Maximum number of most frequent values in column {x} to display",
+            f"""Maximum number of most frequent values in column {y} to display (computed
+            on the filtered data consisting of the most frequent values in column {x})""",
+            "Height of the plot",
+            "Width of the plot",
+        ]
+        return [(f"'{name}': {val}", desc) for name, val, desc in zip(names, vals, descs)]
+
+
+class Stacked(BaseModel):
+    """
+    enable: bool, default True
+        Whether to create this element
+    ngroups: int, default 10
+        Maximum number of most frequent values from the first column to display
+    nsubgroups: int, default 5
+        Maximum number of most frequent values from the second column to display (computed
+        on the filtered data consisting of the most frequent values from the first column)
+    unit: str, default "auto"
+        Defines the time unit to group values over for a datetime column.
+        It can be "year", "quarter", "month", "week", "day", "hour",
+        "minute", "second". With default value "auto", it will use the
+        time unit such that the resulting number of groups is closest to 15
+    sort_descending: bool, default True
+        Whether to sort the groups in descending order of frequency
+    height: int, default "auto"
+        Height of the plot
+    width: int, default "auto"
+        Width of the plot
+    """
+
+    enable: bool = True
+    ngroups: int = 10
+    nsubgroups: int = 5
+    unit: str = "auto"
+    sort_descending: bool = True
+    height: Union[int, None] = None
+    width: Union[int, None] = None
+
+    def how_to_guide(self, x: str, y: str, height: int, width: int) -> List[Tuple[str, str]]:
+        """
+        how-to guide
+        """
+        vals = [self.ngroups, self.nsubgroups, height, width]
+        names = ["stacked.ngroups", "stacked.nsubgroups", "height", "width"]
+        descs = [
+            f"Maximum number of most frequent values in column {x} to display",
+            f"""Maximum number of most frequent values in column {y} to display (computed
+            on the filtered data consisting of the most frequent values in column {x})""",
+            "Height of the plot",
+            "Width of the plot",
+        ]
+        return [(f"'{name}': {val}", desc) for name, val, desc in zip(names, vals, descs)]
+
+
+class Heatmap(BaseModel):
+    """
+    enable: bool, default True
+        Whether to create this element
+    ngroups: int, default 10
+        Maximum number of most frequent values from the first column to display
+    nsubgroups: int, default 5
+        Maximum number of most frequent values from the second column to display (computed
+        on the filtered data consisting of the most frequent values from the first column)
+    height: int, default "auto"
+        Height of the plot
+    width: int, default "auto"
+        Width of the plot
+    """
+
+    enable: bool = True
+    ngroups: int = 10
+    nsubgroups: int = 5
+    height: Union[int, None] = None
+    width: Union[int, None] = None
+
+    def how_to_guide(self, x: str, y: str, height: int, width: int) -> List[Tuple[str, str]]:
+        """
+        how-to guide for plot(df, nominal, nominal)
+        """
+        vals = [self.ngroups, self.nsubgroups, height, width]
+        names = ["heatmap.ngroups", "heatmap.nsubgroups", "height", "width"]
+        descs = [
+            f"Maximum number of most frequent values in column {x} to display",
+            f"""Maximum number of most frequent values in column {y} to display (computed
+            on the filtered data consisting of the most frequent values in column {x})""",
+            "Height of the plot",
+            "Width of the plot",
+        ]
+        return [(f"'{name}': {val}", desc) for name, val, desc in zip(names, vals, descs)]
+
+    def missing_how_to_guide(self, height: int, width: int) -> List[Tuple[str, str]]:
+        """
+        how-to guide for plot_missing(df)
+        """
+        vals = [height, width]
+        names = ["height", "width"]
+        descs = ["Height of the plot", "Width of the plot"]
+        return [(f"'{name}': {val}", desc) for name, val, desc in zip(names, vals, descs)]
+
+
+class Pearson(BaseModel):
+    """
+    enable: bool, default True
+        Whether to create this element
+    height: int, default "auto"
+        Height of the plot
+    width: int, default "auto"
+        Width of the plot
+    """
+
+    enable: bool = True
+    height: Union[int, None] = None
+    width: Union[int, None] = None
+
+    def how_to_guide(self, height: int, width: int) -> List[Tuple[str, str]]:
+        """
+        how-to guide
+        """
+        vals = [height, width]
+        names = ["height", "width"]
+        descs = ["Height of the plot", "Width of the plot"]
+        return [(f"'{name}': {val}", desc) for name, val, desc in zip(names, vals, descs)]
+
+
+class Spearman(BaseModel):
+    """
+    height: int, default "auto"
+        Height of the plot
+    width: int, default "auto"
+        Width of the plot
+    """
+
+    enable: bool = True
+    height: Union[int, None] = None
+    width: Union[int, None] = None
+
+    def how_to_guide(self, height: int, width: int) -> List[Tuple[str, str]]:
+        """
+        how-to guide
+        """
+        vals = [height, width]
+        names = ["height", "width"]
+        descs = ["Height of the plot", "Width of the plot"]
+        return [(f"'{name}': {val}", desc) for name, val, desc in zip(names, vals, descs)]
+
+
+class KendallTau(BaseModel):
+    """
+    enable: bool, default True
+        Whether to create this element
+    height: int, default "auto"
+        Height of the plot
+    width: int, default "auto"
+        Width of the plot
+    """
+
+    enable: bool = True
+    height: Union[int, None] = None
+    width: Union[int, None] = None
+
+    def how_to_guide(self, height: int, width: int) -> List[Tuple[str, str]]:
+        """
+        how-to guide
+        """
+        vals = [height, width]
+        names = ["height", "width"]
+        descs = ["Height of the plot", "Width of the plot"]
+        return [(f"'{name}': {val}", desc) for name, val, desc in zip(names, vals, descs)]
+
+
+class Spectrum(BaseModel):
+    """
+    enable: bool, default True
+        Whether to create this element
+    bins: int, default 20
+        Number of bins
+    height: int, default "auto"
+        Height of the plot
+    width: int, default "auto"
+        Width of the plot
+    """
+
+    enable: bool = True
+    bins: int = 20
+    height: Union[int, None] = None
+    width: Union[int, None] = None
+
+    def how_to_guide(self, height: int, width: int) -> List[Tuple[str, str]]:
+        """
+        how-to guide
+        """
+        vals = [self.bins, height, width]
+        names = ["spectrum.bins", "height", "width"]
+        descs = ["Number of bins", "Height of the plot", "Width of the plot"]
+        return [(f"'{name}': {val}", desc) for name, val, desc in zip(names, vals, descs)]
+
+
+class Dendrogram(BaseModel):
+    """
+    enable: bool, default True
+        Whether to create this element
+    height: int, default "auto"
+        Height of the plot
+    width: int, default "auto"
+        Width of the plot
+    """
+
+    enable: bool = True
+    height: Union[int, None] = None
+    width: Union[int, None] = None
+
+    def how_to_guide(self, height: int, width: int) -> List[Tuple[str, str]]:
+        """
+        how-to guide
+        """
+        vals = [height, width]
+        names = ["height", "width"]
+        descs = ["Height of the plot", "Width of the plot"]
+        return [(f"'{name}': {val}", desc) for name, val, desc in zip(names, vals, descs)]
+
+
+class PDF(BaseModel):
+    """
+    enable: bool, default True
+        Whether to create this element
+    sample_size:
+        Number of evenly spaced samples between the minimum and maximum values to compute the pdf at
+    height: int, default "auto"
+        Height of the plot
+    width: int, default "auto"
+        Width of the plot
+    """
+
+    enable: bool = True
+    sample_size: int = 100
+    height: Union[int, None] = None
+    width: Union[int, None] = None
+
+    def how_to_guide(self, height: int, width: int) -> List[Tuple[str, str]]:
+        """
+        how-to guide
+        """
+        vals = [self.sample_size, height, width]
+        names = ["pdf.sample_size", "height", "width"]
+        descs = [
+            """Number of evenly spaced samples between the minimum and maximum values to
+            compute the pdf at""",
+            "Height of the plot",
+            "Width of the plot",
+        ]
+        return [(f"'{name}': {val}", desc) for name, val, desc in zip(names, vals, descs)]
+
+
+class CDF(BaseModel):
+    """
+    enable: bool, default True
+        Whether to create this element
+    sample_size:
+        Number of evenly spaced samples between the minimum and maximum values to compute the cdf at
+    height: int, default "auto"
+        Height of the plot
+    width: int, default "auto"
+        Width of the plot
+    """
+
+    enable: bool = True
+    sample_size: int = 100
+    height: Union[int, None] = None
+    width: Union[int, None] = None
+
+    def how_to_guide(self, height: int, width: int) -> List[Tuple[str, str]]:
+        """
+        how-to guide
+        """
+        vals = [self.sample_size, height, width]
+        names = ["pdf.sample_size", "height", "width"]
+        descs = [
+            """Number of evenly spaced samples between the minimum and maximum values to
+            compute the pdf at""",
+            "Height of the plot",
+            "Width of the plot",
+        ]
+        return [(f"'{name}': {val}", desc) for name, val, desc in zip(names, vals, descs)]
+
+
+def _form(val: Any) -> Any:
+    """
+    Format a value for the how-to guide
+    """
+    return f"'{val}'" if isinstance(val, str) else val
+
+
+class Config(BaseModel):
+    """
+    Configuration class
+    """
+
+    # pylint: disable=too-many-instance-attributes
+    hist: Hist = Field(default_factory=Hist)
+    bar: Bar = Field(default_factory=Bar)
+    pie: Pie = Field(default_factory=Pie)
+    line: Line = Field(default_factory=Line)
+    stats: Stats = Field(default_factory=Stats)
+    wordcloud: WordCloud = Field(default_factory=WordCloud)
+    wordfreq: WordFrequency = Field(default_factory=WordFrequency)
+    wordlen: WordLength = Field(default_factory=WordLength)
+    qqnorm: QQNorm = Field(default_factory=QQNorm)
+    kde: KDE = Field(default_factory=KDE)
+    box: Box = Field(default_factory=Box)
+    scatter: Scatter = Field(default_factory=Scatter)
+    nested: Nested = Field(default_factory=Nested)
+    stacked: Stacked = Field(default_factory=Stacked)
+    heatmap: Heatmap = Field(default_factory=Heatmap)
+    insight: Insight = Field(default_factory=Insight)
+    hexbin: Hexbin = Field(default_factory=Hexbin)
+    pearson: Pearson = Field(default_factory=Pearson)
+    spearman: Spearman = Field(default_factory=Spearman)
+    kendall: KendallTau = Field(default_factory=KendallTau)
+    spectrum: Spectrum = Field(default_factory=Spectrum)
+    dendro: Dendrogram = Field(default_factory=Dendrogram)
+    pdf: PDF = Field(default_factory=PDF)
+    cdf: CDF = Field(default_factory=CDF)
+    plot: Plot = Field(default_factory=Plot)
+
+    @classmethod
+    def from_dict(
+        cls, display: Optional[List[str]] = None, config: Optional[Dict[str, Any]] = None
+    ) -> Config:
+        """
+        Converts an dictionary instance into a config class
+        """
+        cfg = cls()
+
+        if display:
+            display = [DISPLAY_MAP[disp] for disp in display]
+            # set all plots not in display list to enable=False except for Plot class
+            for plot in set(vars(cfg).keys()) - set(display) - {"plot"}:
+                setattr(getattr(cfg, plot), "enable", False)
+
+        if config:
+            # get the global parameters from config
+            global_params = {key: config[key] for key in config if "." not in key}
+            for param, val in global_params.items():
+                # set the parameter to the specified value for each plot that
+                # has this parameter
+                if param not in vars(cfg.plot).keys():
+                    raise Exception(param + " does not exist")
+                else:
+                    for plot in vars(cfg).keys():
+                        if hasattr(getattr(cfg, plot), param):
+                            setattr(getattr(cfg, plot), param, val)
+
+                    # ngroups applies to "bars" and "slices" for the bar and pie charts
+                    if param == "ngroups":
+                        setattr(getattr(cfg, "bar"), "bars", val)
+                        setattr(getattr(cfg, "pie"), "slices", val)
+
+            # get the local parameters from config
+            local_params = {key: config[key] for key in config if key not in global_params}
+            for key, value in local_params.items():
+                plot, rest = key.split(".", 1)
+                param = rest.replace(".", "__")
+                if plot not in vars(cfg).keys():
+                    raise Exception(plot + " does not exist")
+                elif not hasattr(getattr(cfg, plot), param):
+                    raise Exception(key.replace(f"{plot}.", "") + " does not exist")
+                else:
+                    setattr(getattr(cfg, plot), param, value)
+
+        return cfg
diff --git a/dataprep/eda/container.py b/dataprep/eda/container.py
index b22919dd8..6e171e9c3 100644
--- a/dataprep/eda/container.py
+++ b/dataprep/eda/container.py
@@ -2,16 +2,19 @@
     This module implements the Container class.
 """
 
+import random
 import sys
 import webbrowser
-import random
 from tempfile import NamedTemporaryFile
 from typing import Any, Dict
-from bokeh.io import output_notebook
+
 from bokeh.embed import components
+from bokeh.io import output_notebook
 from bokeh.resources import INLINE
 from jinja2 import Environment, PackageLoader
+
 from ..utils import is_notebook
+from .configs import Config
 
 output_notebook(INLINE, hide_banner=True)  # for offline usage
 
@@ -44,16 +47,13 @@ class Container:
     This class creates a customized Container object for the plot* function.
     """
 
-    def __init__(
-        self,
-        to_render: Dict[str, Any],
-        visual_type: str,
-    ) -> None:
+    def __init__(self, to_render: Dict[str, Any], visual_type: str, cfg: Config) -> None:
         self.context = Context(**to_render)
         setattr(self.context, "rnd", random.randint(0, 9999))
         if visual_type in GRID_VISUAL_TYPES:
             self.template_base = ENV_LOADER.get_template("grid_base.html")
         elif visual_type in TAB_VISUAL_TYPES:
+            setattr(self.context, "highlight", cfg.insight.enable)
             if to_render.get("tabledata"):
                 self.context.meta.insert(0, "Stats")  # type: ignore
             if visual_type == "correlation_impact":
@@ -116,6 +116,14 @@ class Context:
     """
     Define the context class that stores all the parameters needed by template engine.
     The instance is read-only.
+
+    Since we use same template to render different components without strict evaluation,
+    when the engine tries to read an attribute from Context object, it will get None if
+    the attribute doesn't exist, making the rendering keep going instead of being
+    interrupted.
+
+    Here we override __getitem__() and __getattr__() to do the trick, it also makes
+    the object have a key-value pair which works as same as accessing its attribute.
     """
 
     _title = "DataPrep.EDA Report"
@@ -129,14 +137,21 @@ def __init__(self, **param: Any) -> None:
 
         for attr, value in param.items():
             if attr == "layout":
-                setattr(self, "components", components(value))
+                if len(value) == 0:
+                    setattr(self, "components", ("", []))
+                else:
+                    setattr(self, "components", components(value))
+            elif attr == "meta":
+                setattr(self, attr, value)
+                figs = [val for val in value if val != "Stats"]
+                setattr(self, "figs", figs)
             else:
                 setattr(self, attr, value)
 
     def __getitem__(self, key: str) -> Any:
-        try:
+        if hasattr(self, key):
             return getattr(self, key)
-        except KeyError:
+        else:
             return None
 
     def __getattr__(self, attr: str) -> None:
diff --git a/dataprep/eda/correlation/__init__.py b/dataprep/eda/correlation/__init__.py
index 7d40af7f0..b348a4a86 100644
--- a/dataprep/eda/correlation/__init__.py
+++ b/dataprep/eda/correlation/__init__.py
@@ -2,15 +2,16 @@
     This module implements the plot_correlation(df) function.
 """
 
-from typing import Optional, Tuple, Union
+from typing import Any, Dict, List, Optional, Tuple, Union
 
 import dask.dataframe as dd
 import pandas as pd
 
+from ..configs import Config
+from ..container import Container
 from ..progress_bar import ProgressBar
 from .compute import compute_correlation
 from .render import render_correlation
-from ..container import Container
 
 __all__ = ["render_correlation", "compute_correlation", "plot_correlation"]
 
@@ -22,6 +23,8 @@ def plot_correlation(
     *,
     value_range: Optional[Tuple[float, float]] = None,
     k: Optional[int] = None,
+    config: Optional[Dict[str, Any]] = None,
+    display: Optional[List[str]] = None,
     progress: bool = True,
 ) -> Container:
     """
@@ -42,9 +45,14 @@ def plot_correlation(
         Range of value.
     k
         Choose top-k element.
+    config
+        A dictionary for configuring the visualizations
+        E.g. config={"scatter.sample_size": 5000}
+    display
+        A list containing the names of the visualizations to display
+        E.g. display=["Pearson"]
     progress
         Enable the progress bar.
-
     Examples
     --------
     >>> from dataprep.eda.correlation.computation import plot_correlation
@@ -64,8 +72,10 @@ def plot_correlation(
     This function only supports numerical or categorical data,
     and it is better to drop None, Nan and Null value before using it
     """
+    cfg = Config.from_dict(display, config)
+
     with ProgressBar(minimum=1, disable=not progress):
-        itmdt = compute_correlation(df, x=x, y=y, value_range=value_range, k=k)
-    to_render = render_correlation(itmdt)
+        itmdt = compute_correlation(df, x, y, cfg=cfg, value_range=value_range, k=k)
+    to_render = render_correlation(itmdt, cfg)
 
-    return Container(to_render, itmdt.visual_type)
+    return Container(to_render, itmdt.visual_type, cfg)
diff --git a/dataprep/eda/correlation/compute/__init__.py b/dataprep/eda/correlation/compute/__init__.py
index 324723385..c8727dad7 100644
--- a/dataprep/eda/correlation/compute/__init__.py
+++ b/dataprep/eda/correlation/compute/__init__.py
@@ -1,15 +1,16 @@
 """This module implements the intermediates computation
 for plot_correlation(df) function."""
 
-from typing import Optional, Tuple
+from typing import Optional, Tuple, List, Dict, Union, Any
 
+from ...configs import Config
 from ...data_array import DataArray, DataFrame
+from ...dtypes import NUMERICAL_DTYPES
 from ...intermediate import Intermediate
+from ...utils import to_dask
 from .bivariate import _calc_bivariate
 from .nullivariate import _calc_nullivariate
 from .univariate import _calc_univariate
-from ...dtypes import NUMERICAL_DTYPES
-from ...utils import to_dask
 
 __all__ = ["compute_correlation"]
 
@@ -19,35 +20,53 @@ def compute_correlation(
     x: Optional[str] = None,
     y: Optional[str] = None,
     *,
+    cfg: Union[Config, Dict[str, Any], None] = None,
+    display: Optional[List[str]] = None,
     value_range: Optional[Tuple[float, float]] = None,
     k: Optional[int] = None,
 ) -> Intermediate:
+    # pylint: disable=too-many-arguments
     """
     Parameters
     ----------
     df
         The pandas dataframe for which plots are calculated for each column.
+    cfg
+        Config instance
     x
         A valid column name of the dataframe
     y
         A valid column name of the dataframe
     value_range
         If the correlation value is out of the range, don't show it.
+    cfg: Union[Config, Dict[str, Any], None], default None
+        When a user call plot_correlation(), the created Config object will be passed to
+        compute_correlation().
+        When a user call compute_correlation() directly, if he/she wants to customize the output,
+        cfg is a dictionary for configuring. If not, cfg is None and
+        default values will be used for parameters.
+    display: Optional[List[str]], default None
+        A list containing the names of the visualizations to display. Only exist when
+        a user call compute_correlation() directly and want to customize the output
     k
         Choose top-k element
     """
+    if isinstance(cfg, dict):
+        cfg = Config.from_dict(display, cfg)
+    elif not cfg:
+        cfg = Config()
     if x is not None and y is not None:
         df = to_dask(df.select_dtypes(NUMERICAL_DTYPES))
     else:
         df = DataArray(df).select_num_columns()
 
     if x is None and y is None:  # pylint: disable=no-else-return
-        return _calc_nullivariate(df, value_range=value_range, k=k)
+        return _calc_nullivariate(df, cfg, value_range=value_range, k=k)
     elif x is not None and y is None:
-        return _calc_univariate(df, x=x, value_range=value_range, k=k)
+        return _calc_univariate(df, x, cfg, value_range=value_range, k=k)
     elif x is None and y is not None:
         raise ValueError("Please give the column name to x instead of y")
     elif x is not None and y is not None:
-        return _calc_bivariate(df, x=x, y=y, k=k)
+        return _calc_bivariate(df, cfg, x, y, k=k)
 
     raise ValueError("Not Possible")
diff --git a/dataprep/eda/correlation/compute/bivariate.py b/dataprep/eda/correlation/compute/bivariate.py
index 146ff4a64..2fe26695e 100644
--- a/dataprep/eda/correlation/compute/bivariate.py
+++ b/dataprep/eda/correlation/compute/bivariate.py
@@ -5,15 +5,17 @@
 from typing import Optional, Tuple
 
 import dask
-import dask.dataframe as dd
 import dask.array as da
+import dask.dataframe as dd
 import numpy as np
 
+from ...configs import Config
 from ...intermediate import Intermediate
 
 
 def _calc_bivariate(
     df: dd.DataFrame,
+    cfg: Config,
     x: str,
     y: str,
     *,
@@ -25,7 +27,7 @@ def _calc_bivariate(
         raise ValueError(f"{y} not in columns names")
 
     df = df[[x, y]].dropna()
-    coeffs, df_smp, influences = scatter_with_regression(df, sample_size=1000, k=k)
+    coeffs, df_smp, influences = scatter_with_regression(df, cfg, k=k)
 
     coeffs, df_smp, influences = dask.compute(coeffs, df_smp, influences)
 
@@ -47,26 +49,18 @@ def _calc_bivariate(
 
 
 def scatter_with_regression(
-    df: dd.DataFrame, sample_size: int, k: Optional[int] = None
+    df: dd.DataFrame, cfg: Config, k: Optional[int] = None
 ) -> Tuple[Tuple[da.Array, da.Array], Tuple[da.Array, da.Array], Optional[da.Array]]:
-    """Calculate pearson correlation on 2 given arrays.
-
-    Parameters
-    ----------
-    df
-        dataframe
-    sample_size
-        Number of points to show in the scatter plot
-    k : Optional[int] = None
-        Highlight k points which influence pearson correlation most
-    """
+    """Sample and compute regression line"""
     df["ones"] = 1
     arr = df.to_dask_array(lengths=True)
 
     (coeffa, coeffb), _, _, _ = da.linalg.lstsq(arr[:, [0, 2]], arr[:, 1])
 
     df = df.drop(columns=["ones"])
-    df_smp = df.map_partitions(lambda x: x.sample(min(sample_size, x.shape[0])), meta=df)
+    df_smp = df.map_partitions(
+        lambda x: x.sample(min(cfg.scatter.sample_size, x.shape[0])), meta=df
+    )
     # TODO influences should not be computed on a sample
     influences = (
         pearson_influence(
diff --git a/dataprep/eda/correlation/compute/common.py b/dataprep/eda/correlation/compute/common.py
index e5220bc27..309204f54 100644
--- a/dataprep/eda/correlation/compute/common.py
+++ b/dataprep/eda/correlation/compute/common.py
@@ -4,7 +4,8 @@
 
 import dask
 import numpy as np
-from bottleneck import rankdata as rankdata_, nanrankdata as nanrankdata_
+from bottleneck import nanrankdata as nanrankdata_
+from bottleneck import rankdata as rankdata_
 from scipy.stats import kendalltau as kendalltau_
 
 
diff --git a/dataprep/eda/correlation/compute/nullivariate.py b/dataprep/eda/correlation/compute/nullivariate.py
index e13ee9368..6a2ceaf14 100644
--- a/dataprep/eda/correlation/compute/nullivariate.py
+++ b/dataprep/eda/correlation/compute/nullivariate.py
@@ -3,21 +3,23 @@
 Currently this boils down to pandas' implementation."""
 
 from functools import partial
-from typing import Dict, Optional, Tuple, List, Any
+from typing import Any, Dict, List, Optional, Tuple
 
 import dask
 import dask.array as da
 import numpy as np
 import pandas as pd
 
+from ...configs import Config
 from ...data_array import DataArray
 from ...intermediate import Intermediate
-from .common import CorrelationMethod
 from ...utils import cut_long_name
+from .common import CorrelationMethod
 
 
 def _calc_nullivariate(
     df: DataArray,
+    cfg: Config,
     *,
     value_range: Optional[Tuple[float, float]] = None,
     k: Optional[int] = None,
@@ -25,52 +27,100 @@ def _calc_nullivariate(
     # pylint: disable=too-many-statements,too-many-locals,too-many-branches
 
     most_show = 6  # the most number of column/row to show in "insight"
-    # longest = 5  # the longest length of word to show in "insight"
 
     if value_range is not None and k is not None:
         raise ValueError("value_range and k cannot be present in both")
 
-    cordx, cordy, corrs = correlation_nxn(df)
+    cordx, cordy, corrs = correlation_nxn(df, cfg)
 
     # The computations below is not expensive (scales with # of columns)
     # So we do them in pandas
 
     (corrs,) = dask.compute(corrs)
-    pearson_corr, spearman_corr, kendalltau_corr = corrs.values()
 
-    pearson_pos_max, pearson_neg_max, pearson_mean, pearson_pos_cols, pearson_neg_cols = most_corr(
-        pearson_corr
-    )
-    (
-        spearman_pos_max,
-        spearman_neg_max,
-        spearman_mean,
-        spearman_pos_cols,
-        spearman_neg_cols,
-    ) = most_corr(spearman_corr)
-    (
-        kendalltau_pos_max,
-        kendalltau_neg_max,
-        kendalltau_mean,
-        kendalltau_pos_cols,
-        kendalltau_neg_cols,
-    ) = most_corr(kendalltau_corr)
-    pearson_min, pearson_cols = least_corr(pearson_corr)
-    spearman_min, spearman_cols = least_corr(spearman_corr)
-    kendalltau_min, kendalltau_cols = least_corr(kendalltau_corr)
-
-    p_p_corr = create_string("positive", pearson_pos_cols, most_show, df)
-    s_p_corr = create_string("positive", spearman_pos_cols, most_show, df)
-    k_p_corr = create_string("positive", kendalltau_pos_cols, most_show, df)
-    p_n_corr = create_string("negative", pearson_neg_cols, most_show, df)
-    s_n_corr = create_string("negative", spearman_neg_cols, most_show, df)
-    k_n_corr = create_string("negative", kendalltau_neg_cols, most_show, df)
-    p_corr = create_string("least", pearson_cols, most_show, df)
-    s_corr = create_string("least", spearman_cols, most_show, df)
-    k_corr = create_string("least", kendalltau_cols, most_show, df)
+    if cfg.stats.enable or cfg.insight.enable:
+        if cfg.stats.enable or cfg.pearson.enable:
+            (
+                pearson_pos_max,
+                pearson_neg_max,
+                pearson_mean,
+                pearson_pos_cols,
+                pearson_neg_cols,
+            ) = most_corr(corrs[CorrelationMethod.Pearson])
+            pearson_min, pearson_cols = least_corr(corrs[CorrelationMethod.Pearson])
+        if cfg.stats.enable or cfg.spearman.enable:
+            (
+                spearman_pos_max,
+                spearman_neg_max,
+                spearman_mean,
+                spearman_pos_cols,
+                spearman_neg_cols,
+            ) = most_corr(corrs[CorrelationMethod.Spearman])
+            spearman_min, spearman_cols = least_corr(corrs[CorrelationMethod.Spearman])
+        if cfg.stats.enable or cfg.kendall.enable:
+            (
+                kendalltau_pos_max,
+                kendalltau_neg_max,
+                kendalltau_mean,
+                kendalltau_pos_cols,
+                kendalltau_neg_cols,
+            ) = most_corr(corrs[CorrelationMethod.KendallTau])
+            kendalltau_min, kendalltau_cols = least_corr(corrs[CorrelationMethod.KendallTau])
+
+    if cfg.stats.enable:
+        tabledata = {
+            "Highest Positive Correlation": {
+                "Pearson": pearson_pos_max,
+                "Spearman": spearman_pos_max,
+                "KendallTau": kendalltau_pos_max,
+            },
+            "Highest Negative Correlation": {
+                "Pearson": pearson_neg_max,
+                "Spearman": spearman_neg_max,
+                "KendallTau": kendalltau_neg_max,
+            },
+            "Lowest Correlation": {
+                "Pearson": pearson_min,
+                "Spearman": spearman_min,
+                "KendallTau": kendalltau_min,
+            },
+            "Mean Correlation": {
+                "Pearson": pearson_mean,
+                "Spearman": spearman_mean,
+                "KendallTau": kendalltau_mean,
+            },
+        }
+
+    if cfg.insight.enable:
+        insights: Dict[str, List[Any]] = {}
+        if cfg.pearson.enable:
+            p_p_corr = create_string("positive", pearson_pos_cols, most_show, df)
+            p_n_corr = create_string("negative", pearson_neg_cols, most_show, df)
+            p_corr = create_string("least", pearson_cols, most_show, df)
+            insights["Pearson"] = [p_p_corr, p_n_corr, p_corr]
+        if cfg.spearman.enable:
+            s_p_corr = create_string("positive", spearman_pos_cols, most_show, df)
+            s_n_corr = create_string("negative", spearman_neg_cols, most_show, df)
+            s_corr = create_string("least", spearman_cols, most_show, df)
+            insights["Spearman"] = [s_p_corr, s_n_corr, s_corr]
+        if cfg.kendall.enable:
+            k_p_corr = create_string("positive", kendalltau_pos_cols, most_show, df)
+            k_n_corr = create_string("negative", kendalltau_neg_cols, most_show, df)
+            k_corr = create_string("least", kendalltau_cols, most_show, df)
+            insights["KendallTau"] = [k_p_corr, k_n_corr, k_corr]
 
     dfs = {}
     for method, corr in corrs.items():
+        if (  # pylint: disable=too-many-boolean-expressions
+            method == CorrelationMethod.Pearson
+            and not cfg.pearson.enable
+            or method == CorrelationMethod.Spearman
+            and not cfg.spearman.enable
+            or method == CorrelationMethod.KendallTau
+            and not cfg.kendall.enable
+        ):
+            continue
+
         ndf = pd.DataFrame(
             {
                 "x": df.columns[cordx],
@@ -93,38 +143,13 @@ def _calc_nullivariate(
         data=dfs,
         axis_range=list(df.columns.unique()),
         visual_type="correlation_impact",
-        tabledata={
-            "Highest Positive Correlation": {
-                "Pearson": pearson_pos_max,
-                "Spearman": spearman_pos_max,
-                "KendallTau": kendalltau_pos_max,
-            },
-            "Highest Negative Correlation": {
-                "Pearson": pearson_neg_max,
-                "Spearman": spearman_neg_max,
-                "KendallTau": kendalltau_neg_max,
-            },
-            "Lowest Correlation": {
-                "Pearson": pearson_min,
-                "Spearman": spearman_min,
-                "KendallTau": kendalltau_min,
-            },
-            "Mean Correlation": {
-                "Pearson": pearson_mean,
-                "Spearman": spearman_mean,
-                "KendallTau": kendalltau_mean,
-            },
-        },
-        insights={
-            "Pearson": [p_p_corr, p_n_corr, p_corr],
-            "Spearman": [s_p_corr, s_n_corr, s_corr],
-            "KendallTau": [k_p_corr, k_n_corr, k_corr],
-        },
+        tabledata=tabledata if cfg.stats.enable else {},
+        insights=insights if cfg.insight.enable else {},
     )
 
 
 def correlation_nxn(
-    df: DataArray,
+    df: DataArray, cfg: Config
 ) -> Tuple[np.ndarray, np.ndarray, Dict[CorrelationMethod, da.Array]]:
     """
     Calculation of a n x n correlation matrix for n columns
@@ -138,11 +163,14 @@ def correlation_nxn(
     cordx, cordy = np.meshgrid(range(ncols), range(ncols))
     cordx, cordy = cordy.ravel(), cordx.ravel()
 
-    corrs = {
-        CorrelationMethod.Pearson: _pearson_nxn(df),
-        CorrelationMethod.Spearman: _spearman_nxn(df),
-        CorrelationMethod.KendallTau: _kendall_tau_nxn(df),
-    }
+    corrs: Dict[CorrelationMethod, da.Array] = {}
+
+    if cfg.pearson.enable or cfg.stats.enable:
+        corrs[CorrelationMethod.Pearson] = _pearson_nxn(df)
+    if cfg.spearman.enable or cfg.stats.enable:
+        corrs[CorrelationMethod.Spearman] = _spearman_nxn(df)
+    if cfg.kendall.enable or cfg.stats.enable:
+        corrs[CorrelationMethod.KendallTau] = _kendall_tau_nxn(df)
 
     return cordx, cordy, corrs
 
diff --git a/dataprep/eda/correlation/compute/univariate.py b/dataprep/eda/correlation/compute/univariate.py
index 9b6122165..553f9cd50 100644
--- a/dataprep/eda/correlation/compute/univariate.py
+++ b/dataprep/eda/correlation/compute/univariate.py
@@ -9,14 +9,16 @@
 import numpy as np
 import pandas as pd
 
+from ...configs import Config
 from ...data_array import DataArray
 from ...intermediate import Intermediate
-from .common import CorrelationMethod, kendalltau, nanrankdata, corrcoef
+from .common import CorrelationMethod, corrcoef, kendalltau, nanrankdata
 
 
 def _calc_univariate(
     df: DataArray,
-    x: Optional[str] = None,
+    x: str,
+    cfg: Config,
     *,
     value_range: Optional[Tuple[float, float]] = None,
     k: Optional[int] = None,
@@ -28,6 +30,8 @@ def _calc_univariate(
         The dataframe for which plots are calculated.
     x
         A valid column name of the dataframe.
+    cfg
+        Config instance
     value_range
         If the correlation value is out of the range, don't show it.
     k
@@ -45,7 +49,13 @@ def _calc_univariate(
     xarr = df.values[:, df.columns == x]
     data = df.values[:, df.columns != x]
 
-    funcs = [_pearson_1xn, _spearman_1xn, _kendall_tau_1xn]
+    funcs = []
+    if cfg.pearson.enable:
+        funcs.append(_pearson_1xn)
+    if cfg.spearman.enable:
+        funcs.append(_spearman_1xn)
+    if cfg.kendall.enable:
+        funcs.append(_kendall_tau_1xn)
 
     dfs = {}
     (computed,) = dask.compute(
diff --git a/dataprep/eda/correlation/render.py b/dataprep/eda/correlation/render.py
index 62a62b4dd..a187f8139 100644
--- a/dataprep/eda/correlation/render.py
+++ b/dataprep/eda/correlation/render.py
@@ -2,7 +2,7 @@
     This module implements the visualization for
     plot_correlation(df) function
 """
-from typing import List, Optional, Sequence, Tuple, Any, Dict
+from typing import Any, Dict, List, Sequence, Tuple
 
 import numpy as np
 from bokeh.layouts import column, row
@@ -25,49 +25,37 @@
 from bokeh.models.widgets import Panel, Tabs
 from bokeh.plotting import Figure, figure
 
+from ..configs import Config
 from ..intermediate import Intermediate
 from ..palette import BRG, RDBU
 
 __all__ = ["render_correlation"]
 
 
-def render_correlation(
-    itmdt: Intermediate,
-    plot_width: int = 400,
-    plot_height: int = 400,
-    palette: Optional[Sequence[str]] = None,
-) -> Figure:
+def render_correlation(itmdt: Intermediate, cfg: Config) -> Any:
     """
     Render a correlation plot
 
     Parameters
     ----------
     itmdt
-    plot_width
-        The width of the plot
-    plot_height
-        The height of the plot
-    palette
-        The palette to use. By default (None),
-        the palette will be automatically chosen based on different visualization types.
-
-    Returns
-    -------
-    Figure
-        The bokeh Figure instance.
+        Intermediate computations
+    cfg
+        Config instance
     """
+    plot_width = cfg.plot.width if cfg.plot.width is not None else 400
+    plot_height = cfg.plot.height if cfg.plot.height is not None else 400
+
     if itmdt.visual_type is None:
         visual_elem = Figure()
     elif itmdt.visual_type == "correlation_impact":
-        visual_elem = render_correlation_impact(itmdt, plot_width, plot_height, palette or RDBU)
+        visual_elem = render_correlation_impact(itmdt, plot_width, plot_height, cfg)
     elif itmdt.visual_type == "correlation_heatmaps":
-        visual_elem = render_correlation_heatmaps(itmdt, plot_width, plot_height, palette or RDBU)
+        visual_elem = render_correlation_heatmaps(itmdt, plot_width, plot_height)
     elif itmdt.visual_type == "correlation_single_heatmaps":
-        visual_elem = render_correlation_single_heatmaps(
-            itmdt, plot_width, plot_height, palette or RDBU
-        )
+        visual_elem = render_correlation_single_heatmaps(itmdt, plot_width, plot_height, cfg)
     elif itmdt.visual_type == "correlation_scatter":
-        visual_elem = render_scatter(itmdt, plot_width, plot_height, palette or BRG)
+        visual_elem = render_scatter(itmdt, plot_width, plot_height, cfg)
     elif itmdt.visual_type == "correlation_crossfilter":
         visual_elem = render_crossfilter(itmdt, plot_width, plot_height)
     else:
@@ -126,14 +114,13 @@ def tweak_figure(fig: Figure) -> None:
 
 
 def render_correlation_impact(
-    itmdt: Intermediate, plot_width: int, plot_height: int, palette: Sequence[str]
+    itmdt: Intermediate, plot_width: int, plot_height: int, cfg: Config
 ) -> Dict[str, Any]:
     """
     Render correlation heatmaps in to tabs
     """
     tabs: List[Panel] = []
     tooltips = [("x", "@x"), ("y", "@y"), ("correlation", "@correlation{1.11}")]
-    axis_range = itmdt["axis_range"]
 
     for method, df in itmdt["data"].items():
         # in case of numerical column names
@@ -141,9 +128,9 @@ def render_correlation_impact(
         df["x"] = df["x"].apply(str)
         df["y"] = df["y"].apply(str)
 
-        mapper, color_bar = create_color_mapper(palette)
-        x_range = FactorRange(*axis_range)
-        y_range = FactorRange(*reversed(axis_range))
+        mapper, color_bar = create_color_mapper(RDBU)
+        x_range = FactorRange(*itmdt["axis_range"])
+        y_range = FactorRange(*reversed(itmdt["axis_range"]))
         fig = Figure(
             x_range=x_range,
             y_range=y_range,
@@ -179,12 +166,27 @@ def render_correlation_impact(
         "layout": [panel.child for panel in tabs],
         "meta": [panel.title for panel in tabs],
         "container_width": plot_width + 150,
+        "how_to_guide": corr_how_to_guides(cfg, plot_height, plot_width),
     }
 
 
-def render_correlation_heatmaps(
-    itmdt: Intermediate, plot_width: int, plot_height: int, palette: Sequence[str]
-) -> Tabs:
+def corr_how_to_guides(cfg: Config, height: int, width: int) -> Dict[str, List[Tuple[str, str]]]:
+    """
+    How-to guide for correlation_impact
+    """
+    htgs: Dict[str, List[Tuple[str, str]]] = {}
+
+    if cfg.pearson.enable:
+        htgs["Pearson"] = cfg.pearson.how_to_guide(height, width)
+    if cfg.spearman.enable:
+        htgs["Spearman"] = cfg.spearman.how_to_guide(height, width)
+    if cfg.pearson.enable:
+        htgs["KendallTau"] = cfg.kendall.how_to_guide(height, width)
+
+    return htgs
+
+
+def render_correlation_heatmaps(itmdt: Intermediate, plot_width: int, plot_height: int) -> Tabs:
     """
     Render correlation heatmaps in to tabs
     """
@@ -198,7 +200,7 @@ def render_correlation_heatmaps(
         df["x"] = df["x"].apply(str)
         df["y"] = df["y"].apply(str)
 
-        mapper, color_bar = create_color_mapper(palette)
+        mapper, color_bar = create_color_mapper(RDBU)
         x_range = FactorRange(*axis_range)
         y_range = FactorRange(*reversed(axis_range))
         fig = Figure(
@@ -234,7 +236,7 @@ def render_correlation_heatmaps(
 
 
 def render_correlation_single_heatmaps(
-    itmdt: Intermediate, plot_width: int, plot_height: int, palette: Sequence[str]
+    itmdt: Intermediate, plot_width: int, plot_height: int, cfg: Config
 ) -> Dict[str, Any]:
     """
     Render correlation heatmaps, but with single column
@@ -243,7 +245,7 @@ def render_correlation_single_heatmaps(
     tooltips = [("y", "@y"), ("correlation", "@correlation{1.11}")]
 
     for method, df in itmdt["data"].items():
-        mapper, color_bar = create_color_mapper(palette)
+        mapper, color_bar = create_color_mapper(RDBU)
 
         x_range = FactorRange(*df["x"].unique())
         y_range = FactorRange(*df["y"].unique())
@@ -256,6 +258,7 @@ def render_correlation_single_heatmaps(
             tools="hover",
             toolbar_location=None,
             tooltips=tooltips,
+            title=" ",
         )
 
         tweak_figure(fig)
@@ -275,9 +278,11 @@ def render_correlation_single_heatmaps(
         tabs.append(tab)
 
     return {
+        "tabledata": {},
         "layout": [panel.child for panel in tabs],
         "meta": [panel.title for panel in tabs],
         "container_width": plot_width,
+        "how_to_guide": corr_how_to_guides(cfg, plot_height, plot_width),
     }
 
 
@@ -300,7 +305,7 @@ def create_color_mapper(palette: Sequence[str]) -> Tuple[LinearColorMapper, Colo
 
 ######### Scatter #########
 def render_scatter(
-    itmdt: Intermediate, plot_width: int, plot_height: int, palette: Sequence[str]
+    itmdt: Intermediate, plot_width: int, plot_height: int, cfg: Config
 ) -> Dict[str, Any]:
     """
     Render scatter plot with a regression line and possible most influencial points
@@ -320,13 +325,14 @@ def render_scatter(
         tools=[],
         x_axis_label=xcol,
         y_axis_label=ycol,
+        title=" ",
     )
 
     # Scatter
     scatter = fig.scatter(x=df.columns[0], y=df.columns[1], source=df)
     if maybe_label:
         assert len(maybe_label) == 1
-        mapper = CategoricalColorMapper(factors=["=", "+", "-"], palette=palette)
+        mapper = CategoricalColorMapper(factors=["=", "+", "-"], palette=BRG)
         scatter.glyph.fill_color = {"field": maybe_label[0], "transform": mapper}
         scatter.glyph.line_color = {"field": maybe_label[0], "transform": mapper}
 
@@ -354,10 +360,14 @@ def render_scatter(
         )
 
         fig.add_layout(legend, place="right")
+
     return {
         "layout": [fig],
         "meta": ["Scatter Plot & Regression Line"],
         "container_width": plot_width,
+        "how_to_guide": {
+            "Scatter Plot & Regression Line": cfg.scatter.how_to_guide(plot_height, plot_width)
+        },
     }
 
 
diff --git a/dataprep/eda/create_report/__init__.py b/dataprep/eda/create_report/__init__.py
index 421ed7357..34b32578b 100644
--- a/dataprep/eda/create_report/__init__.py
+++ b/dataprep/eda/create_report/__init__.py
@@ -1,12 +1,13 @@
 """
     This module implements the create_report(df) function.
 """
+from typing import Any, Dict, List, Optional
 
-from typing import Optional
-from pathlib import Path
 import pandas as pd
 from bokeh.resources import INLINE
 from jinja2 import Environment, PackageLoader
+
+from ..configs import Config
 from .formatter import format_report
 from .report import Report
 
@@ -19,6 +20,9 @@
 
 def create_report(
     df: pd.DataFrame,
+    *,
+    config: Optional[Dict[str, Any]] = None,
+    display: Optional[List[str]] = None,
     title: Optional[str] = "DataPrep Report",
     mode: Optional[str] = "basic",
     progress: bool = True,
@@ -30,6 +34,13 @@ def create_report(
     ----------
     df
         The DataFrame for which data are calculated.
+    config
+        A dictionary for configuring the visualizations
+        E.g. config={"hist.bins": 20}
+    display
+        The list that contains the names of plots user wants to display,
+        E.g. display =  ["bar", "hist"]
+        Without user's specifications, the default is "auto"
     title: Optional[str], default "DataPrep Report"
         The title of the report, which will be shown on the navigation bar.
     mode: Optional[str], default "basic"
@@ -48,10 +59,11 @@ def create_report(
     >>> report.save('My Fantastic Report') # save report to local disk
     >>> report.show_browser() # show report in the browser
     """
+    cfg = Config.from_dict(display, config)
     context = {
         "resources": INLINE.render(),
         "title": title,
-        "components": format_report(df, mode, progress),
+        "components": format_report(df, cfg, mode, progress),
     }
     template_base = ENV_LOADER.get_template("base.html")
     report = template_base.render(context=context)
diff --git a/dataprep/eda/create_report/formatter.py b/dataprep/eda/create_report/formatter.py
index 600c4dd4d..5419bd5b5 100644
--- a/dataprep/eda/create_report/formatter.py
+++ b/dataprep/eda/create_report/formatter.py
@@ -11,13 +11,14 @@
 from bokeh.models import Title
 from bokeh.plotting import Figure
 
+from ..configs import Config
 from ..correlation import render_correlation
 from ..correlation.compute.nullivariate import correlation_nxn
 from ..data_array import DataArray
 from ..distribution import render
 from ..distribution.compute.common import _calc_line_dt
 from ..distribution.compute.overview import calc_stats
-from ..distribution.compute.univariate import cont_comps, nom_comps, calc_stats_dt
+from ..distribution.compute.univariate import calc_stats_dt, cont_comps, nom_comps
 from ..distribution.render import format_cat_stats, format_num_stats, format_ov_stats, stats_viz_dt
 from ..dtypes import (
     CATEGORICAL_DTYPES,
@@ -36,7 +37,10 @@
 
 
 def format_report(
-    df: Union[pd.DataFrame, dd.DataFrame], mode: Optional[str], progress: bool = True
+    df: Union[pd.DataFrame, dd.DataFrame],
+    cfg: Config,
+    mode: Optional[str],
+    progress: bool = True,
 ) -> Dict[str, Any]:
     """
     Format the data and figures needed by report
@@ -45,6 +49,8 @@ def format_report(
     ----------
     df
         The DataFrame for which data are calculated.
+    cfg
+        The config instance
     mode
         This controls what type of report to be generated.
         Currently only the 'basic' is fully implemented.
@@ -57,12 +63,12 @@ def format_report(
         A dictionary in which formatted data will be stored.
         This variable acts like an API in passing data to the template engine.
     """
-    # pylint: disable=too-many-locals,too-many-statements
+
     with ProgressBar(minimum=1, disable=not progress):
         df = to_dask(df)
         df = string_dtype_to_object(df)
         if mode == "basic":
-            comps = format_basic(df)
+            comps = format_basic(df, cfg)
         # elif mode == "full":
         #     comps = format_full(df)
         # elif mode == "minimal":
@@ -72,8 +78,7 @@ def format_report(
     return comps
 
 
-def format_basic(df: dd.DataFrame) -> Dict[str, Any]:
-    # pylint: disable=too-many-statements
+def format_basic(df: dd.DataFrame, cfg: Config) -> Dict[str, Any]:
     """
     Format basic version.
 
@@ -81,16 +86,19 @@ def format_basic(df: dd.DataFrame) -> Dict[str, Any]:
     ----------
     df
         The DataFrame for which data are calculated.
-
+    cfg
+        The config dict user passed in. E.g. config =  {"hist.bins": 20}
+        Without user's specifications, the default is "auto"
     Returns
     -------
     Dict[str, Any]
         A dictionary in which formatted data is stored.
         This variable acts like an API in passing data to the template engine.
     """
-    # pylint: disable=too-many-locals
+    # pylint: disable=too-many-locals,too-many-statements
     # aggregate all computations
-    data, completions = basic_computations(df)
+    setattr(getattr(cfg, "plot"), "report", True)
+    data, completions = basic_computations(df, cfg)
 
     with catch_warnings():
         filterwarnings(
@@ -127,7 +135,7 @@ def format_basic(df: dd.DataFrame) -> Dict[str, Any]:
                 visual_type="datetime_column",
             )
             stats = stats_viz_dt(data[col]["stats"])
-        rndrd = render(itmdt, plot_height_lrg=250, plot_width_lrg=280)["layout"]
+        rndrd = render(itmdt, cfg)["layout"]
         figs: List[Figure] = []
         for tab in rndrd:
             try:
@@ -146,7 +154,7 @@ def format_basic(df: dd.DataFrame) -> Dict[str, Any]:
         # interactions
         res["has_interaction"] = True
         itmdt = Intermediate(data=data["scat"], visual_type="correlation_crossfilter")
-        rndrd = render_correlation(itmdt)
+        rndrd = render_correlation(itmdt, cfg)
         rndrd.sizing_mode = "stretch_width"
         res["interactions"] = components(rndrd)
 
@@ -167,7 +175,7 @@ def format_basic(df: dd.DataFrame) -> Dict[str, Any]:
             axis_range=list(data["num_cols"]),
             visual_type="correlation_heatmaps",
         )
-        rndrd = render_correlation(itmdt)
+        rndrd = render_correlation(itmdt, cfg)
         figs.clear()
         for tab in rndrd.tabs:
             fig = tab.child
@@ -182,12 +190,14 @@ def format_basic(df: dd.DataFrame) -> Dict[str, Any]:
     res["has_missing"] = True
     itmdt = completions["miss"](data["miss"])
 
-    rndrd = render_missing(itmdt)
+    rndrd = render_missing(itmdt, cfg)
     figs.clear()
     for fig in rndrd["layout"]:
         fig.sizing_mode = "stretch_width"
         fig.title = Title(
-            text=rndrd["meta"][rndrd["layout"].index(fig)], align="center", text_font_size="20px"
+            text=rndrd["meta"][rndrd["layout"].index(fig)],
+            align="center",
+            text_font_size="20px",
         )
         figs.append(fig)
     res["missing"] = components(figs)
@@ -195,13 +205,16 @@ def format_basic(df: dd.DataFrame) -> Dict[str, Any]:
     return res
 
 
-def basic_computations(df: dd.DataFrame) -> Tuple[Dict[str, Any], Dict[str, Any]]:
+def basic_computations(df: dd.DataFrame, cfg: Config) -> Tuple[Dict[str, Any], Dict[str, Any]]:
     """Computations for the basic version.
 
     Parameters
     ----------
     df
         The DataFrame for which data are calculated.
+    cfg
+        The config dict user passed in. E.g. config =  {"hist.bins": 20}
+        Without user's specifications, the default is "auto"
     """
     data: Dict[str, Any] = {}
     df = DataArray(df)
@@ -213,32 +226,30 @@ def basic_computations(df: dd.DataFrame) -> Tuple[Dict[str, Any], Dict[str, Any]
     # variables
     for col in df.columns:
         if is_dtype(detect_dtype(df.frame[col]), Continuous()):
-            data[col] = cont_comps(df.frame[col], 20)
+            data[col] = cont_comps(df.frame[col], cfg)
         elif is_dtype(detect_dtype(df.frame[col]), Nominal()):
             # cast the column as string type if it contains a mutable type
             try:
                 first_rows[col].apply(hash)
             except TypeError:
                 df.frame[col] = df.frame[col].astype(str)
-            data[col] = nom_comps(
-                df.frame[col], first_rows[col], 10, True, 10, 20, True, False, False
-            )
+            data[col] = nom_comps(df.frame[col], first_rows[col], cfg)
         elif is_dtype(detect_dtype(df.frame[col]), DateTime()):
             data[col] = {}
             data[col]["stats"] = calc_stats_dt(df.frame[col])
             data[col]["line"] = dask.delayed(_calc_line_dt)(df.frame[[col]], "auto")
 
     # overview
-    data["ov"] = calc_stats(df.frame, None)
+    data["ov"] = calc_stats(df.frame, cfg, None)
     # interactions
     data["scat"] = df_num.frame.map_partitions(
         lambda x: x.sample(min(1000, x.shape[0])), meta=df_num.frame
     )
     # correlations
-    data.update(zip(("cordx", "cordy", "corrs"), correlation_nxn(df_num)))
+    data.update(zip(("cordx", "cordy", "corrs"), correlation_nxn(df_num, cfg)))
     # missing values
     (delayed, completion,) = compute_missing_nullivariate(  # pylint: disable=unexpected-keyword-arg
-        df, 30, _staged=True
+        df, cfg, _staged=True
     )
     data["miss"] = delayed
     completions = {"miss": completion}
diff --git a/dataprep/eda/create_report/report.py b/dataprep/eda/create_report/report.py
index 67ff9ce8a..fdef3c92d 100644
--- a/dataprep/eda/create_report/report.py
+++ b/dataprep/eda/create_report/report.py
@@ -3,11 +3,11 @@
 """
 import sys
 import webbrowser
-from typing import Optional
 from pathlib import Path
 from tempfile import NamedTemporaryFile
-from ...utils import is_notebook
+from typing import Optional
 
+from ...utils import is_notebook
 
 CELL_HEIGHT_OVERRIDE = """<style>
                             div.output_scroll {
diff --git a/dataprep/eda/distribution/__init__.py b/dataprep/eda/distribution/__init__.py
index b5a52e4d7..4b6038179 100644
--- a/dataprep/eda/distribution/__init__.py
+++ b/dataprep/eda/distribution/__init__.py
@@ -2,11 +2,12 @@
 This module implements the plot(df) function.
 """
 
-from typing import Optional, Tuple, Union
+from typing import Any, Dict, List, Optional, Union
 
 import dask.dataframe as dd
 import pandas as pd
 
+from ..configs import Config
 from ..container import Container
 from ..dtypes import DTypeDef
 from ..progress_bar import ProgressBar
@@ -22,20 +23,8 @@ def plot(
     y: Optional[str] = None,
     z: Optional[str] = None,
     *,
-    bins: int = 50,
-    ngroups: int = 20,
-    largest: bool = True,
-    nsubgroups: int = 5,
-    timeunit: str = "auto",
-    agg: str = "mean",
-    sample_size: int = 1000,
-    top_words: int = 30,
-    stopword: bool = True,
-    lemmatize: bool = False,
-    stem: bool = False,
-    value_range: Optional[Tuple[float, float]] = None,
-    yscale: str = "linear",
-    tile_size: Optional[float] = None,
+    config: Optional[Dict[str, Any]] = None,
+    display: Optional[List[str]] = None,
     dtype: Optional[DTypeDef] = None,
     progress: bool = True,
 ) -> Container:
@@ -76,63 +65,24 @@ def plot(
     Parameters
     ----------
     df
-        Dataframe from which plots are to be generated
+        DataFrame from which visualizations are generated
     x: Optional[str], default None
         A valid column name from the dataframe
     y: Optional[str], default None
         A valid column name from the dataframe
     z: Optional[str], default None
         A valid column name from the dataframe
-    bins: int, default 10
-        For a histogram or box plot with numerical x axis, it defines
-        the number of equal-width bins to use when grouping.
-    ngroups: int, default 10
-        When grouping over a categorical column, it defines the
-        number of groups to show in the plot. Ie, the number of
-        bars to show in a bar chart.
-    largest: bool, default True
-        If true, when grouping over a categorical column, the groups
-        with the largest count will be output. If false, the groups
-        with the smallest count will be output.
-    nsubgroups: int, default 5
-        If x and y are categorical columns, ngroups refers to
-        how many groups to show from column x, and nsubgroups refers to
-        how many subgroups to show from column y in each group in column x.
-    timeunit: str, default "auto"
-        Defines the time unit to group values over for a datetime column.
-        It can be "year", "quarter", "month", "week", "day", "hour",
-        "minute", or "second". With default value "auto", it will use the
-        time unit such that the resulting number of groups is closest to 15.
-    agg: str, default "mean"
-        Specify the aggregate to use when aggregating over a numerical
-        column
-    sample_size: int, default 1000
-        Sample size for the scatter plot
-    top_words: int, default 30
-        Specify the amount of words to show in the wordcloud and
-        word frequency bar chart
-    stopword: bool, default True
-        Eliminate the stopwords in the text data for plotting wordcloud and
-        word frequency bar chart
-    lemmatize: bool, default False
-        Lemmatize the words in the text data for plotting wordcloud and
-        word frequency bar chart
-    stem: bool, default False
-        Apply Potter Stem on the text data for plotting wordcloud and
-        word frequency bar chart
-    value_range: Optional[Tuple[float, float]], default None
-        The lower and upper bounds on the range of a numerical column.
-        Applies when column x is specified and column y is unspecified.
-    yscale
-        The scale to show on the y axis. Can be "linear" or "log".
-    tile_size: Optional[float], default None
-        Size of the tile for the hexbin plot. Measured from the middle
-        of a hexagon to its left or right corner.
+    config
+        A dictionary for configuring the visualizations
+        E.g. config={"hist.bins": 20}
+    display
+        A list containing the names of the visualizations to display
+        E.g. display=["Histogram"]
     dtype: str or DType or dict of str or dict of DType, default None
         Specify Data Types for designated column or all columns.
         E.g.  dtype = {"a": Continuous, "b": "Nominal"} or
         dtype = {"a": Continuous(), "b": "nominal"}
-        or dtype = Continuous() or dtype = "Continuous" or dtype = Continuous()
+        or dtype = Continuous() or dtype = "Continuous" or dtype = Continuous().
     progress
         Enable the progress bar.
 
@@ -142,30 +92,14 @@ def plot(
     >>> from dataprep.eda import *
     >>> iris = pd.read_csv('https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv')
     >>> plot(iris)
-    >>> plot(iris, "petal_length", bins=20, value_range=(1,5))
+    >>> plot(iris, "petal_length")
     >>> plot(iris, "petal_width", "species")
     """
-    # pylint: disable=too-many-locals,line-too-long
+    cfg = Config.from_dict(display, config)
 
     with ProgressBar(minimum=1, disable=not progress):
-        intermediate = compute(
-            df,
-            x=x,
-            y=y,
-            z=z,
-            bins=bins,
-            ngroups=ngroups,
-            largest=largest,
-            nsubgroups=nsubgroups,
-            timeunit=timeunit.lower(),
-            agg=agg,
-            sample_size=sample_size,
-            top_words=top_words,
-            stopword=stopword,
-            lemmatize=lemmatize,
-            stem=stem,
-            value_range=value_range,
-            dtype=dtype,
-        )
-    to_render = render(intermediate, yscale=yscale, tile_size=tile_size)
-    return Container(to_render, intermediate.visual_type)
+        itmdt = compute(df, x, y, z, cfg=cfg, dtype=dtype)
+
+    to_render = render(itmdt, cfg)
+
+    return Container(to_render, itmdt.visual_type, cfg)
diff --git a/dataprep/eda/distribution/compute/__init__.py b/dataprep/eda/distribution/compute/__init__.py
index e95cd8bea..af36646bc 100644
--- a/dataprep/eda/distribution/compute/__init__.py
+++ b/dataprep/eda/distribution/compute/__init__.py
@@ -1,10 +1,14 @@
-"""Computations for plot(df, ...)."""
+"""
+Computations for plot(df, ...)
+"""
 
-from typing import Optional, Tuple, Union, cast
+
+from typing import Optional, Union, List, Dict, Any
 
 import dask.dataframe as dd
 import pandas as pd
 
+from ...configs import Config
 from ...dtypes import DTypeDef, string_dtype_to_object
 from ...intermediate import Intermediate
 from ...utils import to_dask
@@ -22,118 +26,61 @@ def compute(
     y: Optional[str] = None,
     z: Optional[str] = None,
     *,
-    bins: int = 10,
-    ngroups: int = 10,
-    largest: bool = True,
-    nsubgroups: int = 5,
-    timeunit: str = "auto",
-    agg: str = "mean",
-    sample_size: int = 1000,
-    top_words: int = 30,
-    stopword: bool = True,
-    lemmatize: bool = False,
-    stem: bool = False,
-    value_range: Optional[Tuple[float, float]] = None,
+    cfg: Union[Config, Dict[str, Any], None] = None,
+    display: Optional[List[str]] = None,
     dtype: Optional[DTypeDef] = None,
 ) -> Intermediate:
-    """All in one compute function.
+    """
+    All in one compute function.
 
     Parameters
     ----------
     df
-        Dataframe from which plots are to be generated
+        DataFrame from which visualizations are generated
+    cfg: Union[Config, Dict[str, Any], None], default None
+        When a user call plot(), the created Config object will be passed to compute().
+        When a user call compute() directly, if he/she wants to customize the output,
+        cfg is a dictionary for configuring. If not, cfg is None and
+        default values will be used for parameters.
+    display: Optional[List[str]], default None
+        A list containing the names of the visualizations to display. Only exist when
+        a user call compute() directly and want to customize the output
     x: Optional[str], default None
         A valid column name from the dataframe
     y: Optional[str], default None
         A valid column name from the dataframe
     z: Optional[str], default None
         A valid column name from the dataframe
-    bins: int, default 10
-        For a histogram or box plot with numerical x axis, it defines
-        the number of equal-width bins to use when grouping.
-    ngroups: int, default 10
-        When grouping over a categorical column, it defines the
-        number of groups to show in the plot. Ie, the number of
-        bars to show in a bar chart.
-    largest: bool, default True
-        If true, when grouping over a categorical column, the groups
-        with the largest count will be output. If false, the groups
-        with the smallest count will be output.
-    nsubgroups: int, default 5
-        If x and y are categorical columns, ngroups refers to
-        how many groups to show from column x, and nsubgroups refers to
-        how many subgroups to show from column y in each group in column x.
-    timeunit: str, default "auto"
-        Defines the time unit to group values over for a datetime column.
-        It can be "year", "quarter", "month", "week", "day", "hour",
-        "minute", "second". With default value "auto", it will use the
-        time unit such that the resulting number of groups is closest to 15.
-    agg: str, default "mean"
-        Specify the aggregate to use when aggregating over a numeric column
-    sample_size: int, default 1000
-        Sample size for the scatter plot
-    top_words: int, default 30
-        Specify the amount of words to show in the wordcloud and
-        word frequency bar chart
-    stopword: bool, default True
-        Eliminate the stopwords in the text data for plotting wordcloud and
-        word frequency bar chart
-    lemmatize: bool, default False
-        Lemmatize the words in the text data for plotting wordcloud and
-        word frequency bar chart
-    stem: bool, default False
-        Apply Potter Stem on the text data for plotting wordcloud and
-        word frequency bar chart
-    value_range: Optional[Tuple[float, float]], default None
-        The lower and upper bounds on the range of a numerical column.
-        Applies when column x is specified and column y is unspecified.
     dtype: str or DType or dict of str or dict of DType, default None
         Specify Data Types for designated column or all columns.
         E.g.  dtype = {"a": Continuous, "b": "Nominal"} or
         dtype = {"a": Continuous(), "b": "nominal"}
         or dtype = Continuous() or dtype = "Continuous" or dtype = Continuous()
-    """  # pylint: disable=too-many-locals
+    """
+    # pylint: disable=too-many-arguments
+
     df = to_dask(df)
     df.columns = df.columns.astype(str)
     df = string_dtype_to_object(df)
 
+    if isinstance(cfg, dict):
+        cfg = Config.from_dict(display, cfg)
+
+    elif not cfg:
+        cfg = Config()
+
     if not any((x, y, z)):
-        return compute_overview(df, bins, ngroups, largest, timeunit, dtype)
+        return compute_overview(df, cfg, dtype)
 
     if sum(v is None for v in (x, y, z)) == 2:
-        col: str = cast(str, x or y or z)
-        return compute_univariate(
-            df,
-            col,
-            bins,
-            ngroups,
-            largest,
-            timeunit,
-            top_words,
-            stopword,
-            lemmatize,
-            stem,
-            value_range,
-            dtype,
-        )
+        x = x or y or z
+        return compute_univariate(df, x, cfg, dtype)
 
     if sum(v is None for v in (x, y, z)) == 1:
         x, y = (v for v in (x, y, z) if v is not None)
-        return compute_bivariate(
-            df,
-            x,
-            y,
-            bins,
-            ngroups,
-            largest,
-            nsubgroups,
-            timeunit,
-            agg,
-            sample_size,
-            dtype,
-        )
+        return compute_bivariate(df, x, y, cfg, dtype)
 
     if x is not None and y is not None and z is not None:
-        return compute_trivariate(df, x, y, z, ngroups, largest, timeunit, agg, dtype)
+        return compute_trivariate(df, x, y, z, cfg, dtype)
 
     raise ValueError("not possible")
diff --git a/dataprep/eda/distribution/compute/bivariate.py b/dataprep/eda/distribution/compute/bivariate.py
index 02168521b..09de5c91b 100644
--- a/dataprep/eda/distribution/compute/bivariate.py
+++ b/dataprep/eda/distribution/compute/bivariate.py
@@ -7,22 +7,16 @@
 import pandas as pd
 
 from ....errors import UnreachableError
+from ...configs import Config
+from ...dtypes import Continuous, DateTime, DTypeDef, Nominal, detect_dtype, is_dtype
 from ...intermediate import Intermediate
-from ...dtypes import (
-    Continuous,
-    DateTime,
-    DTypeDef,
-    Nominal,
-    detect_dtype,
-    is_dtype,
-)
 from .common import (
     DTMAP,
-    _get_timeunit,
-    _calc_line_dt,
-    _calc_groups,
     _calc_box_otlrs,
     _calc_box_stats,
+    _calc_groups,
+    _calc_line_dt,
+    _get_timeunit,
 )
 
 
@@ -30,59 +24,30 @@ def compute_bivariate(
     df: dd.DataFrame,
     x: str,
     y: str,
-    bins: int,
-    ngroups: int,
-    largest: bool,
-    nsubgroups: int,
-    timeunit: str,
-    agg: str,
-    sample_size: int,
-    dtype: Optional[DTypeDef] = None,
+    cfg: Config,
+    dtype: Optional[DTypeDef],
 ) -> Intermediate:
     """Compute functions for plot(df, x, y).
 
     Parameters
     ----------
     df
-        Dataframe from which plots are to be generated
+        DataFrame from which visualizations are generated
     x
-        A valid column name from the dataframe
+        A column name from the DataFrame
     y
-        A valid column name from the dataframe
-    bins
-        For a histogram or box plot with numerical x axis, it defines
-        the number of equal-width bins to use when grouping.
-    ngroups
-        When grouping over a categorical column, it defines the
-        number of groups to show in the plot. Ie, the number of
-        bars to show in a bar chart.
-    largest
-        If true, when grouping over a categorical column, the groups
-        with the largest count will be output. If false, the groups
-        with the smallest count will be output.
-    nsubgroups
-        If x and y are categorical columns, ngroups refers to
-        how many groups to show from column x, and nsubgroups refers to
-        how many subgroups to show from column y in each group in column x.
-    timeunit
-        Defines the time unit to group values over for a datetime column.
-        It can be "year", "quarter", "month", "week", "day", "hour",
-        "minute", "second". With default value "auto", it will use the
-        time unit such that the resulting number of groups is closest to 15.
-    agg
-        Specify the aggregate to use when aggregating over a numeric column
-    sample_size
-        Sample size for the scatter plot
+        A column name from the DataFrame
+    cfg
+        Config instance
     dtype: str or DType or dict of str or dict of DType, default None
         Specify Data Types for designated column or all columns.
         E.g.  dtype = {"a": Continuous, "b": "Nominal"} or
         dtype = {"a": Continuous(), "b": "nominal"}
         or dtype = Continuous() or dtype = "Continuous" or dtype = Continuous()
     """
-    # pylint: disable=too-many-arguments,too-many-locals
+    # pylint: disable=too-many-branches,too-many-statements
+    xtype, ytype = detect_dtype(df[x], dtype), detect_dtype(df[y], dtype)
 
-    xtype = detect_dtype(df[x], dtype)
-    ytype = detect_dtype(df[y], dtype)
     if (
         is_dtype(xtype, Nominal())
         and is_dtype(ytype, Continuous())
@@ -91,15 +56,19 @@ def compute_bivariate(
     ):
         x, y = (x, y) if is_dtype(xtype, Nominal()) else (y, x)
         df = df[[x, y]]
-        first_rows = df.head()
         try:
-            first_rows[x].apply(hash)
+            df.head()[x].apply(hash)
         except TypeError:
             df[x] = df[x].astype(str)
 
-        (comps,) = dask.compute(nom_cont_comps(df.dropna(), bins, ngroups, largest))
+        (comps,) = dask.compute(_nom_cont_comps(df.dropna(), cfg))
 
-        return Intermediate(x=x, y=y, data=comps, ngroups=ngroups, visual_type="cat_and_num_cols")
+        return Intermediate(
+            x=x,
+            y=y,
+            data=comps,
+            visual_type="cat_and_num_cols",
+        )
     elif (
         is_dtype(xtype, DateTime())
         and is_dtype(ytype, Continuous())
@@ -110,15 +79,29 @@ def compute_bivariate(
         df = df[[x, y]].dropna()
         dtnum: List[Any] = []
         # line chart
-        dtnum.append(dask.delayed(_calc_line_dt)(df, timeunit, agg))
+        if cfg.line.enable:
+            dtnum.append(dask.delayed(_calc_line_dt)(df, cfg.line.unit, cfg.line.agg))
         # box plot
-        dtnum.append(dask.delayed(calc_box_dt)(df, timeunit))
+        if cfg.box.enable:
+            dtnum.append(dask.delayed(_calc_box_dt)(df, cfg.box.unit))
+
         dtnum = dask.compute(*dtnum)
+
+        if len(dtnum) == 2:
+            linedata = dtnum[0]
+            boxdata = dtnum[1]
+        elif cfg.line.enable:
+            linedata = dtnum[0]
+            boxdata = []
+        else:
+            boxdata = dtnum[0]
+            linedata = []
+
         return Intermediate(
             x=x,
             y=y,
-            linedata=dtnum[0],
-            boxdata=dtnum[1],
+            linedata=linedata,
+            boxdata=boxdata,
             visual_type="dt_and_num_cols",
         )
     elif (
@@ -131,51 +114,80 @@ def compute_bivariate(
         df = df[[x, y]].dropna()
         df[y] = df[y].apply(str, meta=(y, str))
         dtcat: List[Any] = []
-        # line chart
-        dtcat.append(dask.delayed(_calc_line_dt)(df, timeunit, ngroups=ngroups, largest=largest))
-        # stacked bar chart
-        dtcat.append(dask.delayed(calc_stacked_dt)(df, timeunit, ngroups, largest))
+        if cfg.line.enable:
+            # line chart
+            dtcat.append(
+                dask.delayed(_calc_line_dt)(
+                    df,
+                    cfg.line.unit,
+                    ngroups=cfg.line.ngroups,
+                    largest=cfg.line.sort_descending,
+                )
+            )
+        if cfg.stacked.enable:
+            # stacked bar chart
+            dtcat.append(
+                dask.delayed(_calc_stacked_dt)(
+                    df,
+                    cfg.stacked.unit,
+                    cfg.stacked.ngroups,
+                    cfg.stacked.sort_descending,
+                )
+            )
         dtcat = dask.compute(*dtcat)
+
+        if len(dtcat) == 2:
+            linedata = dtcat[0]
+            stackdata = dtcat[1]
+        elif cfg.line.enable:
+            linedata = dtcat[0]
+            stackdata = []
+        else:
+            stackdata = dtcat[0]
+            linedata = []
+
         return Intermediate(
             x=x,
             y=y,
-            linedata=dtcat[0],
-            stackdata=dtcat[1],
+            linedata=linedata,
+            stackdata=stackdata,
             visual_type="dt_and_cat_cols",
         )
     elif is_dtype(xtype, Nominal()) and is_dtype(ytype, Nominal()):
         df = df[[x, y]]
-        first_rows = df.head()
+        head = df.head()
         try:
-            first_rows[x].apply(hash)
+            head[x].apply(hash)
         except TypeError:
             df[x] = df[x].astype(str)
         try:
-            first_rows[y].apply(hash)
+            head[y].apply(hash)
         except TypeError:
             df[y] = df[y].astype(str)
 
         (comps,) = dask.compute(df.dropna().groupby([x, y]).size())
-
         return Intermediate(
             x=x,
             y=y,
             data=comps,
-            ngroups=ngroups,
-            nsubgroups=nsubgroups,
             visual_type="two_cat_cols",
         )
     elif is_dtype(xtype, Continuous()) and is_dtype(ytype, Continuous()):
-        # one partition required for apply(pd.cut) in calc_box_num
+        # one partition required for apply(pd.cut) in _calc_box_cont
         df = df[[x, y]].dropna().repartition(npartitions=1)
 
         data: Dict[str, Any] = {}
-        # scatter plot data
-        data["scat"] = df.map_partitions(lambda x: x.sample(min(100, x.shape[0])), meta=df)
-        # hexbin plot data
-        data["hex"] = df
-        # box plot
-        data["box"] = calc_box_num(df, bins)
+        if cfg.scatter.enable:
+            # scatter plot data
+            data["scat"] = df.map_partitions(
+                lambda x: x.sample(min(cfg.scatter.sample_size, x.shape[0])), meta=df
+            )
+        if cfg.hexbin.enable:
+            # hexbin plot data
+            data["hex"] = df
+        if cfg.box.enable:
+            # box plot
+            data["box"] = _calc_box_cont(df, cfg)
 
         (data,) = dask.compute(data)
 
@@ -183,83 +195,78 @@ def compute_bivariate(
             x=x,
             y=y,
             data=data,
-            spl_sz=sample_size,
             visual_type="two_num_cols",
         )
     else:
         raise UnreachableError
 
 
-def nom_cont_comps(df: dd.DataFrame, bins: int, ngroups: int, largest: bool) -> Dict[str, Any]:
+def _nom_cont_comps(df: dd.DataFrame, cfg: Config) -> Dict[str, Any]:
     """
     Computations for a nominal and continuous column
-
-    Parameters
-    ----------
-    df
-        Dask dataframe with one categorical and one numerical column
-    bins
-        Number of bins to use in the histogram
-    ngroups
-        Number of groups to show from the categorical column
-    largest
-        Select the largest or smallest groups
     """
     data: Dict[str, Any] = {}
-
-    x, y = df.columns[0], df.columns[1]
+    x, y = df.columns
 
     # filter the dataframe to consist of ngroup groups
     # https://stackoverflow.com/questions/46927174/filtering-grouped-df-in-dask
     cnts = df[x].value_counts(sort=False)
     data["ttl_grps"] = cnts.shape[0]
-    thresh = cnts.nlargest(ngroups).min() if largest else cnts.nsmallest(ngroups).max()
-    df = df[df[x].map(cnts) >= thresh] if largest else df[df[x].map(cnts) <= thresh]
 
-    # group the data to compute a box plot and histogram for each group
-    grps = df.groupby(x)[y]
-    data["box"] = grps.apply(box_comps, meta="object")
-
-    minv, maxv = df[y].min(), df[y].max()
-    # TODO when are minv and maxv computed? This may not be optimal if
-    # minv and maxv are computed ngroups times for each histogram
-    data["hist"] = grps.apply(hist, bins, minv, maxv, meta="object")
+    if cfg.box.enable:
+        if cfg.box.sort_descending:
+            thresh = cnts.nlargest(cfg.box.ngroups).min()
+            df_box = df[df[x].map(cnts) >= thresh]
+        else:
+            thresh = cnts.nsmallest(cfg.box.ngroups).max()
+            df_box = df[df[x].map(cnts) <= thresh]
+        grps_box = df_box.groupby(x)[y]
+        data["box"] = grps_box.apply(_box_comps, meta=object)
+        if (
+            cfg.line.enable
+            and cfg.box.sort_descending == cfg.line.sort_descending
+            and cfg.box.ngroups == cfg.line.ngroups
+        ):
+            data["hist"] = grps_box.apply(
+                _hist, cfg.line.bins, df_box[y].min(), df_box[y].max(), meta=object
+            )
+    if cfg.line.enable and (
+        cfg.box.sort_descending != cfg.line.sort_descending or cfg.box.ngroups != cfg.line.ngroups
+    ):
+        if cfg.line.sort_descending:
+            thresh = cnts.nlargest(cfg.line.ngroups).min()
+            df_hist = df[df[x].map(cnts) >= thresh]
+        else:
+            thresh = cnts.nsmallest(cfg.line.ngroups).max()
+            df_hist = df[df[x].map(cnts) <= thresh]
+        grps_hist = df_hist.groupby(x)[y]
+        data["hist"] = grps_hist.apply(
+            _hist, cfg.line.bins, df_hist[y].min(), df_hist[y].max(), meta=object
+        )
 
     return data
 
 
-def calc_box_num(df: dd.DataFrame, bins: int) -> dd.Series:
+def _calc_box_cont(df: dd.DataFrame, cfg: Config) -> dd.Series:
     """
-    Box plot for a binned numerical variable
-
-    Parameters
-    ----------
-    df
-        dask dataframe
-    bins
-        number of bins to compute a box plot
+    Box plot for a binned continuous variable
     """
-    x, y = df.columns[0], df.columns[1]
+    x, y = df.columns
     # group the data into intervals
     # https://stackoverflow.com/questions/42442043/how-to-use-pandas-cut-or-equivalent-in-dask-efficiently
-    df["grp"] = df[x].map_partitions(pd.cut, bins=bins, include_lowest=True)
+    df["grp"] = df[x].map_partitions(pd.cut, bins=cfg.box.bins, include_lowest=True)
     # TODO is this calculating the box plot stats for each group in parallel?
     # https://examples.dask.org/dataframes/02-groupby.html#Groupby-Apply
     # https://github.com/dask/dask/issues/4239
     # https://github.com/dask/dask/issues/5124
-    srs = df.groupby("grp")[y].apply(box_comps, meta="object")
+    srs = df.groupby("grp")[y].apply(_box_comps, meta=object)
 
     return srs
 
 
-def box_comps(srs: pd.Series) -> Dict[str, Union[float, np.array]]:
+def _box_comps(srs: pd.Series) -> Dict[str, Union[float, np.array]]:
     """
     Box plot computations
-
-    Parameters
-    ----------
-    srs
-        pandas series
     """
     data: Dict[str, Any] = {}
 
@@ -277,26 +284,15 @@ def box_comps(srs: pd.Series) -> Dict[str, Union[float, np.array]]:
     return data
 
 
-def hist(srs: pd.Series, bins: int, minv: float, maxv: float) -> Any:
+def _hist(srs: pd.Series, bins: int, minv: float, maxv: float) -> Any:
     """
     Compute a histogram on a given series
-
-    Parameters
-    ----------
-    srs
-        pandas Series of values for the histogram
-    bins
-        number of bins
-    minv
-        lowest bin endpoint
-    maxv
-        highest bin endpoint
     """
 
-    return np.histogram(srs, bins=bins, range=[minv, maxv])
+    return np.histogram(srs, bins=bins, range=(minv, maxv))
 
 
-def calc_box_dt(df: dd.DataFrame, unit: str) -> Tuple[pd.DataFrame, List[str], List[float], str]:
+def _calc_box_dt(df: dd.DataFrame, unit: str) -> Tuple[pd.DataFrame, List[str], List[float], str]:
     """
     Calculate a box plot with date on the x axis.
     Parameters
@@ -334,7 +330,7 @@ def calc_box_dt(df: dd.DataFrame, unit: str) -> Tuple[pd.DataFrame, List[str], L
     return df, outx, outy, DTMAP[unit][3]
 
 
-def calc_stacked_dt(
+def _calc_stacked_dt(
     df: dd.DataFrame,
     unit: str,
     ngroups: int,
diff --git a/dataprep/eda/distribution/compute/overview.py b/dataprep/eda/distribution/compute/overview.py
index 6482da1dc..e341de372 100644
--- a/dataprep/eda/distribution/compute/overview.py
+++ b/dataprep/eda/distribution/compute/overview.py
@@ -1,4 +1,4 @@
-"""Computations for plot(df) function."""
+"""Computations for plot(df)"""
 
 from itertools import combinations
 from typing import Any, Dict, List, Optional, Tuple
@@ -10,7 +10,7 @@
 import pandas as pd
 from dask.array.stats import chisquare
 
-from ....errors import UnreachableError
+from ...configs import Config
 from ...dtypes import (
     Continuous,
     DateTime,
@@ -25,271 +25,178 @@
 from .common import _calc_line_dt, ks_2samp, normaltest, skewtest
 
 
-def compute_overview(
-    df: dd.DataFrame,
-    bins: int,
-    ngroups: int,
-    largest: bool,
-    timeunit: str,
-    dtype: Optional[DTypeDef] = None,
-) -> Intermediate:
-    # pylint: disable=too-many-arguments,too-many-locals,too-many-branches
-
+def compute_overview(df: dd.DataFrame, cfg: Config, dtype: Optional[DTypeDef]) -> Intermediate:
     """
     Compute functions for plot(df)
+
     Parameters
     ----------
     df
-        Dataframe from which plots are to be generated
-    bins
-        For a histogram or box plot with numerical x axis, it defines
-        the number of equal-width bins to use when grouping.
-    ngroups
-        When grouping over a categorical column, it defines the
-        number of groups to show in the plot. Ie, the number of
-        bars to show in a bar chart.
-    largest
-        If true, when grouping over a categorical column, the groups
-        with the largest count will be output. If false, the groups
-        with the smallest count will be output.
-    timeunit
-        Defines the time unit to group values over for a datetime column.
-        It can be "year", "quarter", "month", "week", "day", "hour",
-        "minute", "second". With default value "auto", it will use the
-        time unit such that the resulting number of groups is closest to 15.
+        DataFrame from which visualizations are generated
+    cfg
+        Config instance
     dtype: str or DType or dict of str or dict of DType, default None
         Specify Data Types for designated column or all columns.
         E.g.  dtype = {"a": Continuous, "b": "Nominal"} or
         dtype = {"a": Continuous(), "b": "nominal"}
         or dtype = Continuous() or dtype = "Continuous" or dtype = Continuous()
     """
-    # extract the first rows for checking if a column contains a mutable type
-    first_rows: pd.DataFrame = df.head()  # dd.DataFrame.head triggers a (small) data read
+    # pylint: disable=too-many-branches
 
-    datas: List[Any] = []
-    col_names_dtypes: List[Tuple[str, DType]] = []
+    if cfg.bar.enable or cfg.insight.enable:
+        # extract the first rows to check if a column contains a mutable type
+        head: pd.DataFrame = df.head()  # head triggers a (small) data read
 
+    data: List[Tuple[str, DType, Any]] = []
     for col in df.columns:
-        srs = df[col]
-        col_dtype = detect_dtype(srs, dtype)
-        if is_dtype(col_dtype, Nominal()):
-            ## if cfg.barchart_enable or cfg.any_insights("barchart"):
+        col_dtype = detect_dtype(df[col], dtype)
+        if is_dtype(col_dtype, Continuous()) and (cfg.hist.enable or cfg.insight.enable):
+            data.append((col, Continuous(), _cont_calcs(df[col].dropna(), cfg)))
+        elif is_dtype(col_dtype, Nominal()) and (cfg.bar.enable or cfg.insight.enable):
             # cast the column as string type if it contains a mutable type
             try:
-                first_rows[col].apply(hash)
+                head[col].apply(hash)
             except TypeError:
-                srs = df[col] = srs.astype(str)
-            datas.append(calc_nom_col(srs.dropna(), first_rows[col], ngroups, largest))
-            col_names_dtypes.append((col, Nominal()))
-        elif is_dtype(col_dtype, Continuous()):
-            ## if cfg.hist_enable or cfg.any_insights("hist"):
-            datas.append(calc_cont_col(srs.dropna(), bins))
-            col_names_dtypes.append((col, Continuous()))
-        elif is_dtype(col_dtype, DateTime()):
-            datas.append(dask.delayed(_calc_line_dt)(df[[col]], timeunit))
-            col_names_dtypes.append((col, DateTime()))
-        else:
-            raise UnreachableError
-
-    ov_stats = calc_stats(df, dtype)
-    datas, ov_stats = dask.compute(datas, ov_stats)
+                df[col] = df[col].astype(str)
+            data.append((col, Nominal(), _nom_calcs(df[col].dropna(), head[col], cfg)))
+        elif is_dtype(col_dtype, DateTime()) and (cfg.line.enable or cfg.insight.enable):
+            data.append((col, DateTime(), dask.delayed(_calc_line_dt)(df[[col]], cfg.line.unit)))
+
+    ov_stats = calc_stats(df, cfg, dtype)  # overview statistics
+    data, ov_stats = dask.compute(data, ov_stats)
 
     # extract the plotting data, and detect and format the insights
-    plot_data: List[Any] = []
+    plot_data: List[Tuple[str, DType, Any]] = []
     col_insights: Dict[str, List[str]] = {}
-    ov_insights = format_overview(ov_stats)
-    nrows = ov_stats["nrows"]
-    for (col, dtp), dat in zip(col_names_dtypes, datas):
+    all_ins = _format_ov_ins(ov_stats, cfg) if cfg.insight.enable else []
+
+    for col, dtp, dat in data:
         if is_dtype(dtp, Continuous()):
-            hist, col_ins, ov_ins = format_cont(col, dat, nrows)
-            if hist:
-                plot_data.append((col, dtp, hist))
+            if cfg.insight.enable:
+                col_ins, ins = _format_cont_ins(col, dat, ov_stats["nrows"], cfg)
+            if cfg.hist.enable:
+                plot_data.append((col, dtp, dat["hist"]))
         elif is_dtype(dtp, Nominal()):
-            bardata, col_ins, ov_ins = format_nom(col, dat, nrows)
-            if bardata:
-                plot_data.append((col, dtp, bardata))
+            if cfg.insight.enable:
+                col_ins, ins = _format_nom_ins(col, dat, ov_stats["nrows"], cfg)
+            if cfg.bar.enable:
+                plot_data.append((col, dtp, (dat["bar"].to_frame(), dat["nuniq"])))
         elif is_dtype(dtp, DateTime()):
             plot_data.append((col, dtp, dat))
             continue
-        if col_ins:
-            col_insights[col] = col_ins
-        ov_insights += ov_ins
+
+        if cfg.insight.enable:
+            if col_ins:
+                col_insights[col] = col_ins
+            all_ins += ins
 
     return Intermediate(
         data=plot_data,
         stats=ov_stats,
         column_insights=col_insights,
-        overview_insights=_insight_pagination(ov_insights),
-        ov_insights=ov_insights,
+        overview_insights=_insight_pagination(all_ins),
         visual_type="distribution_grid",
     )
 
 
-## def calc_cont_col(srs: dd.Series, cfg: Config)
-def calc_cont_col(srs: dd.Series, bins: int) -> Dict[str, Any]:
+def _cont_calcs(srs: dd.Series, cfg: Config) -> Dict[str, Any]:
     """
-    Computations for a numerical column in plot(df)
-
-    Parameters
-    ----------
-    srs
-        srs over which to compute the barchart and insights
-    bins
-        number of bins in the bar chart
+    Computations for a continuous column in plot(df)
     """
     # dictionary of data for the histogram and related insights
     data: Dict[str, Any] = {}
 
-    ## if cfg.insight.missing_enable:
-    data["npres"] = srs.shape[0]
-
-    ## if cfg.insight.infinity_enable:
-    is_inf_srs = srs.isin({np.inf, -np.inf})
-    data["ninf"] = is_inf_srs.sum()
-
-    # remove infinite values
-    srs = srs[~is_inf_srs]
-
-    ## if cfg.hist_enable or config.insight.uniform_enable or cfg.insight.normal_enable:
-    ## bins = cfg.hist_bins
-    data["hist"] = da.histogram(srs, bins=bins, range=[srs.min(), srs.max()])
+    if cfg.insight.enable:
+        data["npres"] = srs.shape[0]  # number of present (not null) values
 
-    ## if cfg.insight.uniform_enable:
-    data["chisq"] = chisquare(data["hist"][0])
+    # drop infinite values
+    srs = srs[~srs.isin({np.inf, -np.inf})]
 
-    ## if cfg.insight.normal_enable
-    data["norm"] = normaltest(data["hist"][0])
+    # histogram
+    data["hist"] = da.histogram(srs, bins=cfg.hist.bins, range=(srs.min(), srs.max()))
 
-    ## if cfg.insight.negative_enable:
-    data["nneg"] = (srs < 0).sum()
-
-    ## if cfg.insight.skew_enabled:
-    data["skew"] = skewtest(data["hist"][0])
-
-    ## if cfg.insight.unique_enabled:
-    data["nuniq"] = srs.nunique()
-
-    ## if cfg.insight.zero_enabled:
-    data["nzero"] = (srs == 0).sum()
+    if cfg.insight.enable:
+        data["chisq"] = chisquare(data["hist"][0])
+        data["norm"] = normaltest(data["hist"][0])
+        data["skew"] = skewtest(data["hist"][0])
+        data["nneg"] = (srs < 0).sum()  # number of negative values
+        data["nuniq"] = srs.nunique()  # number of unique values
+        data["nzero"] = (srs == 0).sum()  # number of zeros
 
     return data
 
 
-## def calc_nom_col(srs: dd.Series, first_rows: pd.Series, cfg: Config)
-def calc_nom_col(
-    srs: dd.Series, first_rows: pd.Series, ngroups: int, largest: bool
-) -> Dict[str, Any]:
+def _nom_calcs(srs: dd.Series, head: pd.Series, cfg: Config) -> Dict[str, Any]:
     """
-    Computations for a categorical column in plot(df)
-
-    Parameters
-    ----------
-    srs
-        srs over which to compute the barchart and insights
-    first_rows
-        first rows of the dataset read into memory
-    ngroups
-        number of groups to show in the barchart
-    largest
-        whether to show the largest or smallest groups
+    Computations for a nominal column in plot(df)
     """
     # dictionary of data for the bar chart and related insights
-    data = {}
+    data: Dict[str, Any] = {}
 
-    ## if cfg.barchart_enable or cfg.insight.uniform_enable:
+    # value counts for barchart and uniformity insight
     grps = srs.value_counts(sort=False)
 
-    ##    if cfg.barchart_enable:
-    ##       nbars = cfg.barchart_nbars
-    ##       largest = cfg.barchart_largest
-    # select the largest or smallest groups
-    data["bar"] = grps.nlargest(ngroups) if largest else grps.nsmallest(ngroups)
-
-    ##    if cfg.insight.uniform_enable:
-    # compute a chi-squared test on the frequency distribution
-    data["chisq"] = chisquare(grps.values)
-
-    ##    if cfg.barchart_enable or cfg.insight.unique_enable:
-    # total number of groups
-    data["nuniq"] = grps.shape[0]
-
-    ##    if cfg.insight.missing_enable:
-    # number of present (not null) values
-    data["npres"] = grps.sum()
-
-    ## if cfg.insight.unique_enable and not cfg.barchart_enable:
-    ## data["nuniq"] = srs.nunique()
-
-    ## if cfg.insight.missing_enable and not cfg.barchart_enable:
-    ## data["npresent"] = srs.shape[0]
-
-    ## if cfg.insight.constant_length_enable:
-    if not first_rows.apply(lambda x: isinstance(x, str)).all():
-        srs = srs.astype(str)  # srs must be a string to compute the value lengths
-    lengths = srs.str.len()
-    data["min_len"], data["max_len"] = lengths.min(), lengths.max()
+    if cfg.bar.enable:
+        # select the largest or smallest groups
+        data["bar"] = (
+            grps.nlargest(cfg.bar.bars) if cfg.bar.sort_descending else grps.nsmallest(cfg.bar.bars)
+        )
+        data["nuniq"] = grps.shape[0]
+
+    if cfg.insight.enable:
+        data["chisq"] = chisquare(grps.values)  # chi-squared test for uniformity
+        data["nuniq"] = grps.shape[0]  # number of unique values
+        data["npres"] = grps.sum()  # number of present (not null) values
+        if not head.apply(lambda x: isinstance(x, str)).all():
+            srs = srs.astype(str)  # srs must be a string to compute the value lengths
+        data["min_len"], data["max_len"] = srs.str.len().min(), srs.str.len().max()
 
     return data
 
 
-## def calc_stats(srs: dd.Series, dtype_cnts: Dict[str, int], num_cols: List[str], cfg: Config)
-def calc_stats(df: dd.DataFrame, dtype: Optional[DTypeDef]) -> Dict[str, Any]:
+def calc_stats(df: dd.DataFrame, cfg: Config, dtype: Optional[DTypeDef]) -> Dict[str, Any]:
     """
-    Calculate the statistics for plot(df) from a DataFrame
-
-    Parameters
-    ----------
-    df
-        a DataFrame
-    dtype
-        str or DType or dict of str or dict of DType
+    Calculate the statistics for plot(df)
     """
-
     stats = {"nrows": df.shape[0]}
 
-    ## if cfg.stats_enable
-    dtype_cnts, num_cols = get_dtype_cnts_and_num_cols(df, dtype)
-    stats["ncols"] = df.shape[1]
-    stats["npresent_cells"] = df.count().sum()
-    stats["nrows_wo_dups"] = df.drop_duplicates().shape[0]
-    stats["mem_use"] = df.memory_usage(deep=True).sum()
-    stats["dtype_cnts"] = dtype_cnts
+    if cfg.stats.enable or cfg.insight.enable:
+        dtype_cnts, num_cols = get_dtype_cnts_and_num_cols(df, dtype)
 
-    ## if cfg.insight.duplicates_enable and not cfg.stats_enable
-    ## stats["nrows_wo_dups"] = df.drop_duplicates().shape[0]
+    if cfg.stats.enable:
+        stats["ncols"] = df.shape[1]
+        stats["npresent_cells"] = df.count().sum()
+        stats["nrows_wo_dups"] = df.drop_duplicates().shape[0]
+        stats["mem_use"] = df.memory_usage(deep=True).sum()
+        stats["dtype_cnts"] = dtype_cnts
 
-    ## if cfg.insight.similar_distribution_enable
-    # compute distribution similarity on a data sample
-    df_smp = df.map_partitions(lambda x: x.sample(min(1000, x.shape[0])), meta=df)
-    stats["ks_tests"] = []
-    for col1, col2 in list(combinations(num_cols, 2)):
-        stats["ks_tests"].append((col1, col2, ks_2samp(df_smp[col1], df_smp[col2])[1] > 0.05))
+    if not cfg.stats.enable and cfg.insight.enable:
+        stats["nrows_wo_dups"] = df.drop_duplicates().shape[0]
+
+    if cfg.insight.enable:
+        # compute distribution similarity on a data sample
+        df_smp = df.map_partitions(lambda x: x.sample(min(1000, x.shape[0])), meta=df)
+        stats["ks_tests"] = []
+        for col1, col2 in combinations(num_cols, 2):
+            stats["ks_tests"].append((col1, col2, ks_2samp(df_smp[col1], df_smp[col2])[1]))
 
     return stats
 
 
-## def format_overview(data: Dict[str, Any], cfg: Config)
-def format_overview(data: Dict[str, Any]) -> List[Dict[str, str]]:
+def _format_ov_ins(data: Dict[str, Any], cfg: Config) -> List[Dict[str, str]]:
     """
-    Determine and format the overview statistics and insights from plot(df)
-
-    Parameters
-    ----------
-    data
-        dictionary with overview statistics
+    Determine and format the overview insights for plot(df)
     """
     # list of insights
     ins: List[Dict[str, str]] = []
 
-    ## if cfg.insight.duplicates_enable
     pdup = round((1 - data["nrows_wo_dups"] / data["nrows"]) * 100, 2)
-    if pdup > 1:  ## if cfg.insight.duplicates_threshold
+    if pdup > cfg.insight.duplicates__threshold:
         ndup = data["nrows"] - data["nrows_wo_dups"]
         ins.append({"Duplicates": f"Dataset has {ndup} ({pdup}%) duplicate rows"})
 
-    ## if cfg.insight.similar_distribution_enable
     for (*cols, test_result) in data.get("ks_tests", []):
-        if test_result:
+        if test_result > cfg.insight.similar_distribution__threshold:
             msg = f"{cols[0]} and {cols[1]} have similar distributions"
             ins.append({"Similar Distribution": msg})
 
@@ -298,134 +205,88 @@ def format_overview(data: Dict[str, Any]) -> List[Dict[str, str]]:
     return ins
 
 
-## def format_cont(col: str, data: Dict[str, Any], nrows: int, cfg: Config)
-def format_cont(col: str, data: Dict[str, Any], nrows: int) -> Any:
+def _format_cont_ins(col: str, data: Dict[str, Any], nrows: int, cfg: Config) -> Any:
     """
-    Determine and format the insights for a numerical column
-
-    Parameters
-    ----------
-    col
-        the column associated with the insights
-    data
-        dictionary with overview statistics
-    nrows
-        number of rows in the dataset
+    Determine and format the insights for a continuous column
     """
     # list of insights
     ins: List[Dict[str, str]] = []
 
-    ## if cfg.insight.uniform_enable:
-    if data["chisq"][1] > 0.999:  ## cfg.insight.uniform_threshold
+    if data["chisq"][1] > cfg.insight.uniform__threshold:
         ins.append({"Uniform": f"{col} is uniformly distributed"})
 
-    ## if cfg.insight.missing_enable:
     pmiss = round((1 - (data["npres"] / nrows)) * 100, 2)
-    if pmiss > 1:  ## cfg.insight.missing_threshold
+    if pmiss > cfg.insight.missing__threshold:
         nmiss = nrows - data["npres"]
         ins.append({"Missing": f"{col} has {nmiss} ({pmiss}%) missing values"})
 
-    ## if cfg.insight.skewed_enable:
-    if data["skew"][1] < 1e-5:  ## cfg.insight.skewed_threshold
+    if data["skew"][1] < cfg.insight.skewed__threshold:
         ins.append({"Skewed": f"{col} is skewed"})
 
-    ## if cfg.insight.infinity_enable:
-    pinf = round(data["ninf"] / nrows * 100, 2)
-    if pinf >= 1:  ## cfg.insight.infinity_threshold
-        ninf = data["ninf"]
+    pinf = round((nrows - data["npres"]) / nrows * 100, 2)
+    if pinf >= cfg.insight.infinity__threshold:
+        ninf = nrows - data["npres"]
         ins.append({"Infinity": f"{col} has {ninf} ({pinf}%) infinite values"})
 
-    ## if cfg.insight.zeros_enable:
     pzero = round(data["nzero"] / nrows * 100, 2)
-    if pzero > 5:  ## cfg.insight.zeros_threshold
+    if pzero > cfg.insight.zeros__threshold:
         nzero = data["nzero"]
         ins.append({"Zeros": f"{col} has {nzero} ({pzero}%) zeros"})
 
-    ## if cfg.insight.negatives_enable:
     pneg = round(data["nneg"] / nrows * 100, 2)
-    if pneg > 1:  ## cfg.insight.negatives_threshold
+    if pneg > cfg.insight.negatives__threshold:
         nneg = data["nneg"]
         ins.append({"Negatives": f"{col} has {nneg} ({pneg}%) negatives"})
 
-    ## if cfg.insight.normal_enable:
-    if data["norm"][1] > 0.99:
+    if data["norm"][1] > cfg.insight.normal__threshold:
         ins.append({"Normal": f"{col} is normally distributed"})
 
-    hist = data["hist"]  ## if cfg.hist_enable else None
     # list of insight messages
     ins_msg_list = [list(insight.values())[0] for insight in ins]
 
-    return hist, ins_msg_list, ins
+    return ins_msg_list, ins
 
 
-## def format_nom(col: str, data: Dict[str, Any], nrows: int, cfg: Config)
-def format_nom(col: str, data: Dict[str, Any], nrows: int) -> Any:
+def _format_nom_ins(col: str, data: Dict[str, Any], nrows: int, cfg: Config) -> Any:
     """
-    Determine and format the insights for a categorical column
-
-    Parameters
-    ----------
-    col
-        the column associated with the insights
-    data
-        dictionary with overview statistics
-    nrows
-        number of rows in the dataset
+    Determine and format the insights for a nominal column
     """
     # list of insights
     ins: List[Dict[str, str]] = []
 
-    ## if cfg.insight.uniform_enable:
-    if data["chisq"][1] > 0.999:  ## cfg.insight.uniform_threshold
+    if data["chisq"][1] > cfg.insight.uniform__threshold:
         ins.append({"Uniform": f"{col} is uniformly distributed"})
 
-    ## if cfg.insight.missing_enable:
     pmiss = round((1 - (data["npres"] / nrows)) * 100, 2)
-    if pmiss > 1:  ## cfg.insight.missing_threshold
+    if pmiss > cfg.insight.missing__threshold:
         nmiss = nrows - data["npres"]
         ins.append({"Missing": f"{col} has {nmiss} ({pmiss}%) missing values"})
 
-    ## if cfg.insight.high_cardinality_enable:
-    if data["nuniq"] > 50:  ## cfg.insght.high_cardinality_threshold
+    if data["nuniq"] > cfg.insight.high_cardinality__threshold:
         uniq = data["nuniq"]
         msg = f"{col} has a high cardinality: {uniq} distinct values"
         ins.append({"High Cardinality": msg})
 
-    ## if cfg.insight.constant_enable:
-    if data["nuniq"] == 1:
+    if data["nuniq"] == cfg.insight.constant__threshold:
         val = data["bar"].index[0]
         ins.append({"Constant": f'{col} has constant value "{val}"'})
 
-    ## if cfg.insight.constant_length_enable:
     if data["min_len"] == data["max_len"]:
         length = data["min_len"]
         ins.append({"Constant Length": f"{col} has constant length {length}"})
 
-    ## if cfg.insight.constant_length_enable:
     if data["nuniq"] == data["npres"]:
         ins.append({"Unique": f"{col} has all distinct values"})
 
-    bardata = (
-        data["bar"].to_frame(),
-        data["nuniq"],
-    )  ## if cfg.barchart.enable else None
     # list of insight messages
     ins_msg_list = [list(ins.values())[0] for ins in ins]
 
-    return bardata, ins_msg_list, ins
+    return ins_msg_list, ins
 
 
 def _insight_pagination(ins: List[Dict[str, str]]) -> Dict[int, List[Dict[str, str]]]:
     """
     Set the insight display order and paginate the insights
-    Parameters
-    ----------
-    olddc
-        a dict contains all insights for overview section
-    Returns
-    -------
-    Dict[int, List[Dict[str, str]]]
-        paginated dict
     """
     ins_order = [
         "Uniform",
@@ -446,7 +307,7 @@ def _insight_pagination(ins: List[Dict[str, str]]) -> Dict[int, List[Dict[str, s
     ins.sort(key=lambda x: ins_order.index(list(x.keys())[0]))
     # paginate the sorted insights
     page_count = int(np.ceil(len(ins) / 10))
-    paginated_ins: Dict[int, List[Dict[str, str]]] = {}
+    paginated_ins: Dict[int, List[Dict[str, str]]] = dict()
     for i in range(1, page_count + 1):
         paginated_ins[i] = ins[(i - 1) * 10 : i * 10]
 
diff --git a/dataprep/eda/distribution/compute/trivariate.py b/dataprep/eda/distribution/compute/trivariate.py
index 2f5622fd3..6f744c8a2 100644
--- a/dataprep/eda/distribution/compute/trivariate.py
+++ b/dataprep/eda/distribution/compute/trivariate.py
@@ -4,15 +4,9 @@
 
 import dask
 import dask.dataframe as dd
-from ...dtypes import (
-    Continuous,
-    DateTime,
-    DTypeDef,
-    Nominal,
-    detect_dtype,
-    drop_null,
-    is_dtype,
-)
+
+from ...configs import Config
+from ...dtypes import Continuous, DateTime, DTypeDef, Nominal, detect_dtype, drop_null, is_dtype
 from ...intermediate import Intermediate
 from .common import _calc_line_dt
 
@@ -22,10 +16,7 @@ def compute_trivariate(
     x: str,
     y: str,
     z: str,
-    ngroups: int,
-    largest: bool,
-    timeunit: str,
-    agg: str,
+    cfg: Config,
     dtype: Optional[DTypeDef] = None,
 ) -> Intermediate:
     """Compute functions for plot(df, x, y, z).
@@ -33,31 +24,15 @@ def compute_trivariate(
     Parameters
     ----------
     df
-        Dataframe from which plots are to be generated
+        DataFrame from which visualizations are generated
     x
-        A valid column name from the dataframe
+        A column name from the DataFrame
     y
-        A valid column name from the dataframe
+        A column name from the DataFrame
     z
-        A valid column name from the dataframe
-    bins
-        For a histogram or box plot with numerical x axis, it defines
-        the number of equal-width bins to use when grouping.
-    ngroups
-        When grouping over a categorical column, it defines the
-        number of groups to show in the plot. Ie, the number of
-        bars to show in a bar chart.
-    largest
-        If true, when grouping over a categorical column, the groups
-        with the largest count will be output. If false, the groups
-        with the smallest count will be output.
-    timeunit
-        Defines the time unit to group values over for a datetime column.
-        It can be "year", "quarter", "month", "week", "day", "hour",
-        "minute", "second". With default value "auto", it will use the
-        time unit such that the resulting number of groups is closest to 15.
-    agg
-        Specify the aggregate to use when aggregating over a numeric column
+        A column name from the DataFrame
+    cfg:
+        Config instance
     dtype: str or DType or dict of str or dict of DType, default None
         Specify Data Types for designated column or all columns.
         E.g.  dtype = {"a": Continuous, "b": "Nominal"} or
@@ -100,12 +75,16 @@ def compute_trivariate(
     df[z] = df[z].apply(str, meta=(z, str))
 
     # line chart
-    data = dask.compute(dask.delayed(_calc_line_dt)(df, timeunit, agg, ngroups, largest))
+    data = dask.compute(
+        dask.delayed(_calc_line_dt)(
+            df, cfg.line.unit, cfg.line.agg, cfg.line.ngroups, cfg.line.sort_descending
+        )
+    )
     return Intermediate(
         x=x,
         y=y,
         z=z,
-        agg=agg,
+        agg=cfg.line.agg,
         data=data[0],
         visual_type="dt_cat_num_cols",
     )
diff --git a/dataprep/eda/distribution/compute/univariate.py b/dataprep/eda/distribution/compute/univariate.py
index 1f5102f9a..9a8d95e8e 100644
--- a/dataprep/eda/distribution/compute/univariate.py
+++ b/dataprep/eda/distribution/compute/univariate.py
@@ -1,6 +1,8 @@
-"""Computations for plot(df, x)."""
+"""
+Computations for plot(df, x)
+"""
 
-from typing import Any, Dict, List, Optional, Tuple
+from typing import Any, Dict, List, Optional
 
 import dask
 import dask.array as da
@@ -12,130 +14,67 @@
 
 from ....assets.english_stopwords import english_stopwords as ess
 from ....errors import UnreachableError
-from ...dtypes import (
-    Continuous,
-    DateTime,
-    DTypeDef,
-    Nominal,
-    detect_dtype,
-    is_dtype,
-)
+from ...configs import Config
+from ...dtypes import Continuous, DateTime, DTypeDef, Nominal, detect_dtype, is_dtype
 from ...intermediate import Intermediate
 from .common import _calc_line_dt, gaussian_kde, normaltest
 
 
 def compute_univariate(
     df: dd.DataFrame,
-    x: str,
-    bins: int,
-    ngroups: int,
-    largest: bool,
-    timeunit: str,
-    top_words: int,
-    stopword: bool = True,
-    lemmatize: bool = False,
-    stem: bool = False,
-    value_range: Optional[Tuple[float, float]] = None,
-    dtype: Optional[DTypeDef] = None,
+    x: Optional[str],
+    cfg: Config,
+    dtype: Optional[DTypeDef],
 ) -> Intermediate:
-    """Compute functions for plot(df, x).
+    """
+    Compute functions for plot(df, x)
 
     Parameters
     ----------
     df
-        Dataframe from which plots are to be generated
+        DataFrame from which visualizations are generated
     x
-        A valid column name from the dataframe
-    bins
-        For a histogram or box plot with numerical x axis, it defines
-        the number of equal-width bins to use when grouping.
-    ngroups
-        When grouping over a categorical column, it defines the
-        number of groups to show in the plot. Ie, the number of
-        bars to show in a bar chart.
-    largest
-        If true, when grouping over a categorical column, the groups
-        with the largest count will be output. If false, the groups
-        with the smallest count will be output.
-    timeunit
-        Defines the time unit to group values over for a datetime column.
-        It can be "year", "quarter", "month", "week", "day", "hour",
-        "minute", "second". With default value "auto", it will use the
-        time unit such that the resulting number of groups is closest to 15.
-    top_words: int, default 30
-        Specify the amount of words to show in the wordcloud and
-        word frequency bar chart
-    stopword: bool, default True
-        Eliminate the stopwords in the text data for plotting wordcloud and
-        word frequency bar chart
-    lemmatize: bool, default False
-        Lemmatize the words in the text data for plotting wordcloud and
-        word frequency bar chart
-    stem: bool, default False
-        Apply Potter Stem on the text data for plotting wordcloud and
-        word frequency bar chart
-    value_range
-        The lower and upper bounds on the range of a numerical column.
-        Applies when column x is specified and column y is unspecified.
+        A column name from the DataFrame
+    cfg
+        Config instance
     dtype: str or DType or dict of str or dict of DType, default None
         Specify Data Types for designated column or all columns.
         E.g.  dtype = {"a": Continuous, "b": "Nominal"} or
         dtype = {"a": Continuous(), "b": "nominal"}
         or dtype = Continuous() or dtype = "Continuous" or dtype = Continuous()
     """
-    # pylint: disable=too-many-locals, too-many-arguments
-
     col_dtype = detect_dtype(df[x], dtype)
+
     if is_dtype(col_dtype, Nominal()):
-        first_rows = df[x].head()  # dd.Series.head() triggers a (small) data read
+        head = df[x].head()  # dd.Series.head() triggers a (small) data read
         # cast the column as string type if it contains a mutable type
         try:
-            first_rows.apply(hash)
+            head.apply(hash)
         except TypeError:
             df[x] = df[x].astype(str)
+
         # all computations for plot(df, Nominal())
-        data = nom_comps(
-            df[x],
-            first_rows,
-            ngroups,
-            largest,
-            bins,
-            top_words,
-            stopword,
-            lemmatize,
-            stem,
-        )
-        (data,) = dask.compute(data)
+        (data,) = dask.compute(nom_comps(df[x], head, cfg))
 
-        return Intermediate(
-            col=x,
-            data=data,
-            visual_type="categorical_column",
-        )
+        return Intermediate(col=x, data=data, visual_type="categorical_column")
 
     elif is_dtype(col_dtype, Continuous()):
-        # extract the column
-        srs = df[x]
-        # select values in the user defined range
-        if value_range is not None:
-            srs = srs[srs.between(*value_range)]
-
         # all computations for plot(df, Continuous())
-        (data,) = dask.compute(cont_comps(srs, bins))
+        (data,) = dask.compute(cont_comps(df[x], cfg))
 
-        return Intermediate(
-            col=x,
-            data=data,
-            visual_type="numerical_column",
-        )
+        return Intermediate(col=x, data=data, visual_type="numerical_column")
 
     elif is_dtype(col_dtype, DateTime()):
         data_dt: List[Any] = []
         # stats
         data_dt.append(dask.delayed(calc_stats_dt)(df[x]))
         # line chart
-        data_dt.append(dask.delayed(_calc_line_dt)(df[[x]], timeunit))
-        data, line = dask.compute(*data_dt)
+        if cfg.line.enable:
+            data_dt.append(dask.delayed(_calc_line_dt)(df[[x]], cfg.line.unit))
+            data, line = dask.compute(*data_dt)
+        else:
+            data = dask.compute(*data_dt)[0]
+            line = []
         return Intermediate(
             col=x,
             data=data,
@@ -146,279 +85,239 @@ def compute_univariate(
         raise UnreachableError
 
 
-def nom_comps(
-    srs: dd.Series,
-    first_rows: pd.Series,
-    ngroups: int,
-    largest: bool,
-    bins: int,
-    top_words: int,
-    stopword: bool,
-    lemmatize: bool,
-    stem: bool,
-) -> Dict[str, Any]:
+def nom_comps(srs: dd.Series, head: pd.Series, cfg: Config) -> Dict[str, Any]:
+    """
+    All computations required for plot(df, Nominal)
     """
-    This function aggregates all of the computations required for plot(df, Nominal())
+    # pylint: disable=too-many-branches
+    data: Dict[str, Any] = dict()
 
-    Parameters
-    ----------
-    srs
-        one categorical column
-    ngroups
-        Number of groups to return
-    largest
-        If true, show the groups with the largest count,
-        else show the groups with the smallest count
-    bins
-        number of bins for the category length frequency histogram
-    top_words
-        Number of highest frequency words to show in the
-        wordcloud and word frequency bar chart
-    stopword
-        If True, remove stop words, else keep them
-    lemmatize
-        If True, lemmatize the words before computing
-        the word frequencies, else don't
-    stem
-        If True, extract the stem of the words before
-        computing the word frequencies, else don't
-    """  # pylint: disable=too-many-arguments
+    data["nrows"] = srs.shape[0]  # total rows
+    srs = srs.dropna()  # drop null values
+    grps = srs.value_counts(sort=False)  # counts of unique values in the series
 
-    data: Dict[str, Any] = {}
+    if cfg.stats.enable or cfg.bar.enable or cfg.pie.enable:
+        data["nuniq"] = grps.shape[0]  # total number of groups
 
-    # total rows
-    data["nrows"] = srs.shape[0]
-    # drop null values
-    srs = srs.dropna()
-
-    ## if cfg.bar_enable or cfg.pie_enable
-    # counts of unique values in the series
-    grps = srs.value_counts(sort=False)
-    # total number of groups
-    data["nuniq"] = grps.shape[0]
-    # select the largest or smallest groups
-    data["bar"] = grps.nlargest(ngroups) if largest else grps.nsmallest(ngroups)
-    ##     if cfg.barchart_bars == cfg.piechart_slices:
-    data["pie"] = data["bar"]
-    ##     else
-    ##     data["pie"] = grps.nlargest(ngroups) if largest else grps.nsmallest(ngroups)
-    ##     if cfg.insights.evenness_enable
-    data["chisq"] = chisquare(grps.values)
-
-    ## if cfg.stats_enable
-    df = grps.reset_index()
-    ## if cfg.stats_enable or cfg.word_freq_enable
-    if not first_rows.apply(lambda x: isinstance(x, str)).all():
-        srs = srs.astype(str)  # srs must be a string to compute the value lengths
-        df[df.columns[0]] = df[df.columns[0]].astype(str)
-    data.update(calc_cat_stats(srs, df, bins, data["nrows"], data["nuniq"]))
-    # ## if cfg.word_freq_enable
-    data.update(calc_word_freq(df, top_words, stopword, lemmatize, stem))
+    # compute bar and pie together unless the parameters are different
+    if cfg.bar.enable or cfg.pie.enable:
+        # select the largest or smallest groups
+        data["bar"] = (
+            grps.nlargest(cfg.bar.bars) if cfg.bar.sort_descending else grps.nsmallest(cfg.bar.bars)
+        )
+        if cfg.bar.bars == cfg.pie.slices and cfg.bar.sort_descending == cfg.pie.sort_descending:
+            data["pie"] = data["bar"]
+        else:
+            data["pie"] = (
+                grps.nlargest(cfg.pie.slices)
+                if cfg.pie.sort_descending
+                else grps.nsmallest(cfg.pie.slices)
+            )
+        if cfg.insight.enable:
+            data["chisq"] = chisquare(grps.values)
+
+    df = grps.reset_index()  # dataframe with group names and counts
+
+    if cfg.stats.enable or cfg.wordlen.enable:
+        if not head.apply(lambda x: isinstance(x, str)).all():
+            srs = srs.astype(str)  # srs must be a string to compute the value lengths
+    if cfg.stats.enable or cfg.wordcloud.enable or cfg.wordfreq.enable:
+        if not head.apply(lambda x: isinstance(x, str)).all():
+            df[df.columns[0]] = df[df.columns[0]].astype(str)
+
+    if cfg.stats.enable:
+        data.update(_calc_nom_stats(srs, df, data["nrows"], data["nuniq"]))
+    elif cfg.wordfreq.enable and cfg.insight.enable:
+        data["len_stats"] = {"Minimum": srs.str.len().min(), "Maximum": srs.str.len().max()}
+    if cfg.wordlen.enable:
+        lens = srs.str.len()
+        data["len_hist"] = da.histogram(lens, cfg.wordlen.bins, (lens.min(), lens.max()))
+    if cfg.wordcloud.enable or cfg.wordfreq.enable:
+        if all(
+            getattr(cfg.wordcloud, att) == getattr(cfg.wordfreq, att)
+            for att in ("top_words", "stopword", "stem", "lemmatize")
+        ):
+            word_freqs = _calc_word_freq(
+                df,
+                cfg.wordfreq.top_words,
+                cfg.wordfreq.stopword,
+                cfg.wordfreq.lemmatize,
+                cfg.wordfreq.stem,
+            )
+            data["word_cnts_cloud"] = word_freqs["word_cnts"]
+            data["nuniq_words_cloud"] = word_freqs["nuniq_words"]
+        else:
+            word_freqs = _calc_word_freq(
+                df.copy(),
+                cfg.wordfreq.top_words,
+                cfg.wordfreq.stopword,
+                cfg.wordfreq.lemmatize,
+                cfg.wordfreq.stem,
+            )
+            word_freqs_cloud = _calc_word_freq(
+                df,
+                cfg.wordcloud.top_words,
+                cfg.wordcloud.stopword,
+                cfg.wordcloud.lemmatize,
+                cfg.wordcloud.stem,
+            )
+            data["word_cnts_cloud"] = word_freqs_cloud["word_cnts"]
+            data["nuniq_words_cloud"] = word_freqs["nuniq_words"]
+
+        data["word_cnts_freq"] = word_freqs["word_cnts"]
+        data["nwords_freq"] = word_freqs["nwords"]
 
     return data
 
 
-def cont_comps(srs: dd.Series, bins: int) -> Dict[str, Any]:
+def cont_comps(srs: dd.Series, cfg: Config) -> Dict[str, Any]:
     """
-    This function aggregates all of the computations required for plot(df, Continuous())
-
-    Parameters
-    ----------
-    srs
-        one numerical column
-    bins
-        the number of bins in the histogram
+    All computations required for plot(df, Continuous)
     """
-
+    # pylint: disable=too-many-branches
     data: Dict[str, Any] = {}
 
-    ## if cfg.stats_enable or cfg.hist_enable or
-    # calculate the total number of rows then drop the missing values
-    data["nrows"] = srs.shape[0]
+    if cfg.stats.enable or cfg.hist.enable:
+        data["nrows"] = srs.shape[0]  # total rows
+
     srs = srs.dropna()
-    ## if cfg.stats_enable
-    # number of not null (present) values
-    data["npres"] = srs.shape[0]
-    # remove infinite values
-    srs = srs[~srs.isin({np.inf, -np.inf})]
-
-    # shared computations
-    ## if cfg.stats_enable or cfg.hist_enable or cfg.qqplot_enable and cfg.insights_enable:
-    data["min"], data["max"] = srs.min(), srs.max()
-    ## if cfg.hist_enable or cfg.qqplot_enable and cfg.ingsights_enable:
-    data["hist"] = da.histogram(srs, bins=bins, range=[data["min"], data["max"]])
-    ## if cfg.insights_enable and (cfg.qqplot_enable or cfg.hist_enable):
-    data["norm"] = normaltest(data["hist"][0])
-    ## if cfg.qqplot_enable
-    data["qntls"] = srs.quantile(np.linspace(0.01, 0.99, 99))
-    ## elif cfg.stats_enable
-    ## data["qntls"] = srs.quantile([0.05, 0.25, 0.5, 0.75, 0.95])
-    ## elif cfg.boxplot_enable
-    ## data["qntls"] = srs.quantile([0.25, 0.5, 0.75])
-    ## if cfg.stats_enable or cfg.hist_enable and cfg.insights_enable:
-    data["skew"] = skew(srs)
-
-    # if cfg.stats_enable
-    data["nuniq"] = srs.nunique()
-    data["nreals"] = srs.shape[0]
-    data["nzero"] = (srs == 0).sum()
-    data["nneg"] = (srs < 0).sum()
-    data["mean"] = srs.mean()
-    data["std"] = srs.std()
-    data["kurt"] = kurtosis(srs)
-    data["mem_use"] = srs.memory_usage(deep=True)
-
-    ## if cfg.hist_enable and cfg.insight_enable
-    data["chisq"] = chisquare(data["hist"][0])
+
+    if cfg.stats.enable:
+        data["npres"] = srs.shape[0]  # number of present (not null) values
+
+    srs = srs[~srs.isin({np.inf, -np.inf})]  # remove infinite values
+
+    if cfg.hist.enable or cfg.qqnorm.enable and cfg.insight.enable:
+        data["hist"] = da.histogram(srs, cfg.hist.bins, (srs.min(), srs.max()))
+        if cfg.insight.enable:
+            data["norm"] = normaltest(data["hist"][0])
+
+    if cfg.hist.enable and cfg.insight.enable:
+        data["chisq"] = chisquare(data["hist"][0])
+
+    # compute only the required amount of quantiles
+    if cfg.qqnorm.enable:
+        data["qntls"] = srs.quantile(np.linspace(0.01, 0.99, 99))
+    elif cfg.stats.enable:
+        data["qntls"] = srs.quantile([0.05, 0.25, 0.5, 0.75, 0.95])
+    elif cfg.box.enable:
+        data["qntls"] = srs.quantile([0.25, 0.5, 0.75])
+
+    if cfg.stats.enable or cfg.hist.enable and cfg.insight.enable:
+        data["skew"] = skew(srs)
+
+    if cfg.stats.enable or cfg.qqnorm.enable:
+        data["mean"] = srs.mean()
+        data["std"] = srs.std()
+
+    if cfg.stats.enable:
+        data["min"] = srs.min()
+        data["max"] = srs.max()
+        data["nuniq"] = srs.nunique()
+        data["nreals"] = srs.shape[0]
+        data["nzero"] = (srs == 0).sum()
+        data["nneg"] = (srs < 0).sum()
+        data["kurt"] = kurtosis(srs)
+        data["mem_use"] = srs.memory_usage(deep=True)
 
     # compute the density histogram
-    data["dens"] = da.histogram(srs, bins=bins, range=[data["min"], data["max"]], density=True)
-    # gaussian kernel density estimate
-    data["kde"] = gaussian_kde(
-        srs.map_partitions(lambda x: x.sample(min(1000, x.shape[0])), meta=srs)
-    )
+    if cfg.kde.enable:
+        data["dens"] = da.histogram(srs, cfg.kde.bins, (srs.min(), srs.max()), density=True)
+        # gaussian kernel density estimate
+        data["kde"] = gaussian_kde(
+            srs.map_partitions(lambda x: x.sample(min(1000, x.shape[0])), meta=srs)
+        )
 
-    ## if cfg.box_enable
-    data.update(calc_box(srs, data["qntls"]))
+    if cfg.box.enable:
+        data.update(_calc_box(srs, data["qntls"], cfg))
 
     return data
 
 
-def calc_box(srs: dd.Series, qntls: da.Array) -> Dict[str, Any]:
+def _calc_box(srs: dd.Series, qntls: da.Array, cfg: Config) -> Dict[str, Any]:
     """
     Box plot calculations
-
-    Parameters
-    ----------
-    srs
-        one numerical column
-    qntls
-        quantiles of the column
     """
-    data: Dict[str, Any] = {}
-
     # quartiles
-    data["qrtl1"] = qntls.loc[0.25].sum()
-    data["qrtl2"] = qntls.loc[0.5].sum()
-    data["qrtl3"] = qntls.loc[0.75].sum()
+    data = {f"qrtl{i + 1}": qntls.loc[qnt].sum() for i, qnt in enumerate((0.25, 0.5, 0.75))}
+
+    # inter-quartile range
     iqr = data["qrtl3"] - data["qrtl1"]
     srs_iqr = srs[srs.between(data["qrtl1"] - 1.5 * iqr, data["qrtl3"] + 1.5 * iqr)]
+    # lower and upper whiskers
+    data["lw"], data["uw"] = srs_iqr.min(), srs_iqr.max()
+
     # outliers
     otlrs = srs[~srs.between(data["qrtl1"] - 1.5 * iqr, data["qrtl3"] + 1.5 * iqr)]
     # randomly sample at most 100 outliers from each partition without replacement
     smp_otlrs = otlrs.map_partitions(lambda x: x.sample(min(100, x.shape[0])), meta=otlrs)
-    data["lw"] = srs_iqr.min()
-    data["uw"] = srs_iqr.max()
     data["otlrs"] = smp_otlrs.values
-    ##    if cfg.insights_enable
-    data["notlrs"] = otlrs.shape[0]
+    if cfg.insight.enable:
+        data["notlrs"] = otlrs.shape[0]
 
     return data
 
 
-def calc_word_freq(
+def _calc_word_freq(
     df: dd.DataFrame,
-    top_words: int = 30,
-    stopword: bool = True,
-    lemmatize: bool = False,
-    stem: bool = False,
+    top_words: int,
+    stopword: bool,
+    lemmatize: bool,
+    stem: bool,
 ) -> Dict[str, Any]:
     """
-    Parse a categorical column of text data into words, and then
-    compute the frequency distribution of words and the total
-    number of words.
-
-    Parameters
-    ----------
-    df
-        Groupby-count on the categorical column as a dataframe
-    top_words
-        Number of highest frequency words to show in the
-        wordcloud and word frequency bar chart
-    stopword
-        If True, remove stop words, else keep them
-    lemmatize
-        If True, lemmatize the words before computing
-        the word frequencies, else don't
-    stem
-        If True, extract the stem of the words before
-        computing the word frequencies, else don't
+    Parse a categorical column of text data into words, then compute
+    the frequency distribution of words and the total number of words.
     """
     col = df.columns[0]
-    if stopword:
-        # use a regex to replace stop words and non-alphanumeric characters with empty string
-        df[col] = df[col].str.replace(fr"\b(?:{'|'.join(ess)})\b|[^\w+ ]", "")
-    else:
-        df[col] = df[col].str.replace(r"[^\w+ ]", "")
-    # convert to lowercase and split
-    df[col] = df[col].str.lower().str.split()
-    # "explode()" to "stack" all the words in a list into a new column
+
+    regex = fr"\b(?:{'|'.join(ess)})\b|[^\w+ ]" if stopword else r"[^\w+ ]"
+    # use a regex to replace stop words and non-alphanumeric characters with empty string
+    df[col] = df[col].str.replace(regex, "").str.lower().str.split()
+
+    # ".explode()" to "stack" all the words in a list into a new column
     df = df.explode(col)
 
     # lemmatize and stem
     if lemmatize or stem:
         df[col] = df[col].dropna()
     if lemmatize:
-        lem = WordNetLemmatizer()
-        df[col] = df[col].apply(lem.lemmatize, meta="object")
+        df[col] = df[col].apply(WordNetLemmatizer().lemmatize, meta=object)
     if stem:
-        porter = PorterStemmer()
-        df[col] = df[col].apply(porter.stem, meta="object")
-
-    # counts of words, excludes null values
-    word_cnts = df.groupby(col)[df.columns[1]].sum()
-    # total number of words
-    nwords = word_cnts.sum()
-    # total uniq words
-    nuniq_words = word_cnts.shape[0]
-    # words with the highest frequency
-    fnl_word_cnts = word_cnts.nlargest(n=top_words)
+        df[col] = df[col].apply(PorterStemmer().stem, meta=object)
+
+    word_cnts = df.groupby(col)[df.columns[1]].sum()  # counts of words, excludes null values
+    nwords = word_cnts.sum()  # total number of words
+    nuniq_words = word_cnts.shape[0]  # total unique words
+    fnl_word_cnts = word_cnts.nlargest(top_words)  # words with the highest frequency
 
     return {"word_cnts": fnl_word_cnts, "nwords": nwords, "nuniq_words": nuniq_words}
 
 
-def calc_cat_stats(
+def _calc_nom_stats(
     srs: dd.Series,
     df: dd.DataFrame,
-    bins: int,
     nrows: int,
-    nuniq: Optional[dd.core.Scalar] = None,
+    nuniq: dd.core.Scalar,
 ) -> Dict[str, Any]:
     """
-    Calculate stats for a categorical column
-
-    Parameters
-    ----------
-    srs
-        a categorical column
-    df
-        groupby-count on the categorical column as a dataframe
-    bins
-        number of bins for the category length frequency histogram
-    nrows
-        number of rows before dropping null values
-    nuniq
-        number of unique values in the column
+    Calculate statistics for a nominal column
     """
-    # pylint: disable=too-many-locals
     # overview stats
     stats = {
         "nrows": nrows,
         "npres": srs.shape[0],
-        "nuniq": nuniq,  # if cfg.bar_endable or cfg.pie_enable else srs.nunique(),
+        "nuniq": nuniq,
         "mem_use": srs.memory_usage(deep=True),
         "first_rows": srs.reset_index(drop=True).loc[:4],
     }
     # length stats
-    lengths = srs.str.len()
-    minv, maxv = lengths.min(), lengths.max()
-    hist = da.histogram(lengths.values, bins=bins, range=[minv, maxv])
     leng = {
-        "Mean": lengths.mean(),
-        "Standard Deviation": lengths.std(),
-        "Median": lengths.quantile(0.5),
-        "Minimum": minv,
-        "Maximum": maxv,
+        "Mean": srs.str.len().mean(),
+        "Standard Deviation": srs.str.len().std(),
+        "Median": srs.str.len().quantile(0.5),
+        "Minimum": srs.str.len().min(),
+        "Maximum": srs.str.len().max(),
     }
     # letter stats
     # computed on groupby-count:
@@ -435,21 +334,12 @@ def calc_cat_stats(
         "Decimal Number": (df[grp].str.count(r"[0-9]") * df[col]).sum(),
     }
 
-    return {"stats": stats, "len_stats": leng, "letter_stats": letter, "len_hist": hist}
+    return {"stats": stats, "len_stats": leng, "letter_stats": letter}
 
 
 def calc_stats_dt(srs: dd.Series) -> Dict[str, str]:
     """
     Calculate stats from a datetime column
-
-    Parameters
-    ----------
-    srs
-        a datetime column
-    Returns
-    -------
-    Dict[str, str]
-        Dictionary that contains Overview
     """
     size = srs.shape[0]  # include nan
     count = srs.count()  # exclude nan
diff --git a/dataprep/eda/distribution/render.py b/dataprep/eda/distribution/render.py
index 2e5ceb0fa..396676260 100644
--- a/dataprep/eda/distribution/render.py
+++ b/dataprep/eda/distribution/render.py
@@ -3,6 +3,7 @@
 """  # pylint: disable=too-many-lines
 from pathlib import Path
 from typing import Any, Dict, List, Optional, Tuple, Union
+
 import numpy as np
 import pandas as pd
 from bokeh.layouts import row
@@ -27,6 +28,7 @@
 from scipy.stats import norm
 from wordcloud import WordCloud
 
+from ..configs import Config
 from ..dtypes import Continuous, DateTime, Nominal, is_dtype
 from ..intermediate import Intermediate
 from ..palette import CATEGORY20, PASTEL1, RDBU, VIRIDIS
@@ -872,7 +874,7 @@ def scatter_viz(
     # pylint: disable=too-many-arguments
     title = f"{y} by {x}" if len(df) < spl_sz else f"{y} by {x} (sample size {spl_sz})"
     tooltips = [("(x, y)", f"(@{{{x}}}, @{{{y}}})")]
-    fig = figure(  # pylint: disable=too-many-function-args
+    fig = figure(
         tools="hover",
         title=title,
         toolbar_location=None,
@@ -880,9 +882,7 @@ def scatter_viz(
         plot_width=plot_width,
         plot_height=plot_height,
     )
-    fig.circle(  # pylint: disable=too-many-function-args
-        x, y, color=CATEGORY20[0], size=4, name="points", source=df
-    )
+    fig.circle(x, y, color=CATEGORY20[0], source=df)  # pylint: disable=too-many-function-args
     tweak_figure(fig)
     fig.xaxis.axis_label = x
     fig.yaxis.axis_label = y
@@ -897,7 +897,8 @@ def hexbin_viz(
     y: str,
     plot_width: int,
     plot_height: int,
-    tile_size: Optional[float] = None,
+    tile_size: str,
+    aspect_scale: float,
 ) -> Panel:
     """
     Render a hexbin plot
@@ -905,7 +906,7 @@ def hexbin_viz(
     # pylint: disable=too-many-arguments,too-many-locals
     xmin, xmax = df[x].min(), df[x].max()
     ymin, ymax = df[y].min(), df[y].max()
-    if tile_size is None:
+    if tile_size == "auto":
         tile_size = (xmax - xmin) / 25
     title = f"{y} by {x}"
     aspect_scale = (ymax - ymin) / (xmax - xmin + 1e-9)
@@ -953,8 +954,8 @@ def hexbin_viz(
     color_bar.label_standoff = 8
     fig.add_layout(color_bar, "left")
     tweak_figure(fig, "hex")
-    _format_axis(fig, xmin, xmax, "x")
-    _format_axis(fig, ymin, ymax, "y")
+    _format_axis(fig, df[x].min(), df[x].max(), "x")
+    _format_axis(fig, df[y].min(), df[y].max(), "y")
     fig.xaxis.axis_label = x
     fig.yaxis.axis_label = y
 
@@ -1420,112 +1421,149 @@ def stats_viz_dt(stats: Dict[str, Any]) -> Dict[str, Dict[str, str]]:
     return {"Overview": {k: _format_values(k, v) for k, v in stats.items()}}
 
 
-def render_distribution_grid(
-    itmdt: Intermediate, yscale: str, plot_width: int, plot_height: int
-) -> Dict[str, Any]:
+def render_distribution_grid(itmdt: Intermediate, cfg: Config) -> Dict[str, Any]:
     """
-    Render plots and dataset stats from plot(df)
-    """  # pylint: disable=too-many-locals
-    figs: List[Figure] = list()
+    Create visualizations for plot(df)
+    """
+    # pylint: disable=too-many-locals
+    plot_width = cfg.plot.width if cfg.plot.width is not None else 324
+    plot_height = cfg.plot.height if cfg.plot.height is not None else 300
+
+    figs: List[Figure] = []
+    htgs: Dict[str, Any] = {}
     nrows = itmdt["stats"]["nrows"]
     titles: List[str] = []
-    for col, dtype, data in itmdt["data"]:
-        if is_dtype(dtype, Nominal()):
+    for col, dtp, data in itmdt["data"]:
+        if is_dtype(dtp, Nominal()):
             df, ttl_grps = data
             fig = bar_viz(
                 df,
                 ttl_grps,
                 nrows,
                 col,
-                yscale,
+                cfg.bar.yscale,
                 plot_width,
                 plot_height,
                 False,
             )
-        elif is_dtype(dtype, Continuous()):
-            fig = hist_viz(data, nrows, col, yscale, plot_width, plot_height, False)
-            figs.append(fig)
-        elif is_dtype(dtype, DateTime()):
+            htgs[col] = cfg.bar.grid_how_to_guide()
+        elif is_dtype(dtp, Continuous()):
+            fig = hist_viz(data, nrows, col, cfg.hist.yscale, plot_width, plot_height, False)
+            htgs[col] = cfg.hist.grid_how_to_guide()
+        elif is_dtype(dtp, DateTime()):
             df, timeunit, miss_pct = data
-            fig = dt_line_viz(df, col, timeunit, yscale, plot_width, plot_height, False, miss_pct)
+            fig = dt_line_viz(
+                df, col, timeunit, cfg.line.yscale, plot_width, plot_height, False, miss_pct
+            )
         fig.frame_height = plot_height
         titles.append(fig.title.text)
         fig.title.text = ""
         figs.append(fig)
 
+    if cfg.insight.enable and cfg.stats.enable:
+        toggle_content = "Stats and Insights"
+    elif cfg.stats.enable:
+        toggle_content = "Stats"
+    else:
+        toggle_content = "Insights"
     return {
         "layout": figs,
         "meta": titles,
-        "tabledata": format_ov_stats(itmdt["stats"]),
-        "overview_insights": itmdt["overview_insights"],
-        "column_insights": itmdt["column_insights"],
+        "tabledata": format_ov_stats(itmdt["stats"]) if cfg.stats.enable else None,
+        "overview_insights": itmdt["overview_insights"] if cfg.insight.enable else None,
+        "column_insights": itmdt["column_insights"] if cfg.insight.enable else None,
         "container_width": plot_width * 3,
+        "toggle_content": toggle_content,
+        "how_to_guide": htgs,
     }
 
 
-def render_cat(
-    itmdt: Intermediate, yscale: str, plot_width: int, plot_height: int
-) -> Dict[str, Any]:
+def render_cat(itmdt: Intermediate, cfg: Config) -> Dict[str, Any]:
     """
-    Render plots from plot(df, x) when x is a categorical column
+    Create visualizations for plot(df, Nominal)
     """
-    # pylint: disable=too-many-locals
+    if cfg.plot.report:
+        plot_width = 280
+        plot_height = 250
+    else:
+        plot_width = cfg.plot.width if cfg.plot.width is not None else 450
+        plot_height = cfg.plot.height if cfg.plot.height is not None else 400
+
     tabs: List[Panel] = []
+    htgs: Dict[str, List[Tuple[str, str]]] = {}
     col, data = itmdt["col"], itmdt["data"]
     # overview, word length, and charater level statistcs
-    stats, len_stats, letter_stats = (
-        data["stats"],
-        data["len_stats"],
-        data["letter_stats"],
-    )
-    # number of present (not null) rows, and total rows
-    nrows, nuniq = data["nrows"], data["nuniq"]
-    # bar chart and pie chart of the categorical values
-    bar_data, pie = data["bar"].to_frame(), data["pie"].to_frame()
-    fig = bar_viz(
-        bar_data,
-        nuniq,
-        nrows,
-        col,
-        yscale,
-        plot_width,
-        plot_height,
-        True,
-    )
-    tabs.append(Panel(child=row(fig), title="Bar Chart"))
-    tabs.append(pie_viz(pie, nrows, col, plot_width, plot_height))
-    # word counts and total number of words for the wordcloud and word frequencies bar chart
-    word_cnts, nwords, = (
-        data["word_cnts"],
-        data["nwords"],
-    )
-    if nwords > 0:
-        tabs.append(wordcloud_viz(word_cnts, plot_width, plot_height))
-        tabs.append(wordfreq_viz(word_cnts, nwords, plot_width, plot_height, True))
-    # word length histogram
-    length_dist = hist_viz(
-        data["len_hist"], nrows, "Word Length", yscale, plot_width, plot_height, True
-    )
-    tabs.append(Panel(child=row(length_dist), title="Word Length"))
+    if cfg.stats.enable:
+        stats, len_stats, letter_stats = (
+            data["stats"],
+            data["len_stats"],
+            data["letter_stats"],
+        )
+    if cfg.bar.enable:
+        fig = bar_viz(
+            data["bar"].to_frame(),
+            data["nuniq"],
+            data["nrows"],
+            col,
+            cfg.bar.yscale,
+            plot_width,
+            plot_height,
+            True,
+        )
+        tabs.append(Panel(child=row(fig), title="Bar Chart"))
+        htgs["Bar Chart"] = cfg.bar.how_to_guide(plot_height, plot_width)
+    if cfg.pie.enable:
+        pie = data["pie"].to_frame()
+        tabs.append(pie_viz(pie, data["nrows"], col, plot_width, plot_height))
+        htgs["Pie Chart"] = cfg.pie.how_to_guide(plot_height, plot_width)
+    if cfg.wordcloud.enable:
+        if data["nuniq_words_cloud"] > 0:
+            tabs.append(wordcloud_viz(data["word_cnts_cloud"], plot_width, plot_height))
+            htgs["Word Cloud"] = cfg.wordcloud.how_to_guide(plot_height, plot_width)
+    if cfg.wordfreq.enable:
+        if data["nwords_freq"] > 0:
+            tabs.append(
+                wordfreq_viz(
+                    data["word_cnts_freq"],
+                    data["nwords_freq"],
+                    plot_width,
+                    plot_height,
+                    True,
+                )
+            )
+            htgs["Word Frequency"] = cfg.wordfreq.how_to_guide(plot_height, plot_width)
+    if cfg.wordlen.enable:
+        length_dist = hist_viz(
+            data["len_hist"],
+            data["nrows"],
+            "Word Length",
+            cfg.wordlen.yscale,
+            plot_width,
+            plot_height,
+            True,
+        )
+        tabs.append(Panel(child=row(length_dist), title="Word Length"))
+        htgs["Word Length"] = cfg.wordlen.how_to_guide(plot_height, plot_width)
 
     # panel.child.children[0] is a figure
     for panel in tabs:
         panel.child.children[0].frame_width = int(plot_width * 0.9)
 
     return {
-        "tabledata": format_cat_stats(stats, len_stats, letter_stats),
-        "insights": nom_insights(data, col),
+        "tabledata": format_cat_stats(stats, len_stats, letter_stats) if cfg.stats.enable else [],
+        "insights": nom_insights(data, col, cfg) if cfg.insight.enable else [],
         "layout": [panel.child.children[0] for panel in tabs],
         "meta": [tab.title for tab in tabs],
         "container_width": plot_width + 110,
+        "how_to_guide": htgs,
     }
 
 
-def nom_insights(data: Dict[str, Any], col: str) -> Dict[str, List[str]]:
+def nom_insights(data: Dict[str, Any], col: str, cfg: Config) -> Dict[str, List[str]]:
     """
-    Format the insights for plot(df, Nominal())
+    Format the insights for plot(df, Nominal)
     """
-    # pylint: disable=line-too-long
+    # pylint: disable=too-many-branches
     # insight dictionary, with a list associated with each plot
     ins: Dict[str, List[str]] = {
         "Stats": [],
@@ -1536,120 +1574,132 @@ def nom_insights(data: Dict[str, Any], col: str) -> Dict[str, List[str]]:
         "Word Length": [],
     }
 
-    ## if cfg.insight.constant_enable:
-    if data["nuniq"] == 1:
-        ins["Stats"].append(f"{col} has a constant value")
-
-    ## if cfg.insight.high_cardinality_enable:
-    if data["nuniq"] > 50:  ## cfg.insght.high_cardinality_threshold
-        nuniq = data["nuniq"]
-        ins["Stats"].append(f"{col} has a high cardinality: {nuniq} distinct values")
-
-    ## if cfg.insight.missing_enable:
-    pmiss = round((data["nrows"] - data["stats"]["npres"]) / data["nrows"] * 100, 2)
-    if pmiss > 1:  ## cfg.insight.missing_threshold
-        nmiss = data["nrows"] - data["stats"]["npres"]
-        ins["Stats"].append(f"{col} has {nmiss} ({pmiss}%) missing values")
-
-    ## if cfg.insight.constant_length_enable:
-    if data["stats"]["nuniq"] == data["stats"]["npres"]:
-        ins["Stats"].append(f"{col} has all distinct values")
-
-    ## if cfg.insight.evenness_enable:
-    if data["chisq"][1] > 0.999:  ## cfg.insight.uniform_threshold
-        ins["Bar Chart"].append(f"{col} is relatively evenly distributed")
-
-    ## if cfg.insight.outstanding_no1_enable
-    factor = data["bar"][0] / data["bar"][1] if len(data["bar"]) > 1 else 0
-    if factor > 1.5:
-        val1, val2 = data["bar"].index[0], data["bar"].index[1]
-        ins["Bar Chart"].append(
-            f"The largest value ({val1}) is over {factor} times larger than the second largest value ({val2})"
-        )
-
-    ## if cfg.insight.attribution_enable
-    if data["pie"][:2].sum() / data["nrows"] > 0.5 and len(data["pie"]) >= 2:
-        vals = ", ".join(str(data["pie"].index[i]) for i in range(2))
-        ins["Pie Chart"].append(f"The top 2 categories ({vals}) take over 50%")
-
-    ## if cfg.insight.high_word_cardinlaity_enable
-    if data["nwords"] > 1000:
-        nwords = data["nwords"]
-        ins["Word Cloud"].append(f"{col} contains many words: {nwords} words")
-
-    ## if cfg.insight.outstanding_no1_word_enable
-    factor = data["word_cnts"][0] / data["word_cnts"][1] if len(data["word_cnts"]) > 1 else 0
-    if factor > 1.5:
-        val1, val2 = data["word_cnts"].index[0], data["word_cnts"].index[1]
-        ins["Word Frequencies"].append(
-            f"The largest value ({val1}) is over {factor} times larger than the second largest value ({val2})"
+    if cfg.stats.enable:
+        if data["nuniq"] == cfg.insight.constant__threshold:
+            ins["Stats"].append(f"{col} has a constant value")
+        if data["nuniq"] > cfg.insight.high_cardinality__threshold:
+            nuniq = data["nuniq"]
+            ins["Stats"].append(f"{col} has a high cardinality: {nuniq} distinct values")
+        pmiss = round((data["nrows"] - data["stats"]["npres"]) / data["nrows"] * 100, 2)
+        if pmiss > cfg.insight.missing__threshold:
+            nmiss = data["nrows"] - data["stats"]["npres"]
+            ins["Stats"].append(f"{col} has {nmiss} ({pmiss}%) missing values")
+        if data["stats"]["nuniq"] == data["stats"]["npres"]:
+            ins["Stats"].append(f"{col} has all distinct values")
+
+    if cfg.bar.enable:
+        if data["chisq"][1] > cfg.insight.uniform__threshold:
+            ins["Bar Chart"].append(f"{col} is relatively evenly distributed")
+        factor = round(data["bar"][0] / data["bar"][1], 2) if len(data["bar"]) > 1 else 0
+        if factor > cfg.insight.outstanding_no1__threshold:
+            val1, val2 = data["bar"].index[0], data["bar"].index[1]
+            ins["Bar Chart"].append(
+                f"""The largest value ({val1}) is over {factor} times larger than the second
+                largest value ({val2})"""
+            )
+    if cfg.pie.enable:
+        if (
+            data["pie"][:2].sum() / data["nrows"] > cfg.insight.attribution__threshold
+            and len(data["pie"]) >= 2
+        ):
+            vals = ", ".join(str(data["pie"].index[i]) for i in range(2))
+            ins["Pie Chart"].append(f"The top 2 categories ({vals}) take over {0.5*100}%")
+    if cfg.wordcloud.enable:
+        if data["nuniq_words_cloud"] > cfg.insight.high_word_cardinality__threshold:
+            nwords = data["nuniq_words_cloud"]
+            ins["Word Cloud"].append(f"{col} contains many words: {nwords} words")
+    if cfg.wordfreq.enable:
+        factor = (
+            round(data["word_cnts_freq"][0] / data["word_cnts_freq"][1], 2)
+            if len(data["word_cnts_freq"]) > 1
+            else 0
         )
-
-    ## if cfg.insight.constant_word_length_enable
-    if data["len_stats"]["Minimum"] == data["len_stats"]["Maximum"]:
-        ins["Word Frequencies"].append(f"{col} has words of constant length")
+        if factor > cfg.insight.outstanding_no1_word__threshold:
+            val1, val2 = (
+                data["word_cnts_freq"].index[0],
+                data["word_cnts_freq"].index[1],
+            )
+            ins["Word Frequencies"].append(
+                f"""The largest value ({val1}) is over {factor} times larger than the second
+                largest value ({val2})"""
+            )
+        if data["len_stats"]["Minimum"] == data["len_stats"]["Maximum"]:
+            ins["Word Frequencies"].append(f"{col} has words of constant length")
 
     return ins
 
 
-def render_num(
-    itmdt: Intermediate, yscale: str, plot_width: int, plot_height: int
-) -> Dict[str, Any]:
-    # pylint: disable=too-many-locals
+def render_num(itmdt: Intermediate, cfg: Config) -> Dict[str, Any]:
     """
-    Render plots from plot(df, x) when x is a numerical column
+    Create visualizations for plot(df, Continuous)
     """
-    col, data = itmdt["col"], itmdt["data"]
+    # pylint: disable=too-many-locals
+    if cfg.plot.report:
+        plot_width = 280
+        plot_height = 250
+    else:
+        plot_width = cfg.plot.width if cfg.plot.width is not None else 450
+        plot_height = cfg.plot.height if cfg.plot.height is not None else 400
 
+    col, data = itmdt["col"], itmdt["data"]
     tabs: List[Panel] = []
-    fig = hist_viz(
-        data["hist"],
-        data["nrows"],
-        col,
-        yscale,
-        plot_width,
-        plot_height,
-        True,
-    )
-    tabs.append(Panel(child=row(fig), title="Histogram"))
-    # kde and q-q normal
-    if data["kde"] is not None:
-        dens, kde = data["dens"], data["kde"]
-        tabs.append(kde_viz(dens, kde, col, yscale, plot_width, plot_height))
-    if data["qntls"].any():
-        qntls, mean, std = data["qntls"], data["mean"], data["std"]
-        tabs.append(qqnorm_viz(qntls, mean, std, col, plot_width, plot_height))
-
-    # box plot
-    box_data = {
-        "grp": col,
-        "q1": data["qrtl1"],
-        "q2": data["qrtl2"],
-        "q3": data["qrtl3"],
-        "lw": data["lw"],
-        "uw": data["uw"],
-        "otlrs": [data["otlrs"]],
-    }
-    df = pd.DataFrame(box_data, index=[0])
-    tabs.append(box_viz(df, col, plot_width, plot_height))
+    htgs: Dict[str, List[Tuple[str, str]]] = {}
+
+    if cfg.hist.enable:
+        fig = hist_viz(
+            data["hist"],
+            data["nrows"],
+            col,
+            cfg.hist.yscale,
+            plot_width,
+            plot_height,
+            True,
+        )
+        tabs.append(Panel(child=row(fig), title="Histogram"))
+        htgs["Histogram"] = cfg.hist.how_to_guide(plot_height, plot_width)
+    if cfg.kde.enable:
+        if data["kde"] is not None:
+            dens, kde = data["dens"], data["kde"]
+            tabs.append(kde_viz(dens, kde, col, cfg.kde.yscale, plot_width, plot_height))
+            htgs["KDE Plot"] = cfg.kde.how_to_guide(plot_height, plot_width)
+    if cfg.qqnorm.enable:
+        if data["qntls"].any():
+            qntls, mean, std = data["qntls"], data["mean"], data["std"]
+            tabs.append(qqnorm_viz(qntls, mean, std, col, plot_width, plot_height))
+            htgs["Normal Q-Q Plot"] = cfg.qqnorm.how_to_guide(plot_height, plot_width)
+    if cfg.box.enable:
+        box_data = {
+            "grp": col,
+            "q1": data["qrtl1"],
+            "q2": data["qrtl2"],
+            "q3": data["qrtl3"],
+            "lw": data["lw"],
+            "uw": data["uw"],
+            "otlrs": [data["otlrs"]],
+        }
+        df = pd.DataFrame(box_data, index=[0])
+        tabs.append(box_viz(df, col, plot_width, plot_height))
+        htgs["Box Plot"] = cfg.box.univar_how_to_guide(plot_height, plot_width)
 
     # panel.child.children[0] is a figure
     for panel in tabs:
         panel.child.children[0].frame_width = int(plot_width * 0.9)
 
     return {
-        "tabledata": format_num_stats(data),
-        "insights": cont_insights(data, col),
+        "tabledata": format_num_stats(data) if cfg.stats.enable else [],
+        "insights": cont_insights(data, col, cfg) if cfg.insight.enable else [],
         "layout": [panel.child for panel in tabs],
         "meta": [tab.title for tab in tabs],
         "container_width": plot_width + 110,
+        "how_to_guide": htgs,
     }
 
 
-def cont_insights(data: Dict[str, Any], col: str) -> Dict[str, List[str]]:
+def cont_insights(data: Dict[str, Any], col: str, cfg: Config) -> Dict[str, List[str]]:
     """
-    Format the insights for plot(df, Continuous())
+    Format the insights for plot(df, Continuous)
     """
+    # pylint: disable=too-many-branches
     # insight dictionary with a list associated with each plot
     ins: Dict[str, List[str]] = {
         "Stats": [],
@@ -1659,72 +1709,65 @@ def cont_insights(data: Dict[str, Any], col: str) -> Dict[str, List[str]]:
         "Box Plot": [],
     }
 
-    ## if cfg.insight.infinity_enable:
-    pinf = round((data["npres"] - data["nreals"]) / data["nrows"] * 100, 2)
-    if pinf > 1:  ## cfg.insight.infinity_threshold
-        ninf = data["npres"] - data["nreals"]
-        ins["Stats"].append(f"{col} has {ninf} ({pinf}%) infinite values")
-
-    ## if cfg.insight.missing_enable:
-    pmiss = round((data["nrows"] - data["npres"]) / data["nrows"] * 100, 2)
-    if pmiss > 1:  ## cfg.insight.missing_threshold
-        nmiss = data["nrows"] - data["npres"]
-        ins["Stats"].append(f"{col} has {nmiss} ({pmiss}%) missing values")
-
-    ## if cfg.insight.negatives_enable:
-    pneg = round(data["nneg"] / data["nrows"] * 100, 2)
-    if pneg > 1:  ## cfg.insight.negatives_threshold
-        nneg = data["nneg"]
-        ins["Stats"].append(f"{col} has {nneg} ({pneg}%) negatives")
-
-    ## if cfg.insight.zeros_enable:
-    pzero = round(data["nzero"] / data["nrows"] * 100, 2)
-    if pzero > 5:  ## cfg.insight.zeros_threshold
-        nzero = data["nzero"]
-        ins["Stats"].append(f"{col} has {nzero} ({pzero}%) zeros")
-
-    ## if cfg.insight.normal_enable:
-    if data["norm"][1] > 0.99:
-        ins["Histogram"].append(f"{col} is normally distributed")
-
-    ## if cfg.insight.uniform_enable:
-    if data["chisq"][1] > 0.999:  ## cfg.insight.uniform_threshold
-        ins["Histogram"].append(f"{col} is uniformly distributed")
-
-    ## if cfg.insight.skewed_enable:
-    skw = np.round(data["skew"], 4)
-    if skw >= 20:  ## cfg.insight.skewed_threshold
-        ins["Histogram"].append(f"{col} is skewed right (\u03B31 = {skw})")
-    if skw <= -20:  ## cfg.insight.skewed_threshold
-        ins["Histogram"].append(f"{col} is skewed left (\u03B31 = {skw})")
-
-    ## if cfg.insight.normal_enable:
-    if data["norm"][1] <= 0.01:
-        pval = data["norm"][1]
-        ins["Normal Q-Q Plot"].append(f"{col} is not normally distributed (p-value {pval})")
-
-    ## if cfg.insight.box_enable
-    if data["notlrs"] > 0:
-        notlrs = data["notlrs"]
-        ins["Box Plot"].append(f"{col} has {notlrs} outliers")
+    if cfg.stats.enable:
+        pinf = round((data["npres"] - data["nreals"]) / data["nrows"] * 100, 2)
+        if pinf > cfg.insight.infinity__threshold:
+            ninf = data["npres"] - data["nreals"]
+            ins["Stats"].append(f"{col} has {ninf} ({pinf}%) infinite values")
+        pmiss = round((data["nrows"] - data["npres"]) / data["nrows"] * 100, 2)
+        if pmiss > cfg.insight.missing__threshold:
+            nmiss = data["nrows"] - data["npres"]
+            ins["Stats"].append(f"{col} has {nmiss} ({pmiss}%) missing values")
+        pneg = round(data["nneg"] / data["nrows"] * 100, 2)
+        if pneg > cfg.insight.negatives__threshold:
+            nneg = data["nneg"]
+            ins["Stats"].append(f"{col} has {nneg} ({pneg}%) negatives")
+        pzero = round(data["nzero"] / data["nrows"] * 100, 2)
+        if pzero > cfg.insight.zeros__threshold:
+            nzero = data["nzero"]
+            ins["Stats"].append(f"{col} has {nzero} ({pzero}%) zeros")
+
+    if cfg.hist.enable:
+        if data["norm"][1] > cfg.insight.normal__threshold:
+            ins["Histogram"].append(f"{col} is normally distributed")
+        if data["chisq"][1] > cfg.insight.uniform__threshold:
+            ins["Histogram"].append(f"{col} is uniformly distributed")
+        skw = np.round(data["skew"], 4)
+        if skw >= cfg.insight.skewed__threshold:
+            ins["Histogram"].append(f"{col} is skewed right (\u03B31 = {skw})")
+        if skw <= -cfg.insight.skewed__threshold:
+            ins["Histogram"].append(f"{col} is skewed left (\u03B31 = {skw})")
+
+    if cfg.qqnorm.enable:
+        if data["norm"][1] <= 1 - cfg.insight.normal__threshold:
+            pval = data["norm"][1]
+            ins["Normal Q-Q Plot"].append(f"{col} is not normally distributed (p-value {pval})")
+
+    if cfg.box.enable:
+        if data["notlrs"] > cfg.insight.outlier__threshold:
+            notlrs = data["notlrs"]
+            ins["Box Plot"].append(f"{col} has {notlrs} outliers")
 
     return ins
 
 
-def render_dt(
-    itmdt: Intermediate, yscale: str, plot_width: int, plot_height: int
-) -> Dict[str, Any]:
+def render_dt(itmdt: Intermediate, cfg: Config) -> Dict[str, Any]:
     """
-    Render plots from plot(df, x) when x is a numerical column
+    Create visualizations for plot(df, DateTime)
     """
-    tabs: List[Panel] = []
-    df, timeunit, miss_pct = itmdt["line"]
-    fig = dt_line_viz(df, itmdt["col"], timeunit, yscale, plot_width, plot_height, True, miss_pct)
-    fig.frame_width = int(plot_width * 0.95)
-    tabs.append(Panel(child=fig, title="Line Chart"))
+    plot_width = cfg.plot.width if cfg.plot.width is not None else 450
+    plot_height = cfg.plot.height if cfg.plot.height is not None else 400
 
+    tabs: List[Panel] = []
+    if cfg.line.enable:
+        df, timeunit, miss_pct = itmdt["line"]
+        fig = dt_line_viz(
+            df, itmdt["col"], timeunit, cfg.line.yscale, plot_width, plot_height, True, miss_pct
+        )
+        fig.frame_width = int(plot_width * 0.95)
+        tabs.append(Panel(child=fig, title="Line Chart"))
     return {
-        "tabledata": stats_viz_dt(itmdt["data"]),
+        "tabledata": stats_viz_dt(itmdt["data"]) if cfg.stats.enable else [],
         "insights": None,
         "layout": [panel.child for panel in tabs],
         "meta": [tab.title for tab in tabs],
@@ -1732,87 +1775,102 @@ def render_dt(
     }
 
 
-def render_cat_num(
-    itmdt: Intermediate,
-    yscale: str,
-    plot_width: int,
-    plot_height: int,
-) -> Dict[str, Any]:
+def render_cat_num(itmdt: Intermediate, cfg: Config) -> Dict[str, Any]:
     """
-    Render plots from plot(df, x, y) when x is a categorical column
-    and y is a numerical column
+    Create visualizations for plot(df, Nominal, Continuous)
     """
+    plot_width = cfg.plot.width if cfg.plot.width is not None else 450
+    plot_height = cfg.plot.height if cfg.plot.height is not None else 400
+
     tabs: List[Panel] = []
+    htgs: Dict[str, List[Tuple[str, str]]] = {}
     data, x, y = itmdt["data"], itmdt["x"], itmdt["y"]
 
-    # box plot
-    df = data["box"].to_frame().reset_index()[: 5 * itmdt["ngroups"]]
-    df = df.pivot(index=itmdt["x"], columns="level_1", values=[0]).reset_index()
-    df.columns = df.columns.get_level_values(1)
-    df.columns = ["grp"] + list(df.columns[1:])
-    tabs.append(box_viz(df, x, plot_width, plot_height, y, data["ttl_grps"]))
-
-    # multiline plot
-    df = data["hist"].to_frame()[: itmdt["ngroups"]]
-    tabs.append(
-        line_viz(
-            df,
-            x,
-            y,
-            yscale,
-            plot_width,
-            plot_height,
-            data["ttl_grps"],
+    if cfg.box.enable:
+        # box plot
+        df = data["box"].to_frame().reset_index()[: 5 * cfg.box.ngroups]
+        df = df.pivot(index=x, columns="level_1", values=[0]).reset_index()
+        df.columns = df.columns.get_level_values(1)
+        df.columns = ["grp"] + list(df.columns[1:])
+        tabs.append(box_viz(df, x, plot_width, plot_height, y, data["ttl_grps"]))
+        htgs["Box Plot"] = cfg.box.nom_cont_how_to_guide(plot_height, plot_width)
+
+    if cfg.line.enable:
+        # multiline plot
+        df = data["hist"].to_frame()[: cfg.line.ngroups]
+        tabs.append(
+            line_viz(
+                df,
+                x,
+                y,
+                cfg.line.yscale,
+                plot_width,
+                plot_height,
+                data["ttl_grps"],
+            )
         )
-    )
+        htgs["Line Chart"] = cfg.line.nom_cont_how_to_guide(plot_height, plot_width)
     for panel in tabs:
         panel.child.children[0].frame_width = int(plot_width * 0.9)
     return {
         "layout": [panel.child for panel in tabs],
         "meta": [panel.title for panel in tabs],
         "container_width": plot_width + 170,
+        "how_to_guide": htgs,
     }
 
 
-def render_two_num(
-    itmdt: Intermediate,
-    plot_width: int,
-    plot_height: int,
-    tile_size: Optional[float] = None,
-) -> Dict[str, Any]:
+def render_two_num(itmdt: Intermediate, cfg: Config) -> Dict[str, Any]:
     """
-    Render plots from plot(df, x, y) when x and y are numerical columns
+    Create visualizations for plot(df, Continuous, Continuous)
     """
+    plot_width = cfg.plot.width if cfg.plot.width is not None else 450
+    plot_height = cfg.plot.height if cfg.plot.height is not None else 400
+
     tabs: List[Panel] = []
-    data = itmdt["data"]
-    # scatter plot
-    tabs.append(
-        scatter_viz(
-            data["scat"],
-            itmdt["x"],
-            itmdt["y"],
-            itmdt["spl_sz"],
-            plot_width,
-            plot_height,
+    htgs: Dict[str, List[Tuple[str, str]]] = {}
+    data, x, y = itmdt["data"], itmdt["x"], itmdt["y"]
+
+    if cfg.scatter.enable:
+        # scatter plot
+        tabs.append(
+            scatter_viz(
+                data["scat"],
+                x,
+                y,
+                cfg.scatter.sample_size,
+                plot_width,
+                plot_height,
+            )
         )
-    )
-    # hexbin plot
-    tabs.append(
-        hexbin_viz(
-            data["hex"],
-            itmdt["x"],
-            itmdt["y"],
-            plot_width,
-            plot_height,
-            tile_size,
+        htgs["Scatter Plot"] = cfg.scatter.how_to_guide(plot_height, plot_width)
+    tile_size = None
+    if cfg.hexbin.enable:
+        # hexbin plot
+        x_diff = data["hex"][x].max() - data["hex"][x].min()
+        tile_size = cfg.hexbin.tile_size if cfg.hexbin.tile_size != "auto" else x_diff / 25
+        aspect_scale = (data["hex"][y].max() - data["hex"][y].min()) / (x_diff + 1e-9)
+        tabs.append(
+            hexbin_viz(
+                data["hex"],
+                x,
+                y,
+                plot_width,
+                plot_height,
+                tile_size,
+                aspect_scale,
+            )
         )
-    )
-    # box plot
-    df = data["box"].to_frame().reset_index()
-    df = df.pivot(index="grp", columns="level_1", values=[0]).reset_index()
-    df.columns = df.columns.get_level_values(1)
-    df.columns = ["grp"] + list(df.columns[1:])
-    tabs.append(box_viz(df, itmdt["x"], plot_width, plot_height, itmdt["y"]))
+        htgs["Hexbin Plot"] = cfg.hexbin.how_to_guide(tile_size, plot_height, plot_width)
+    if cfg.box.enable:
+        # box plot
+        df = data["box"].to_frame().reset_index()
+        df = df.pivot(index="grp", columns="level_1", values=[0]).reset_index()
+        df.columns = df.columns.get_level_values(1)
+        df.columns = ["grp"] + list(df.columns[1:])
+        tabs.append(box_viz(df, x, plot_width, plot_height, y))
+        htgs["Box Plot"] = cfg.box.two_cont_how_to_guide(plot_height, plot_width)
+
     for panel in tabs:
         try:
             panel.child.frame_width = int(plot_width * 0.9)
@@ -1822,95 +1880,145 @@ def render_two_num(
         "layout": [panel.child for panel in tabs],
         "meta": [panel.title for panel in tabs],
         "container_width": plot_width + 80,
+        "how_to_guide": htgs,
     }
 
 
-def render_two_cat(
-    itmdt: Intermediate,
-    plot_width: int,
-    plot_height: int,
-) -> Dict[str, Any]:
+def render_two_cat(itmdt: Intermediate, cfg: Config) -> Dict[str, Any]:
     """
-    Render plots from plot(df, x, y) when x and y are categorical columns
+    Create visualizations for plot(df, Nominal, Nominal)
     """
+    # pylint: disable=too-many-locals
+    plot_width = cfg.plot.width if cfg.plot.width is not None else 972
+    plot_height = cfg.plot.height if cfg.plot.height is not None else 300
+
     tabs: List[Panel] = []
+    htgs: Dict[str, List[Tuple[str, str]]] = {}
     df = itmdt["data"].to_frame("cnt").reset_index()
     x, y = itmdt["x"], itmdt["y"]
-
-    # parse the dataframe to consist of the ngroups largest x groups
-    xgrps = df.groupby(x)["cnt"].sum()
-    x_lrgst = xgrps.nlargest(itmdt["ngroups"])
-    df = df[df[x].isin(x_lrgst.index)]
-    stats = {f"{x}_ttl": len(xgrps), f"{x}_shw": len(x_lrgst)}
-
-    # parse the dataframe to consist of the nsubgroups largest y groups
-    ygrps = df.groupby(y)["cnt"].sum()
-    y_lrgst = ygrps.nlargest(itmdt["nsubgroups"])
-    df = df[df[y].isin(y_lrgst.index)]
-    stats.update(zip((f"{y}_ttl", f"{y}_shw"), (len(ygrps), len(y_lrgst))))
-    df[[x, y]] = df[[x, y]].astype(str)
-
-    # final format
-    df = df.pivot_table(index=y, columns=x, values="cnt", fill_value=0, aggfunc="sum")
-    df = df.unstack().to_frame("cnt").reset_index()
+    xgrps, ygrps = df.groupby(x)["cnt"].sum(), df.groupby(y)["cnt"].sum()
+
+    if (
+        cfg.nested.enable
+        and cfg.stacked.enable
+        and cfg.heatmap.enable
+        and cfg.nested.ngroups == cfg.stacked.ngroups == cfg.heatmap.ngroups
+        and cfg.nested.nsubgroups == cfg.stacked.nsubgroups == cfg.heatmap.nsubgroups
+    ):
+        # parse the dataframe to consist of the ngroups largest x groups
+        # and the nsubgroups largest y groups
+        df, stats = parse_grps(df, cfg.nested.ngroups, cfg.nested.nsubgroups, x, y, xgrps, ygrps)
+        df_nest = df_stack = df_heat = df
+        stats_nest = stats_stack = stats_heat = stats
+    else:
+        if cfg.nested.enable:
+            df_nest, stats_nest = parse_grps(
+                df, cfg.nested.ngroups, cfg.nested.nsubgroups, x, y, xgrps, ygrps
+            )
+        if cfg.stacked.enable:
+            df_stack, stats_stack = parse_grps(
+                df, cfg.stacked.ngroups, cfg.stacked.nsubgroups, x, y, xgrps, ygrps
+            )
+        if cfg.heatmap.enable:
+            df_heat, stats_heat = parse_grps(
+                df, cfg.heatmap.ngroups, cfg.heatmap.nsubgroups, x, y, xgrps, ygrps
+            )
 
     # nested bar chart
-    tabs.append(nested_viz(df, x, y, stats, plot_width, plot_height))
+    if cfg.nested.enable:
+        tabs.append(nested_viz(df_nest, x, y, stats_nest, plot_width, plot_height))
+        htgs["Nested Bar Chart"] = cfg.nested.how_to_guide(x, y, plot_height, plot_width)
     # stacked bar chart
-    # wrangle the dataframe into a pivot table format
-    df2 = df.pivot(index=x, columns=y, values="cnt")
-    df2.index.name = None
-    # aggregate remaining groups into "Others"
-    df2["Others"] = xgrps - df2.sum(axis=1)
-    if df2["Others"].sum() < 1e-6:
-        df2 = df2.drop(columns=["Others"])
-    tabs.append(stacked_viz(df2, x, y, stats, plot_width, plot_height))
+    if cfg.stacked.enable:
+        # stacked bar chart
+        # wrangle the dataframe into a pivot table format
+        df_stack = df_stack.pivot(index=x, columns=y, values="cnt")
+        df_stack.index.name = None
+        # aggregate remaining groups into "Others"
+        df_stack["Others"] = xgrps - df_stack.sum(axis=1)
+        if df_stack["Others"].sum() < 1e-6:
+            df_stack = df_stack.drop(columns="Others")
+        tabs.append(stacked_viz(df_stack, x, y, stats_stack, plot_width, plot_height))
+        htgs["Stacked Bar Chart"] = cfg.stacked.how_to_guide(x, y, plot_height, plot_width)
     # heat map
-    tabs.append(heatmap_viz(df, x, y, stats, plot_width, plot_height))
+    if cfg.heatmap.enable:
+        tabs.append(heatmap_viz(df_heat, x, y, stats_heat, plot_width, plot_height))
+        htgs["Heat Map"] = cfg.heatmap.how_to_guide(x, y, plot_height, plot_width)
 
     return {
         "layout": [panel.child for panel in tabs],
         "meta": [panel.title for panel in tabs],
         "container_width": plot_width,
+        "how_to_guide": htgs,
     }
 
 
-def render_dt_num(
-    itmdt: Intermediate,
-    yscale: str,
-    plot_width: int,
-    plot_height: int,
-) -> Dict[str, Any]:
+def parse_grps(
+    df: pd.DataFrame,
+    ngroups: int,
+    nsubgroups: int,
+    x: str,
+    y: str,
+    xgrps: pd.DataFrame,
+    ygrps: pd.DataFrame,
+) -> Any:
+    """
+    Parse the data for nested bar chart, stacked bar chart, heat map
+    according to the given ngroups and nsubgroups
+    """
+    # pylint: disable=too-many-arguments
+
+    x_lrgst = xgrps.nlargest(ngroups)
+    df = df[df[x].isin(x_lrgst.index)]
+    stats = {f"{x}_ttl": len(xgrps), f"{x}_shw": len(x_lrgst)}
+    y_lrgst = ygrps.nlargest(nsubgroups)
+    df = df[df[y].isin(y_lrgst.index)]
+    stats.update(zip((f"{y}_ttl", f"{y}_shw"), (len(ygrps), len(y_lrgst))))
+    df[[x, y]] = df[[x, y]].astype(str)
+    # final format
+    df = df.pivot_table(index=y, columns=x, values="cnt", fill_value=0, aggfunc="sum")
+    df = df.unstack().to_frame("cnt").reset_index()
+
+    return df, stats
+
+
+def render_dt_num(itmdt: Intermediate, cfg: Config) -> Dict[str, Any]:
     """
     Render plots from plot(df, x, y) when x is dt and y is num
     """
+    plot_width = cfg.plot.width if cfg.plot.width is not None else 450
+    plot_height = cfg.plot.height if cfg.plot.height is not None else 400
+
     tabs: List[Panel] = []
-    linedf, timeunit = itmdt["linedata"]
-    tabs.append(
-        dt_line_viz(
-            linedf,
-            itmdt["x"],
-            timeunit,
-            yscale,
-            plot_width,
-            plot_height,
-            True,
-            y=itmdt["y"],
+    if cfg.line.enable:
+        linedf, timeunit = itmdt["linedata"]
+        tabs.append(
+            dt_line_viz(
+                linedf,
+                itmdt["x"],
+                timeunit,
+                cfg.line.yscale,
+                plot_width,
+                plot_height,
+                True,
+                y=itmdt["y"],
+            )
         )
-    )
-    boxdf, outx, outy, timeunit = itmdt["boxdata"]
-    tabs.append(
-        box_viz_dt(
-            boxdf,
-            outx,
-            outy,
-            itmdt["x"],
-            plot_width,
-            plot_height,
-            itmdt["y"],
-            timeunit=timeunit,
+    if cfg.box.enable:
+        boxdf, outx, outy, timeunit = itmdt["boxdata"]
+        tabs.append(
+            box_viz_dt(
+                boxdf,
+                outx,
+                outy,
+                itmdt["x"],
+                plot_width,
+                plot_height,
+                itmdt["y"],
+                timeunit=timeunit,
+            )
         )
-    )
+
     return {
         "layout": [panel.child for panel in tabs],
         "meta": [panel.title for panel in tabs],
@@ -1918,33 +2026,42 @@ def render_dt_num(
     }
 
 
-def render_dt_cat(
-    itmdt: Intermediate,
-    yscale: str,
-    plot_width: int,
-    plot_height: int,
-) -> Dict[str, Any]:
+def render_dt_cat(itmdt: Intermediate, cfg: Config) -> Dict[str, Any]:
     """
     Render plots from plot(df, x, y) when x is dt and y is num
     """
+    plot_width = cfg.plot.width if cfg.plot.width is not None else 972
+    plot_height = cfg.plot.height if cfg.plot.height is not None else 400
+
     tabs: List[Panel] = []
-    data, grp_cnt_stats, timeunit = itmdt["linedata"]
-    tabs.append(
-        dt_multiline_viz(
-            data,
-            itmdt["x"],
-            itmdt["y"],
-            timeunit,
-            yscale,
-            plot_width,
-            plot_height,
-            grp_cnt_stats,
+    if cfg.line.enable:
+        data, grp_cnt_stats, timeunit = itmdt["linedata"]
+        tabs.append(
+            dt_multiline_viz(
+                data,
+                itmdt["x"],
+                itmdt["y"],
+                timeunit,
+                cfg.line.yscale,
+                plot_width,
+                plot_height,
+                grp_cnt_stats,
+            )
         )
-    )
-    df, grp_cnt_stats, timeunit = itmdt["stackdata"]
-    tabs.append(
-        stacked_viz(df, itmdt["x"], itmdt["y"], grp_cnt_stats, plot_width, plot_height, timeunit)
-    )
+    if cfg.stacked.enable:
+        df, grp_cnt_stats, timeunit = itmdt["stackdata"]
+        tabs.append(
+            stacked_viz(
+                df,
+                itmdt["x"],
+                itmdt["y"],
+                grp_cnt_stats,
+                plot_width,
+                plot_height,
+                timeunit,
+            )
+        )
+
     return {
         "layout": [panel.child for panel in tabs],
         "meta": [panel.title for panel in tabs],
@@ -1952,15 +2069,13 @@ def render_dt_cat(
     }
 
 
-def render_dt_num_cat(
-    itmdt: Intermediate,
-    yscale: str,
-    plot_width: int,
-    plot_height: int,
-) -> Dict[str, Any]:
+def render_dt_num_cat(itmdt: Intermediate, cfg: Config) -> Dict[str, Any]:
     """
     Render plots from plot(df, x, y) when x is dt and y is num
     """
+    plot_width = cfg.plot.width if cfg.plot.width is not None else 972
+    plot_height = cfg.plot.height if cfg.plot.height is not None else 400
+
     tabs: List[Panel] = []
     data, grp_cnt_stats, timeunit = itmdt["data"]
     tabs.append(
@@ -1969,7 +2084,7 @@ def render_dt_num_cat(
             itmdt["x"],
             itmdt["y"],
             timeunit,
-            yscale,
+            cfg.line.yscale,
             plot_width,
             plot_height,
             grp_cnt_stats,
@@ -1984,16 +2099,7 @@ def render_dt_num_cat(
     }
 
 
-def render(
-    itmdt: Intermediate,
-    yscale: str = "linear",
-    tile_size: Optional[float] = None,
-    plot_width_sml: int = 324,
-    plot_height_sml: int = 300,
-    plot_width_lrg: int = 450,
-    plot_height_lrg: int = 400,
-    plot_width_wide: int = 972,
-) -> Union[LayoutDOM, Dict[str, Any]]:
+def render(itmdt: Intermediate, cfg: Config) -> Union[LayoutDOM, Dict[str, Any]]:
     """
     Render a basic plot
 
@@ -2001,44 +2107,29 @@ def render(
     ----------
     itmdt
         The Intermediate containing results from the compute function.
-    yscale: str, default "linear"
-        The scale to show on the y axis. Can be "linear" or "log".
-    tile_size
-        Size of the tile for the hexbin plot. Measured from the middle
-        of a hexagon to its left or right corner.
-    plot_width_small: int, default 324
-        The width of the small plots
-    plot_height_small: int, default 300
-        The height of the small plots
-    plot_width_large: int, default 450
-        The width of the large plots
-    plot_height_large: int, default 400
-        The height of the large plots
-    plot_width_large: int, default 972
-        The width of the large plots
-    plot_width_wide: int, default 972
-        The width of the wide plots
+    cfg
+        Config instance
     """
-    # pylint: disable=too-many-arguments
+
     if itmdt.visual_type == "distribution_grid":
-        visual_elem = render_distribution_grid(itmdt, yscale, plot_width_sml, plot_height_sml)
+        visual_elem = render_distribution_grid(itmdt, cfg)
     elif itmdt.visual_type == "categorical_column":
-        visual_elem = render_cat(itmdt, yscale, plot_width_lrg, plot_height_lrg)
+        visual_elem = render_cat(itmdt, cfg)
     elif itmdt.visual_type == "numerical_column":
-        visual_elem = render_num(itmdt, yscale, plot_width_lrg, plot_height_lrg)
+        visual_elem = render_num(itmdt, cfg)
     elif itmdt.visual_type == "datetime_column":
-        visual_elem = render_dt(itmdt, yscale, plot_width_lrg, plot_height_lrg)
+        visual_elem = render_dt(itmdt, cfg)
     elif itmdt.visual_type == "cat_and_num_cols":
-        visual_elem = render_cat_num(itmdt, yscale, plot_width_lrg, plot_height_lrg)
+        visual_elem = render_cat_num(itmdt, cfg)
     elif itmdt.visual_type == "two_num_cols":
-        visual_elem = render_two_num(itmdt, plot_width_lrg, plot_height_lrg, tile_size)
+        visual_elem = render_two_num(itmdt, cfg)
     elif itmdt.visual_type == "two_cat_cols":
-        visual_elem = render_two_cat(itmdt, plot_width_wide, plot_height_sml)
+        visual_elem = render_two_cat(itmdt, cfg)
     elif itmdt.visual_type == "dt_and_num_cols":
-        visual_elem = render_dt_num(itmdt, yscale, plot_width_lrg, plot_height_lrg)
+        visual_elem = render_dt_num(itmdt, cfg)
     elif itmdt.visual_type == "dt_and_cat_cols":
-        visual_elem = render_dt_cat(itmdt, yscale, plot_width_wide, plot_height_lrg)
+        visual_elem = render_dt_cat(itmdt, cfg)
     elif itmdt.visual_type == "dt_cat_num_cols":
-        visual_elem = render_dt_num_cat(itmdt, yscale, plot_width_wide, plot_height_lrg)
+        visual_elem = render_dt_num_cat(itmdt, cfg)
 
     return visual_elem
diff --git a/dataprep/eda/dtypes.py b/dataprep/eda/dtypes.py
index 61e7f14d1..40e061923 100644
--- a/dataprep/eda/dtypes.py
+++ b/dataprep/eda/dtypes.py
@@ -4,10 +4,9 @@
 from collections import defaultdict
 from typing import Any, DefaultDict, Dict, List, Optional, Tuple, Type, Union
 
-
+import dask.dataframe as dd
 import numpy as np
 import pandas as pd
-import dask.dataframe as dd
 
 from ..errors import UnreachableError
 
diff --git a/dataprep/eda/intermediate.py b/dataprep/eda/intermediate.py
index 1d4091435..ca6c12147 100644
--- a/dataprep/eda/intermediate.py
+++ b/dataprep/eda/intermediate.py
@@ -1,7 +1,7 @@
 """
 Intermediate class
 """
-from typing import Any, Tuple, Union, Dict
+from typing import Any, Dict, Tuple, Union
 
 import pandas as pd
 
diff --git a/dataprep/eda/missing/__init__.py b/dataprep/eda/missing/__init__.py
index 8b4f6ad9f..51fd4a3c2 100644
--- a/dataprep/eda/missing/__init__.py
+++ b/dataprep/eda/missing/__init__.py
@@ -2,16 +2,17 @@
     This module implements the plot_missing(df) function
 """
 
-from typing import Optional, Union
+from typing import Any, Dict, List, Optional, Union
 
 import dask.dataframe as dd
 import pandas as pd
 
+from ..configs import Config
+from ..container import Container
 from ..dtypes import DTypeDef
 from ..progress_bar import ProgressBar
 from .compute import compute_missing
 from .render import render_missing
-from ..container import Container
 
 __all__ = ["render_missing", "compute_missing", "plot_missing"]
 
@@ -21,8 +22,8 @@ def plot_missing(
     x: Optional[str] = None,
     y: Optional[str] = None,
     *,
-    bins: int = 20,
-    ndist_sample: int = 100,
+    config: Optional[Dict[str, Any]] = None,
+    display: Optional[List[str]] = None,
     dtype: Optional[DTypeDef] = None,
     progress: bool = True,
 ) -> Container:
@@ -39,11 +40,13 @@ def plot_missing(
         a valid column name of the data frame.
     y
         a valid column name of the data frame.
-    bins
-        The number of rows in the figure.
-    ndist_sample
-        The number of sample points.
-    wdtype: str or DType or dict of str or dict of DType, default None
+    config
+        A dictionary for configuring the visualizations.
+        E.g. config={"spectrum.bins": 20}
+    display
+        A list containing the names of the visualizations to display
+        E.g. display=["Stats", "Spectrum"]
+    dtype: str or DType or dict of str or dict of DType, default None
         Specify Data Types for designated column or all columns.
         E.g.  dtype = {"a": Continuous, "b": "Nominal"} or
         dtype = {"a": Continuous(), "b": "nominal"}
@@ -59,9 +62,11 @@ def plot_missing(
     >>> plot_missing(df, "HDI_for_year")
     >>> plot_missing(df, "HDI_for_year", "population")
     """
+    cfg = Config.from_dict(display, config)
 
     with ProgressBar(minimum=1, disable=not progress):
-        itmdt = compute_missing(df, x, y, dtype=dtype, bins=bins, ndist_sample=ndist_sample)
-    to_render = render_missing(itmdt)
+        itmdt = compute_missing(df, x, y, cfg=cfg, dtype=dtype)
+
+    to_render = render_missing(itmdt, cfg)
 
-    return Container(to_render, itmdt.visual_type)
+    return Container(to_render, itmdt.visual_type, cfg)
diff --git a/dataprep/eda/missing/compute/__init__.py b/dataprep/eda/missing/compute/__init__.py
index 13c58337e..bb2f06177 100644
--- a/dataprep/eda/missing/compute/__init__.py
+++ b/dataprep/eda/missing/compute/__init__.py
@@ -1,9 +1,10 @@
 """This module implements the plot_missing(df) function's
 calculating intermediate part
 """
-from typing import Optional, cast
+from typing import Optional, cast, List, Any, Dict, Union
 from warnings import catch_warnings, filterwarnings
 
+from ...configs import Config
 from ...data_array import DataArray, DataFrame
 from ...dtypes import DTypeDef, string_dtype_to_object
 from ...intermediate import Intermediate
@@ -14,15 +15,17 @@
 __all__ = ["compute_missing"]
 
 
-def compute_missing(  # pylint: disable=too-many-arguments
+def compute_missing(
     df: DataFrame,
     x: Optional[str] = None,
     y: Optional[str] = None,
     *,
-    bins: int = 30,
-    ndist_sample: int = 100,
+    cfg: Union[Config, Dict[str, Any], None] = None,
+    display: Optional[List[str]] = None,
     dtype: Optional[DTypeDef] = None,
 ) -> Intermediate:
+    # pylint: disable=too-many-arguments
+
     """This function is designed to deal with missing values
     There are three functions: plot_missing(df), plot_missing(df, x)
     plot_missing(df, x, y)
@@ -35,10 +38,15 @@ def compute_missing(  # pylint: disable=too-many-arguments
         a valid column name of the data frame
     y
         a valid column name of the data frame
-    bins
-        The number of rows in the figure
-    ndist_sample
-        The number of sample points
+    cfg: Union[Config, Dict[str, Any], None], default None
+        When a user call plot_missing(), the created Config object will be passed to
+        compute_missing().
+        When a user call compute_missing() directly, if he/she wants to customize the output,
+        cfg is a dictionary for configuring. If not, cfg is None and
+        default values will be used for parameters.
+    display: Optional[List[str]], default None
+        A list containing the names of the visualizations to display. Only exist when
+        a user call compute_missing() directly and want to customize the output
     dtype: str or DType or dict of str or dict of DType, default None
         Specify Data Types for designated column or all columns.
         E.g.  dtype = {"a": Continuous, "b": "Nominal"} or
@@ -57,19 +65,16 @@ def compute_missing(  # pylint: disable=too-many-arguments
     df = DataArray(df)
 
     # pylint: disable=no-else-raise
+    if isinstance(cfg, dict):
+        cfg = Config.from_dict(display, cfg)
+    elif not cfg:
+        cfg = Config()
     if x is None and y is not None:
         raise ValueError("x cannot be None while y has value")
     elif x is not None and y is None:
-        ret = compute_missing_univariate(df, dtype=dtype, x=x, bins=bins)
+        ret = compute_missing_univariate(df, x, cfg, dtype)
     elif x is not None and y is not None:
-        ret = compute_missing_bivariate(
-            df,
-            dtype=dtype,
-            x=x,
-            y=y,
-            bins=bins,
-            ndist_sample=ndist_sample,
-        )
+        ret = compute_missing_bivariate(df, x, y, cfg, dtype)
     else:
         # supress divide by 0 error due to heatmap
         with catch_warnings():
@@ -78,6 +83,6 @@ def compute_missing(  # pylint: disable=too-many-arguments
                 "invalid value encountered in true_divide",
                 category=RuntimeWarning,
             )
-            ret = compute_missing_nullivariate(df, bins=bins)
+            ret = compute_missing_nullivariate(df, cfg)
 
     return cast(Intermediate, ret)
diff --git a/dataprep/eda/missing/compute/bivariate.py b/dataprep/eda/missing/compute/bivariate.py
index fca2ff0c6..49d81cccd 100644
--- a/dataprep/eda/missing/compute/bivariate.py
+++ b/dataprep/eda/missing/compute/bivariate.py
@@ -1,49 +1,43 @@
 """This module implements the plot_missing(df) function's
 calculating intermediate part."""
 
-from typing import List, Optional, Generator, Any
+from typing import Any, Generator, List, Optional
 
 import dask.dataframe as dd
 import numpy as np
 import pandas as pd
 from scipy.stats import rv_histogram
 
+from ...configs import Config
 from ...data_array import DataArray
-from ...dtypes import (
-    Continuous,
-    DTypeDef,
-    detect_dtype,
-    is_dtype,
-)
+from ...dtypes import Continuous, DTypeDef, Nominal, detect_dtype, is_dtype
 from ...intermediate import ColumnsMetadata, Intermediate
 from ...staged import staged
 from .common import LABELS, histogram
 
 
-def _compute_missing_bivariate(  # pylint: disable=too-many-locals
+def _compute_missing_bivariate(  # pylint: disable=too-many-locals,too-many-statements
     df: DataArray,
     x: str,
     y: str,
-    bins: int,
-    ndist_sample: int,
+    cfg: Config,
     dtype: Optional[DTypeDef] = None,
 ) -> Generator[Any, Any, Intermediate]:
-    # pylint: disable=too-many-arguments
     """Calculate the distribution change on another column y when
     the missing values in x is dropped."""
 
-    xloc = df.columns.get_loc(x)
-    yloc = df.columns.get_loc(y)
+    xloc, yloc = df.columns.get_loc(x), df.columns.get_loc(y)
 
     col0 = df.values[~df.nulls[:, yloc], yloc].astype(df.dtypes[y])
     col1 = df.values[~(df.nulls[:, xloc] | df.nulls[:, yloc]), yloc].astype(df.dtypes[y])
 
     minimum, maximum = col0.min(), col0.max()
+    bins = cfg.bar.bars if is_dtype(detect_dtype(df.frame[y], dtype), Nominal()) else cfg.hist.bins
 
-    hists = [histogram(col, dtype=dtype, bins=bins, return_edges=True) for col in [col0, col1]]
+    hists = [histogram(col, bins, return_edges=True, dtype=dtype) for col in [col0, col1]]
 
     quantiles = None
-    if is_dtype(detect_dtype(df.frame[y], dtype), Continuous()):
+    if is_dtype(detect_dtype(df.frame[y], dtype), Continuous()) and cfg.box.enable:
         quantiles = [
             dd.from_dask_array(col).quantile([0, 0.25, 0.5, 0.75, 1]) for col in [col0, col1]
         ]
@@ -56,60 +50,66 @@ def _compute_missing_bivariate(  # pylint: disable=too-many-locals
     meta["y", "dtype"] = detect_dtype(df.frame[y], dtype)
 
     if is_dtype(detect_dtype(df.frame[y], dtype), Continuous()):
-        dists = [rv_histogram((hist[0], hist[2])) for hist in hists]  # type: ignore
-        xs = np.linspace(minimum, maximum, ndist_sample)
-
-        pdfs = [dist.pdf(xs) for dist in dists]
-        cdfs = [dist.cdf(xs) for dist in dists]
-
-        distdf = pd.DataFrame(
-            {
-                "x": np.tile(xs, 2),
-                "pdf": np.concatenate(pdfs),
-                "cdf": np.concatenate(cdfs),
-                "label": np.repeat(LABELS, ndist_sample),
-            }
-        )
-
-        counts, xs, edges = zip(*hists)
-
-        lower_bounds: List[float] = []
-        upper_bounds: List[float] = []
-
-        for edge in edges:
-            lower_bounds.extend(edge[:-1])
-            upper_bounds.extend(edge[1:])
 
-        histdf = pd.DataFrame(
-            {
-                "x": np.concatenate(xs),
-                "count": np.concatenate(counts),
-                "label": np.repeat(LABELS, [len(count) for count in counts]),
-                "lower_bound": lower_bounds,
-                "upper_bound": upper_bounds,
-            }
-        )
-
-        boxdf = pd.DataFrame(quantiles)
-        boxdf.columns = ["min", "q1", "q2", "q3", "max"]
-
-        iqr = boxdf["q3"] - boxdf["q1"]
-        boxdf["upper"] = np.minimum(boxdf["q3"] + 1.5 * iqr, boxdf["max"])
-        boxdf["lower"] = np.maximum(boxdf["q3"] - 1.5 * iqr, boxdf["min"])
-        boxdf["label"] = LABELS
+        if cfg.pdf.enable or cfg.cdf.enable:
+            dists = [rv_histogram((hist[0], hist[2])) for hist in hists]  # type: ignore
+            distdf = pd.DataFrame({})
+
+        if cfg.pdf.enable:
+            xs_pdf = np.linspace(minimum, maximum, cfg.pdf.sample_size)
+            pdfs = [dist.pdf(xs_pdf) for dist in dists]
+            distdf["x_pdf"] = np.tile(xs_pdf, 2)
+            distdf["pdf"] = np.concatenate(pdfs)
+            distdf["pdf_label"] = np.repeat(LABELS, cfg.pdf.sample_size)
+
+        if cfg.cdf.enable:
+            xs_cdf = np.linspace(minimum, maximum, cfg.cdf.sample_size)
+            cdfs = [dist.cdf(xs_cdf) for dist in dists]
+            distdf["x_cdf"] = np.tile(xs_cdf, 2)
+            distdf["cdf"] = np.concatenate(cdfs)
+            distdf["cdf_label"] = np.repeat(LABELS, cfg.cdf.sample_size)
+
+        if cfg.hist.enable:
+            counts, xs, edges = zip(*hists)
+
+            lower_bounds: List[float] = []
+            upper_bounds: List[float] = []
+
+            for edge in edges:
+                lower_bounds.extend(edge[:-1])
+                upper_bounds.extend(edge[1:])
+
+            histdf = pd.DataFrame(
+                {
+                    "x": np.concatenate(xs),
+                    "count": np.concatenate(counts),
+                    "label": np.repeat(LABELS, [len(count) for count in counts]),
+                    "lower_bound": lower_bounds,
+                    "upper_bound": upper_bounds,
+                }
+            )
+
+        if cfg.box.enable:
+            boxdf = pd.DataFrame(quantiles)
+            boxdf.columns = ["min", "q1", "q2", "q3", "max"]
+
+            iqr = boxdf["q3"] - boxdf["q1"]
+            boxdf["upper"] = np.minimum(boxdf["q3"] + 1.5 * iqr, boxdf["max"])
+            boxdf["lower"] = np.maximum(boxdf["q3"] - 1.5 * iqr, boxdf["min"])
+            boxdf["label"] = LABELS
 
         itmdt = Intermediate(
-            dist=distdf,
-            hist=histdf,
-            box=boxdf,
+            dist=distdf if cfg.pdf.enable or cfg.cdf.enable else pd.DataFrame({}),
+            hist=histdf if cfg.hist.enable else pd.DataFrame({}),
+            box=boxdf if cfg.box.enable else pd.DataFrame({}),
             meta=meta["y"],
             x=x,
             y=y,
             visual_type="missing_impact_1v1",
         )
         return itmdt
-    else:
 
+    else:
         counts, xs = zip(*hists)
 
         df_ret = pd.DataFrame(
@@ -126,12 +126,12 @@ def _compute_missing_bivariate(  # pylint: disable=too-many-locals
             sortidx = np.argsort(-counts[0])
             selected_xs = xs[0][sortidx[:bins]]
             df_ret = df_ret[df_ret["x"].isin(selected_xs)]
-            partial = (bins, len(counts[0]))
+            shown = bins
         else:
-            partial = (len(counts[0]), len(counts[0]))
-
-        meta["y", "partial"] = partial
+            shown = len(counts[0])
 
+        meta["y", "shown"] = shown
+        meta["y", "total"] = len(counts[0])
         itmdt = Intermediate(
             hist=df_ret,
             x=x,
diff --git a/dataprep/eda/missing/compute/common.py b/dataprep/eda/missing/compute/common.py
index cd60f297c..688e0fe4e 100644
--- a/dataprep/eda/missing/compute/common.py
+++ b/dataprep/eda/missing/compute/common.py
@@ -4,27 +4,22 @@
 import dask.array as da
 import dask.dataframe as dd
 
-from ...dtypes import (
-    Continuous,
-    DTypeDef,
-    Nominal,
-    detect_dtype,
-    is_dtype,
-)
+from ...configs import Config
+from ...dtypes import Continuous, DTypeDef, Nominal, detect_dtype, is_dtype
 
 LABELS = ["Orignal data", "After drop missing values"]
 
 
 def uni_histogram(
     srs: dd.Series,
-    bins: int,
+    cfg: Config,
     dtype: Optional[DTypeDef] = None,
 ) -> Tuple[da.Array, ...]:
     """Calculate "histogram" for both numerical and categorical."""
 
     if is_dtype(detect_dtype(srs, dtype), Continuous()):
 
-        counts, edges = da.histogram(srs, bins, range=[srs.min(), srs.max()])
+        counts, edges = da.histogram(srs, cfg.hist.bins, (srs.min(), srs.max()))
         centers = (edges[:-1] + edges[1:]) / 2
 
         return counts, centers, edges
diff --git a/dataprep/eda/missing/compute/nullivariate.py b/dataprep/eda/missing/compute/nullivariate.py
index c10a17179..2afae9f4a 100644
--- a/dataprep/eda/missing/compute/nullivariate.py
+++ b/dataprep/eda/missing/compute/nullivariate.py
@@ -1,7 +1,7 @@
 """This module implements the plot_missing(df) function's
 calculating intermediate part
 """
-from typing import Any, Callable, Dict, Generator, Optional, Tuple, List
+from typing import Any, Callable, Dict, Generator, List, Optional, Tuple
 
 import dask.array as da
 import dask.dataframe as dd
@@ -9,20 +9,20 @@
 import pandas as pd
 from dask import delayed
 from scipy.cluster import hierarchy
-from ...utils import cut_long_name
 
+from ...configs import Config
 from ...data_array import DataArray
 from ...intermediate import Intermediate
 from ...staged import staged
+from ...utils import cut_long_name
 
 
-def _compute_missing_nullivariate(df: DataArray, bins: int) -> Generator[Any, Any, Intermediate]:
+def _compute_missing_nullivariate(df: DataArray, cfg: Config) -> Generator[Any, Any, Intermediate]:
     """Calculate the data for visualizing the plot_missing(df).
     This contains the missing spectrum, missing bar chart and missing heatmap."""
     # pylint: disable=too-many-locals
 
     most_show = 5  # the most number of column/row to show in "insight"
-    # longest = 5  # the longest length of word to show in "insight"
 
     df.compute()
 
@@ -36,19 +36,19 @@ def _compute_missing_nullivariate(df: DataArray, bins: int) -> Generator[Any, An
     avg_col = nullity.sum() / ncols
 
     tasks = (
-        missing_spectrum(df, bins=bins),
-        null_perc,
-        missing_bars(null_cnts, df.columns.values, nrows),
-        missing_heatmap(df),
-        missing_dendrogram(df),
-        nullity.sum(),
-        missing_col_cnt(df),
-        missing_row_cnt(df),
-        missing_most_col(df),
-        missing_most_row(df),
-        miss_perc,
-        avg_row,
-        avg_col,
+        missing_spectrum(df, cfg.spectrum.bins) if cfg.spectrum.enable else None,
+        null_perc if cfg.spectrum.enable or cfg.heatmap.enable else None,
+        missing_bars(null_cnts, df.columns.values, nrows) if cfg.bar.enable else None,
+        missing_heatmap(df) if cfg.heatmap.enable else None,
+        missing_dendrogram(df) if cfg.dendro.enable else None,
+        nullity.sum() if cfg.stats.enable else None,
+        missing_col_cnt(df) if cfg.stats.enable else None,
+        missing_row_cnt(df) if cfg.stats.enable else None,
+        missing_most_col(df) if cfg.insight.enable else None,
+        missing_most_row(df) if cfg.insight.enable else None,
+        miss_perc if cfg.stats.enable else None,
+        avg_row if cfg.stats.enable else None,
+        avg_col if cfg.stats.enable else None,
     )
 
     ### Lazy Region End
@@ -69,61 +69,74 @@ def _compute_missing_nullivariate(df: DataArray, bins: int) -> Generator[Any, An
     ) = yield tasks
     ### Eager Region Begin
 
-    sel = ~((null_perc == 0) | (null_perc == 1))
-    if nrows != 1:
-        heatmap = pd.DataFrame(
-            data=heatmap[:, sel][sel, :], columns=df.columns[sel], index=df.columns[sel]
+    if cfg.heatmap.enable:
+        sel = ~((null_perc == 0) | (null_perc == 1))
+        if nrows != 1:
+            heatmap = pd.DataFrame(
+                data=heatmap[:, sel][sel, :], columns=df.columns[sel], index=df.columns[sel]
+            )
+        else:
+            heatmap = pd.DataFrame(data=heatmap, columns=df.columns[sel], index=df.columns[sel])
+
+    if cfg.stats.enable:
+        missing_stat = {
+            "Missing Cells": cnt,
+            "Missing Cells (%)": str(round(miss_perc * 100, 1)) + "%",
+            "Missing Columns": col_cnt,
+            "Missing Rows": row_cnt,
+            "Avg Missing Cells per Column": round(avg_col, 2),
+            "Avg Missing Cells per Row": round(avg_row, 2),
+        }
+
+    if cfg.insight.enable:
+        suffix_col = "" if most_col[0] <= most_show else ", ..."
+        suffix_row = "" if most_row[0] <= most_show else ", ..."
+
+        top_miss_col = (
+            str(most_col[0])
+            + " col(s): "
+            + str(
+                "("
+                + ", ".join(cut_long_name(df.columns[e]) for e in most_col[2][:most_show])
+                + suffix_col
+                + ")"
+            )
+        )
+
+        top_miss_row = (
+            str(most_row[0])
+            + " row(s): "
+            + str("(" + ", ".join(str(e) for e in most_row[2][:most_show]) + suffix_row + ")")
         )
-    else:
-        heatmap = pd.DataFrame(data=heatmap, columns=df.columns[sel], index=df.columns[sel])
-
-    suffix_col = "" if most_col[0] <= most_show else ", ..."
-    suffix_row = "" if most_row[0] <= most_show else ", ..."
-
-    top_miss_col = (
-        str(most_col[0])
-        + " col(s): "
-        + str(
-            "("
-            + ", ".join(cut_long_name(df.columns[e]) for e in most_col[2][:most_show])
-            + suffix_col
-            + ")"
+
+        insights = (
+            {
+                "Bar Chart": [
+                    top_miss_col
+                    + " contain the most missing values with rate "
+                    + str(round(most_col[1] * 100, 1))
+                    + "%",
+                    top_miss_row
+                    + " contain the most missing columns with rate "
+                    + str(round(most_row[1] * 100, 1))
+                    + "%",
+                ]
+            },
         )
-    )
 
-    top_miss_row = (
-        str(most_row[0])
-        + " row(s): "
-        + str("(" + ", ".join(str(e) for e in most_row[2][:most_show]) + suffix_row + ")")
-    )
+    data_total_missing = {}
+    if cfg.spectrum.enable:
+        data_total_missing = {col: null_perc[i] for i, col in enumerate(df.columns)}
 
     return Intermediate(
-        data_total_missing={col: null_perc[idx] for idx, col in enumerate(df.columns)},
-        data_spectrum=pd.DataFrame(spectrum),
+        data_total_missing=data_total_missing,
+        data_spectrum=pd.DataFrame(spectrum) if spectrum else spectrum,
         data_bars=bars,
         data_heatmap=heatmap,
         data_dendrogram=dendrogram,
         visual_type="missing_impact",
-        missing_stat={
-            "Missing Cells": cnt,
-            "Missing Cells (%)": str(round(miss_perc * 100, 1)) + "%",
-            "Missing Columns": col_cnt,
-            "Missing Rows": row_cnt,
-            "Avg Missing Cells per Column": round(avg_col, 2),
-            "Avg Missing Cells per Row": round(avg_row, 2),
-        },
-        insights={
-            "Bar Chart": [
-                top_miss_col
-                + " contain the most missing values with rate "
-                + str(round(most_col[1] * 100, 1))
-                + "%",
-                top_miss_row
-                + " contain the most missing columns with rate "
-                + str(round(most_row[1] * 100, 1))
-                + "%",
-            ]
-        },
+        missing_stat=missing_stat if cfg.stats.enable else {},
+        insights=insights if cfg.insight.enable else {},
     )
 
 
@@ -151,9 +164,9 @@ def imp(block: np.ndarray) -> np.ndarray:
     return imp
 
 
-def missing_spectrum(  # pylint: disable=too-many-locals
+def missing_spectrum(
     df: DataArray, bins: int
-) -> Dict[str, da.Array]:
+) -> Dict[str, da.Array]:  # pylint: disable=too-many-locals
     """Calculate a missing spectrum for each column."""
 
     nrows, ncols = df.shape
diff --git a/dataprep/eda/missing/compute/univariate.py b/dataprep/eda/missing/compute/univariate.py
index 2148624c0..1a9cbea3b 100644
--- a/dataprep/eda/missing/compute/univariate.py
+++ b/dataprep/eda/missing/compute/univariate.py
@@ -1,4 +1,4 @@
-"""This module implements the plot_missing(df) function's
+"""This module implements the plot_missing(df, x) function's
 calculating intermediate part
 """
 from typing import Any, Generator, List, Optional
@@ -6,13 +6,9 @@
 import numpy as np
 import pandas as pd
 
+from ...configs import Config
 from ...data_array import DataArray
-from ...dtypes import (
-    DTypeDef,
-    Nominal,
-    detect_dtype,
-    is_dtype,
-)
+from ...dtypes import DTypeDef, Continuous, Nominal, detect_dtype, is_dtype
 from ...intermediate import ColumnsMetadata, Intermediate
 from ...staged import staged
 from .common import LABELS, uni_histogram
@@ -21,7 +17,7 @@
 def _compute_missing_univariate(  # pylint: disable=too-many-locals
     df: DataArray,
     x: str,
-    bins: int,
+    cfg: Config,
     dtype: Optional[DTypeDef] = None,
 ) -> Generator[Any, Any, Intermediate]:
     """Calculate the distribution change on other columns when
@@ -33,13 +29,19 @@ def _compute_missing_univariate(  # pylint: disable=too-many-locals
     hists = {}
 
     for col in df.columns:
-        if col == x:
+        if (
+            col == x
+            or is_dtype(detect_dtype(df.frame[col]), Nominal())
+            and not cfg.bar.enable
+            or is_dtype(detect_dtype(df.frame[col]), Continuous())
+            and not cfg.hist.enable
+        ):
             continue
 
         srs0 = df.frame[col].dropna()  # series from original dataframe
         srs1 = ddf[col].dropna()  # series with null rows from col x removed
 
-        hists[col] = [uni_histogram(srs, bins=bins, dtype=dtype) for srs in [srs0, srs1]]
+        hists[col] = [uni_histogram(srs, cfg, dtype) for srs in [srs0, srs1]]
 
     ### Lazy Region End
     hists = yield hists
@@ -75,13 +77,16 @@ def _compute_missing_univariate(  # pylint: disable=too-many-locals
 
         # If the cardinality of a categorical column is too large,
         # we show the top `num_bins` values, sorted by their count before drop
-        if len(counts[0]) > bins and is_dtype(detect_dtype(df.frame[col_name], dtype), Nominal()):
+        if len(counts[0]) > cfg.bar.bars and is_dtype(
+            detect_dtype(df.frame[col_name], dtype), Nominal()
+        ):
             sortidx = np.argsort(-counts[0])
-            selected_xs = xs[0][sortidx[:bins]]
+            selected_xs = xs[0][sortidx[: cfg.bar.bars]]
             ret_df = ret_df[ret_df["x"].isin(selected_xs)]
-            meta[col_name, "partial"] = (bins, len(counts[0]))
+            meta[col_name, "shown"] = cfg.bar.bars
         else:
-            meta[col_name, "partial"] = (len(counts[0]), len(counts[0]))
+            meta[col_name, "shown"] = len(counts[0])
+        meta[col_name, "total"] = len(counts[0])
         meta[col_name, "dtype"] = detect_dtype(df.frame[col_name], dtype)
         dfs[col_name] = ret_df
 
diff --git a/dataprep/eda/missing/render.py b/dataprep/eda/missing/render.py
index caaaf69ed..be5d36b1c 100644
--- a/dataprep/eda/missing/render.py
+++ b/dataprep/eda/missing/render.py
@@ -21,11 +21,11 @@
     Panel,
     PrintfTickFormatter,
     Range1d,
-    Title,
 )
 from bokeh.plotting import Figure
 
 from ...errors import UnreachableError
+from ..configs import Config
 from ..dtypes import Continuous, Nominal, drop_null, is_dtype
 from ..intermediate import ColumnMetadata, Intermediate
 from ..palette import CATEGORY10, CATEGORY20, GREYS256, RDBU
@@ -35,20 +35,17 @@
 __all__ = ["render_missing"]
 
 
-def render_missing(
-    itmdt: Intermediate,
-    plot_width: int = 400,
-    plot_height: int = 400,
-) -> Dict[str, Any]:
+def render_missing(itmdt: Intermediate, cfg: Config) -> Dict[str, Any]:
     """
-    @Jinglin write here
+    Render the visualizations from plot_missing
     """
+
     if itmdt.visual_type == "missing_impact":
-        return render_missing_impact(itmdt, plot_width, plot_height)
+        return render_missing_impact(itmdt, cfg)
     elif itmdt.visual_type == "missing_impact_1vn":
-        return render_missing_impact_1vn(itmdt, plot_width - 100, plot_height - 100)
+        return render_missing_impact_1vn(itmdt, cfg)
     elif itmdt.visual_type == "missing_impact_1v1":
-        return render_missing_impact_1v1(itmdt, plot_width, plot_height)
+        return render_missing_impact_1v1(itmdt, cfg)
     else:
         raise UnreachableError
 
@@ -95,10 +92,10 @@ def render_dist(
     tooltips = [
         (x, "@x"),
         (typ.upper(), f"@{{{typ}}}"),
-        ("Label", "@label"),
+        ("Label", f"@{{{typ}}}_label"),
     ]
     y_range = Range1d(0, df[typ].max() * 1.01)
-    x_range = Range1d(0, df["x"].max() * 1.01)
+    x_range = Range1d(0, df[f"x_{typ}"].max() * 1.01)
 
     fig = tweak_figure(
         Figure(
@@ -106,15 +103,16 @@ def render_dist(
             y_range=y_range,
             plot_width=plot_width,
             plot_height=plot_height,
+            title=" ",
             tools="hover",
             toolbar_location=None,
             tooltips=tooltips,
         )
     )
     for idx, label in enumerate(LABELS):
-        group = df[df["label"] == label]
+        group = df[df[f"{typ}_label"] == label]
         fig.line(
-            x="x",
+            x=f"x_{typ}",
             y=typ,
             source=group,
             color=CATEGORY10[idx],
@@ -175,6 +173,7 @@ def render_hist(  # pylint: disable=too-many-arguments
             y_range=y_range,
             plot_width=plot_width,
             plot_height=plot_height,
+            title=" " if show_legend else "",
             tools="hover",
             toolbar_location=None,
             tooltips=tooltips,
@@ -193,7 +192,7 @@ def render_hist(  # pylint: disable=too-many-arguments
 
         relocate_legend(fig, "left")
     else:
-        shown, total = meta["partial"]
+        shown, total = meta["shown"], meta["total"]
         if shown != total:
             fig.xaxis.axis_label = f"Top {shown} out of {total}"
             fig.xaxis.axis_label_standoff = 0
@@ -227,6 +226,7 @@ def render_boxwhisker(df: pd.DataFrame, plot_width: int, plot_height: int) -> Fi
             x_range=df["label"].unique(),
             plot_width=plot_width,
             plot_height=plot_height,
+            title=" ",
             tools="",
             toolbar_location=None,
             tooltips=tooltips,
@@ -310,33 +310,48 @@ def create_color_mapper_heatmap(
     return mapper, colorbar
 
 
-def render_missing_impact(itmdt: Intermediate, plot_width: int, plot_height: int) -> Dict[str, Any]:
+def render_missing_impact(itmdt: Intermediate, cfg: Config) -> Dict[str, Any]:
     """
     Render correlation heatmaps in to tabs
     """
+    plot_width = cfg.plot.width if cfg.plot.width is not None else 400
+    plot_height = cfg.plot.height if cfg.plot.height is not None else 400
+
     tabs: List[Panel] = []
-    fig_barchart = render_bar_chart(itmdt["data_bars"], "linear", plot_width, plot_height)
-    tabs.append(Panel(child=row(fig_barchart), title="Bar Chart"))
+    htgs: Dict[str, List[Tuple[str, str]]] = {}
 
-    fig_spectrum = render_missing_spectrum(
-        itmdt["data_spectrum"], itmdt["data_total_missing"], plot_width, plot_height
-    )
-    tabs.append(Panel(child=row(fig_spectrum), title="Spectrum"))
+    if cfg.bar.enable:
+        fig_barchart = render_bar_chart(itmdt["data_bars"], "linear", plot_width, plot_height)
+        tabs.append(Panel(child=row(fig_barchart), title="Bar Chart"))
+        htgs["Bar Chart"] = cfg.bar.missing_how_to_guide(plot_height, plot_width)
 
-    fig_heatmap = render_heatmaps(itmdt["data_heatmap"], plot_width, plot_height)
-    tabs.append(Panel(child=row(fig_heatmap), title="Heatmap"))
+    if cfg.spectrum.enable:
+        fig_spectrum = render_missing_spectrum(
+            itmdt["data_spectrum"], itmdt["data_total_missing"], plot_width, plot_height
+        )
+        tabs.append(Panel(child=row(fig_spectrum), title="Spectrum"))
+        htgs["Spectrum"] = cfg.spectrum.how_to_guide(plot_height, plot_width)
+
+    if cfg.heatmap.enable:
+        fig_heatmap = render_heatmaps(itmdt["data_heatmap"], plot_width, plot_height)
+        tabs.append(Panel(child=row(fig_heatmap), title="Heat Map"))
+        htgs["Heat Map"] = cfg.heatmap.missing_how_to_guide(plot_height, plot_width)
 
-    fig_dendrogram = render_dendrogram(itmdt["data_dendrogram"], plot_width, plot_height)
-    tabs.append(Panel(child=row(fig_dendrogram), title="Dendrogram"))
+    if cfg.dendro.enable:
+        fig_dendrogram = render_dendrogram(itmdt["data_dendrogram"], plot_width, plot_height)
+        tabs.append(Panel(child=row(fig_dendrogram), title="Dendrogram"))
+        htgs["Dendrogram"] = cfg.dendro.how_to_guide(plot_height, plot_width)
 
-    stat_dict = {name: itmdt["missing_stat"][name] for name in itmdt["missing_stat"]}
+    if cfg.stats.enable:
+        stat_dict = {name: itmdt["missing_stat"][name] for name in itmdt["missing_stat"]}
 
     return {
         "insights": itmdt["insights"],
-        "tabledata": {"Missing Statistics": stat_dict},
+        "tabledata": {"Missing Statistics": stat_dict} if cfg.stats.enable else {},
         "layout": [panel.child.children[0] for panel in tabs],
         "meta": [panel.title for panel in tabs],
         "container_width": plot_width + 160,
+        "how_to_guide": htgs,
     }
 
 
@@ -356,6 +371,7 @@ def empty_figure() -> Figure:
             plot_width=plot_width,
             plot_height=plot_height,
             x_axis_location="below",
+            title=" ",
             tools="hover",
             toolbar_location=None,
             background_fill_color="#fafafa",
@@ -605,24 +621,30 @@ def render_dendrogram(dend: Dict["str", Any], plot_width: int, plot_height: int)
     return fig
 
 
-def render_missing_impact_1vn(
-    itmdt: Intermediate,
-    plot_width: int,
-    plot_height: int,
-) -> Dict[str, Any]:
+def render_missing_impact_1vn(itmdt: Intermediate, cfg: Config) -> Dict[str, Any]:
     """
     Render the plot from `plot_missing(df, "x")`
     """
+    plot_width = cfg.plot.width if cfg.plot.width is not None else 300
+    plot_height = cfg.plot.height if cfg.plot.height is not None else 300
 
     dfs = itmdt["data"]
     x = itmdt["x"]
     meta = itmdt["meta"]
     panels = []
+    htgs: Dict[str, List[Tuple[str, str]]] = {}
+    titles: List[str] = []
     for col, df in dfs.items():
+        title = f"Missing impact of {x} by {col}"
         fig = render_hist(df, col, meta[col], plot_width, plot_height, False)
         fig.frame_height = plot_height
-        fig.title = Title(text=f"Missing impact of {x} by {col}")
         panels.append(Panel(child=fig, title=col))
+
+        if is_dtype(meta[col]["dtype"], Nominal()):
+            htgs[title] = cfg.bar.grid_how_to_guide()
+        else:
+            htgs[title] = cfg.hist.grid_how_to_guide()
+        titles.append(title)
     legend_colors = [CATEGORY10[count] for count in range(len(LABELS))]
     return {
         "layout": [panel.child for panel in panels],
@@ -630,34 +652,40 @@ def render_missing_impact_1vn(
         "legend_labels": [
             {"label": label, "color": color} for label, color in zip(LABELS, legend_colors)
         ],
+        "meta": titles,
+        "how_to_guide": htgs,
     }
 
 
-def render_missing_impact_1v1(
-    itmdt: Intermediate,
-    plot_width: int,
-    plot_height: int,
-) -> Dict[str, Any]:
+def render_missing_impact_1v1(itmdt: Intermediate, cfg: Config) -> Dict[str, Any]:
     """
     Render the plot from `plot_missing(df, "x", "y")`
     """
-    x, y = itmdt["x"], itmdt["y"]
-    meta = itmdt["meta"]
+    plot_width = cfg.plot.width if cfg.plot.width is not None else 400
+    plot_height = cfg.plot.height if cfg.plot.height is not None else 400
+
+    x, y, meta = itmdt["x"], itmdt["y"], itmdt["meta"]
+    htgs: Dict[str, List[Tuple[str, str]]] = {}
 
     if is_dtype(meta["dtype"], Continuous()):
         panels = []
 
-        fig = render_hist(itmdt["hist"], y, meta, plot_width, plot_height, True)
-        panels.append(Panel(child=fig, title="Histogram"))
-
-        fig = render_dist(itmdt["dist"], y, "pdf", plot_width, plot_height)
-        panels.append(Panel(child=fig, title="PDF"))
-
-        fig = render_dist(itmdt["dist"], y, "cdf", plot_width, plot_height)
-        panels.append(Panel(child=fig, title="CDF"))
-
-        fig = render_boxwhisker(itmdt["box"], plot_width, plot_height)
-        panels.append(Panel(child=fig, title="Box"))
+        if cfg.hist.enable:
+            fig = render_hist(itmdt["hist"], y, meta, plot_width, plot_height, True)
+            panels.append(Panel(child=fig, title="Histogram"))
+            htgs["Histogram"] = cfg.hist.how_to_guide(plot_height, plot_width)
+        if cfg.pdf.enable:
+            fig = render_dist(itmdt["dist"], y, "pdf", plot_width, plot_height)
+            panels.append(Panel(child=fig, title="PDF"))
+            htgs["PDF"] = cfg.pdf.how_to_guide(plot_height, plot_width)
+        if cfg.cdf.enable:
+            fig = render_dist(itmdt["dist"], y, "cdf", plot_width, plot_height)
+            panels.append(Panel(child=fig, title="CDF"))
+            htgs["CDF"] = cfg.cdf.how_to_guide(plot_height, plot_width)
+        if cfg.box.enable:
+            fig = render_boxwhisker(itmdt["box"], plot_width, plot_height)
+            panels.append(Panel(child=fig, title="Box Plot"))
+            htgs["Box Plot"] = cfg.box.univar_how_to_guide(plot_height, plot_width)
 
         for panel in panels:
             panel.child.frame_width = plot_width
@@ -666,13 +694,20 @@ def render_missing_impact_1v1(
             "layout": [panel.child for panel in panels],
             "meta": [panel.title for panel in panels],
             "container_width": plot_width + 240,
+            "how_to_guide": htgs,
         }
     else:
         fig = render_hist(itmdt["hist"], y, meta, plot_width, plot_height, True)
         fig.frame_width = plot_width
-        shown, total = meta["partial"]
+        shown, total = meta["shown"], meta["total"]
         if shown != total:
             _title = f"Missing impact of {x} by ({shown} out of {total}) {y}"
         else:
             _title = f"Missing impact of {x} by {y}"
-        return {"layout": [fig], "meta": [_title], "container_width": plot_width + 240}
+        htgs[_title] = cfg.bar.how_to_guide(plot_height, plot_width)
+        return {
+            "layout": [fig],
+            "meta": [_title],
+            "container_width": plot_width + 240,
+            "how_to_guide": htgs,
+        }
diff --git a/dataprep/eda/templates/grid_base.html b/dataprep/eda/templates/grid_base.html
index b7b2cf730..054ca6f19 100644
--- a/dataprep/eda/templates/grid_base.html
+++ b/dataprep/eda/templates/grid_base.html
@@ -7,13 +7,14 @@
 </head>
 
 <body>
-    {% if context.tabledata %}
+    {% if context.tabledata or context.overview_insights%}
     <div class="top-info-{{ context.rnd }}">
         <input type='checkbox' style='display: none' id="tb-{{ context.rnd }}">
         <label class="toggle-label-{{ context.rnd }}" for="tb-{{ context.rnd }}">
-            Stats and Insights
+            {{ context.toggle_content }}
         </label>
         <div class="stats-{{ context.rnd }}">
+            {% if context.tabledata%}
             <div class="tabledata-{{ context.rnd }}">
                 <div>
                     <h3>Dataset Statistics</h3>
@@ -37,6 +38,8 @@ <h3>Dataset Statistics</h3>
                     </table>
                 </div>
             </div>
+            {% endif %}
+            {% if context.overview_insights %}
             <div class="insights-{{ context.rnd }}">
                 {% for page, content in context.overview_insights.items() %}
                 <div class="page-box-{{ context.rnd }}" id="page-{{ context.rnd }}-{{ loop.index }}">
@@ -57,7 +60,9 @@ <h3>Dataset Insights</h3>
                 </div>
                 {% endfor %}
             </div>
+            {% endif %}
         </div>
+        {% if context.overview_insights %}
         <div class="page-switch-box-{{ context.rnd }}">
             <ul class="page-switch-{{ context.rnd }}">
                 {% for _ in context.overview_insights %}
@@ -67,6 +72,7 @@ <h3>Dataset Insights</h3>
                 {% endfor %}
             </ul>
         </div>
+        {% endif %}
     </div>
     {% endif %}
     {% if context.legend_labels %}
@@ -85,6 +91,21 @@ <h3>Dataset Insights</h3>
             {% if context.meta %}
             <div class="menu-{{ context.rnd }}">
                 <div class="plot-title-{{ context.rnd }}">{{ context.meta[loop.index0] }}</div>
+                {% if context.how_to_guide[context.meta[loop.index0]] %}
+                <div class="insight-container-{{ context.rnd }}">
+                    <input type='checkbox' style='display: none' id="htgbt-{{ context.rnd }}-{{ loop.index0 }}" class="insight-check-{{ context.rnd }}">
+                    <label class="htg-btn-{{ context.rnd }}" for="htgbt-{{ context.rnd }}-{{ loop.index0 }}"></label>
+                    <div class="insight-panel-{{ context.rnd }}" style="padding:20px;">
+                        {% for desc in context.how_to_guide[context.meta[loop.index0]] %}
+                        <div class="htg-entry-{{ context.rnd }}">
+                            <div class="htg-entry-conf-{{ context.rnd }}">{{ desc[0] }}</div>
+                            <div class="htg-entry-desc-{{ context.rnd }}">{{ desc[1] }}</div>
+                        </div>
+                        {% endfor %}
+                        <button class="htg-copy-btn-{{ context.rnd }}" onclick="copyParam(this)">Copy All Parameters</button>
+                    </div>
+                </div>
+                {% endif %}
                 {% if context.column_insights[context.meta[loop.index0]] %}
                 <input type='checkbox' style='display: none' id="ib-{{ context.rnd }}-{{ loop.index0 }}" class="insight-check-{{ context.rnd }}">
                 <label class="insight-btn-{{ context.rnd }}" for="ib-{{ context.rnd }}-{{ loop.index0 }}"></label>
diff --git a/dataprep/eda/templates/grid_scripts.html b/dataprep/eda/templates/grid_scripts.html
index 5f9707787..66c3ceafe 100644
--- a/dataprep/eda/templates/grid_scripts.html
+++ b/dataprep/eda/templates/grid_scripts.html
@@ -62,4 +62,31 @@
             }
         }
     }
+
+    function copyParam(e) {
+        const buttonText = e.innerText;
+        const paramElement = e.parentElement.childNodes;
+        let param = '';
+        for (let i of paramElement) {
+            if (i.nodeName === 'DIV') {
+                const nodeParam = i.firstElementChild.innerText;
+                param = param + nodeParam + ', ';
+            }
+        }
+        navigator.clipboard.writeText(param).then(
+            () => {
+                e.innerText = 'Copied to Clipboard!';
+                setTimeout(() => {
+                    e.innerText = buttonText;
+                }, 1500);
+            }
+        ).catch(
+            error => {
+                e.innerText = error;
+                setTimeout(() => {
+                    e.innerText = buttonText;
+                }, 1500);
+            }
+        )
+    }
 </script>
\ No newline at end of file
diff --git a/dataprep/eda/templates/grid_styles.html b/dataprep/eda/templates/grid_styles.html
index c17221963..88ad5c014 100644
--- a/dataprep/eda/templates/grid_styles.html
+++ b/dataprep/eda/templates/grid_styles.html
@@ -70,8 +70,12 @@
         height: 20px;
         opacity: 0.5;
         display: inline-block;
-        vertical-align: top;
         cursor: pointer;
+        position: relative;
+        left: 250px;
+        bottom: 2px;
+        margin: 0;
+        z-index: 1;
     }
 
     .insight-btn-{{ context.rnd }}:hover {
@@ -149,7 +153,7 @@
         color: #141212;
         display: inline-block;
         cursor: pointer;
-        width: 972px;
+        width: {{context.container_width}}px;
         background-color: white;
         border: 1px solid #b5d6ea;
         border-radius: 5px;
@@ -166,7 +170,7 @@
     .stats-{{ context.rnd }} {
         display: none;
         justify-content: space-between;
-        width: 972px;
+        width: {{context.container_width}};
         height: 350px;
     }
 
@@ -188,8 +192,8 @@
     }
 
     .tabledata-{{ context.rnd }} {
-        flex: 0 1 50%;
-        max-width: 477px;
+        flex: 1 1 50%;
+<!--        max-width: 477px;-->
     }
 
     .tabledata-{{ context.rnd }} h3 {
@@ -205,9 +209,8 @@
     }
 
     .insights-{{ context.rnd }} {
-        flex: 0 1 50%;
+        flex: 1 1 50%;
         text-align: center;
-        max-width: 477px;
     }
 
     .page-box-{{ context.rnd }} {
@@ -345,4 +348,71 @@
         margin: 0 10px;
         color: #444444
     }
+
+    .insight-container-{{ context.rnd }} {
+        width: 300px;
+        height: 0;
+        position: relative;
+        top: 10px;
+        right: 5px;
+    }
+
+    .htg-btn-{{ context.rnd }} {
+        filter: invert(73%) sepia(44%) saturate(243%) hue-rotate(181deg) brightness(87%) contrast(87%);
+        background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='currentColor'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z' /%3E%3C/svg%3E");
+        background-size: 20px;
+        background-repeat: no-repeat;
+        width: 20px;
+        height: 20px;
+        opacity: 0.5;
+        display: inline-block;
+        cursor: pointer;
+        position: absolute;
+        left: 275px;
+        bottom: -8px;
+        margin: 0;
+        z-index: 1;
+    }
+
+    .htg-btn-{{ context.rnd }}:hover {
+        opacity: 1;
+    }
+
+    .htg-entry-{{ context.rnd }} {
+        border: 1px solid white;
+        border-radius: 5px;
+        background: rgba(0, 0, 0, 0.1);
+        color: #ffffffe0;
+        padding: 10px;
+        margin: 5px 0px;
+    }
+
+    .htg-entry-conf-{{ context.rnd }} {
+        font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
+        font-size: 85%;
+        background: rgb(187, 71, 103);
+        width: fit-content;
+        border: 1px solid white;
+        border-radius: 2px;
+        padding: 2px 5px;
+    }
+
+    .htg-entry-desc-{{ context.rnd }} {
+        font-family: sans-serif;
+        font-size: 110%;
+        color: #909599;
+        cursor: default;
+    }
+
+    .htg-copy-btn-{{ context.rnd }} {
+        border: 1px solid white;
+        border-radius: 5px;
+        margin: auto 25%;
+        width: 50%;
+        margin-top: 10px;
+    }
+
+    .htg-copy-btn-{{ context.rnd }}:hover {
+        background: #5DADE2;
+    }
 </style>
\ No newline at end of file
diff --git a/dataprep/eda/templates/tab_base.html b/dataprep/eda/templates/tab_base.html
index 45a142361..776d5652f 100644
--- a/dataprep/eda/templates/tab_base.html
+++ b/dataprep/eda/templates/tab_base.html
@@ -35,13 +35,28 @@ <h3>{{ title }}</h3>
             {% endif %}
             {% for fig in context.components[1] %}
             <div class="plots-{{ context.rnd }}">
-                {% if context.insights[context.meta[loop.index]] %}
+                {% if context.how_to_guide[context.figs[loop.index - 1]] %}
+                <div class="insight-container-{{ context.rnd }}">
+                    <input type='checkbox' style='display: none' id="htgbt-{{ context.rnd }}-{{ loop.index0 }}" class="insight-check-{{ context.rnd }}">
+                    <label class="htg-btn-{{ context.rnd }}" for="htgbt-{{ context.rnd }}-{{ loop.index0 }}"></label>
+                    <div class="insight-panel-{{ context.rnd }}" style="padding:20px;">
+                        {% for desc in context.how_to_guide[context.figs[loop.index - 1]] %}
+                        <div class="htg-entry-{{ context.rnd }}">
+                            <div class="htg-entry-conf-{{ context.rnd }}">{{ desc[0] }}</div>
+                            <div class="htg-entry-desc-{{ context.rnd }}">{{ desc[1] }}</div>
+                        </div>
+                        {% endfor %}
+                        <button class="htg-copy-btn-{{ context.rnd }}" onclick="copyParam(this)">Copy All Parameters</button>
+                    </div>
+                </div>
+                {% endif %}
+                {% if context.insights[context.figs[loop.index - 1]] %}
                 <div class="insight-container-{{ context.rnd }}">
                     <input type='checkbox' style='display: none' id="ib-{{ context.rnd }}-{{ loop.index0 }}" class="insight-check-{{ context.rnd }}">
                     <label class="insight-btn-{{ context.rnd }}" for="ib-{{ context.rnd }}-{{ loop.index0 }}"></label>
                     <div class="insight-panel-{{ context.rnd }}">
                         <ul>
-                            {% for insight in context.insights[context.meta[loop.index]] %}
+                            {% for insight in context.insights[context.figs[loop.index - 1]] %}
                             <li class="entry-{{ context.rnd }}">
                                 {{ insight }}
                             </li>
diff --git a/dataprep/eda/templates/tab_base_corr.html b/dataprep/eda/templates/tab_base_corr.html
index 57f872efa..ec30398e6 100644
--- a/dataprep/eda/templates/tab_base_corr.html
+++ b/dataprep/eda/templates/tab_base_corr.html
@@ -49,14 +49,29 @@
             {% endif %}
             {% for fig in context.components[1] %}
             <div class="plots-{{ context.rnd }}">
-                {% if context.insights[context.meta[loop.index]] %}
+                {% if context.how_to_guide[context.figs[loop.index - 1]] %}
+                <div class="insight-container-{{ context.rnd }}">
+                    <input type='checkbox' style='display: none' id="htgbt-{{ context.rnd }}-{{ loop.index0 }}" class="insight-check-{{ context.rnd }}">
+                    <label class="htg-btn-{{ context.rnd }}" for="htgbt-{{ context.rnd }}-{{ loop.index0 }}"></label>
+                    <div class="insight-panel-{{ context.rnd }}" style="padding:20px;">
+                        {% for desc in context.how_to_guide[context.figs[loop.index - 1]] %}
+                        <div class="htg-entry-{{ context.rnd }}">
+                            <div class="htg-entry-conf-{{ context.rnd }}">{{ desc[0] }}</div>
+                            <div class="htg-entry-desc-{{ context.rnd }}">{{ desc[1] }}</div>
+                        </div>
+                        {% endfor %}
+                        <button class="htg-copy-btn-{{ context.rnd }}" onclick="copyParam(this)">Copy All Parameters</button>
+                    </div>
+                </div>
+                {% endif %}
+                {% if context.insights[context.figs[loop.index - 1]] %}
                 <div class="insight-container-{{ context.rnd }}">
                     <input type='checkbox' style='display: none' id="ib-{{ context.rnd }}-{{ loop.index0 }}"
                         class="insight-check-{{ context.rnd }}">
                     <label class="insight-btn-{{ context.rnd }}" for="ib-{{ context.rnd }}-{{ loop.index0 }}"></label>
                     <div class="insight-panel-{{ context.rnd }}">
                         <ul>
-                            {% for insight in context.insights[context.meta[loop.index]] %}
+                            {% for insight in context.insights[context.figs[loop.index - 1]] %}
                             <li class="entry-{{ context.rnd }}">
                                 {{ insight }}
                             </li>
diff --git a/dataprep/eda/templates/tab_scripts.html b/dataprep/eda/templates/tab_scripts.html
index d2f4ccbd6..0162c399a 100644
--- a/dataprep/eda/templates/tab_scripts.html
+++ b/dataprep/eda/templates/tab_scripts.html
@@ -6,7 +6,9 @@
 
     function init() {
         const tableRows = document.querySelectorAll('.tabledata-{{ context.rnd }} tr');
+        {% if context.highlight %}
         highlightTableValue(tableRows, '#f00');
+        {% endif %}
         scientificNotationStrip(tableRows);
     }
 
@@ -59,4 +61,31 @@
             }
         }
     }
+
+    function copyParam(e) {
+        const buttonText = e.innerText;
+        const paramElement = e.parentElement.childNodes;
+        let param = '';
+        for (let i of paramElement) {
+            if (i.nodeName === 'DIV') {
+                const nodeParam = i.firstElementChild.innerText;
+                param = param + nodeParam + ', ';
+            }
+        }
+        navigator.clipboard.writeText(param).then(
+            () => {
+                e.innerText = 'Copied to Clipboard!';
+                setTimeout(() => {
+                    e.innerText = buttonText;
+                }, 1500);
+            }
+        ).catch(
+            error => {
+                e.innerText = error;
+                setTimeout(() => {
+                    e.innerText = buttonText;
+                }, 1500);
+            }
+        )
+    }
 </script>
\ No newline at end of file
diff --git a/dataprep/eda/templates/tab_styles.html b/dataprep/eda/templates/tab_styles.html
index 108119bec..026d5a6f1 100644
--- a/dataprep/eda/templates/tab_styles.html
+++ b/dataprep/eda/templates/tab_styles.html
@@ -115,11 +115,11 @@
         height: 20px;
         opacity: 0.5;
         cursor: pointer;
+        display: block;
         position: relative;
-        left: 385px;
+        left: 365px;
         bottom: 2px;
         margin: 0;
-        display: block;
         z-index: 1;
     }
 
@@ -179,4 +179,63 @@
         vertical-align: middle;
         color: #000000;
     }
+
+    .htg-btn-{{ context.rnd }} {
+        filter: invert(73%) sepia(44%) saturate(243%) hue-rotate(181deg) brightness(87%) contrast(87%);
+        background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='currentColor'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z' /%3E%3C/svg%3E");
+        background-size: 20px;
+        background-repeat: no-repeat;
+        width: 20px;
+        height: 20px;
+        opacity: 0.5;
+        cursor: pointer;
+        position: relative;
+        left: 385px;
+        bottom: 2px;
+        margin: 0;
+        display: block;
+        z-index: 1;
+    }
+
+    .htg-btn-{{ context.rnd }}:hover {
+        opacity: 1;
+    }
+
+    .htg-entry-{{ context.rnd }} {
+        border: 1px solid white;
+        border-radius: 5px;
+        background: rgba(0, 0, 0, 0.1);
+        color: #ffffffe0;
+        padding: 10px;
+        margin: 5px 0px;
+    }
+
+    .htg-entry-conf-{{ context.rnd }} {
+        font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
+        font-size: 85%;
+        background: rgb(187, 71, 103);
+        width: fit-content;
+        border: 1px solid white;
+        border-radius: 2px;
+        padding: 2px 5px;
+    }
+
+    .htg-entry-desc-{{ context.rnd }} {
+        font-family: sans-serif;
+        font-size: 110%;
+        color: #909599;
+        cursor: default;
+    }
+
+    .htg-copy-btn-{{ context.rnd }} {
+        border: 1px solid white;
+        border-radius: 5px;
+        margin: auto 25%;
+        width: 50%;
+        margin-top: 10px;
+    }
+
+    .htg-copy-btn-{{ context.rnd }}:hover {
+        background: #5DADE2;
+    }
 </style>
\ No newline at end of file


Test patch:
diff --git a/dataprep/tests/eda/test_config.py b/dataprep/tests/eda/test_config.py
index 827251652..3b75657c1 100644
--- a/dataprep/tests/eda/test_config.py
+++ b/dataprep/tests/eda/test_config.py
@@ -2,38 +2,38 @@
     This module for testing config parameter
 """
 
-from ...eda.basic.configs import BarChart, Histogram, WordCloud
-
-
-def test_hist() -> None:
-    dict_data = {"bins": 20, "agg": "mean", "value_range": [0.1, 0.5]}
-    histogram = Histogram()
-    histogram = histogram.from_dict(dict_data)
-    for key in dict_data.keys():
-        assert histogram.__dict__[key] == dict_data[key]
-
-
-def test_bar() -> None:
-    dict_data = {
-        "ngroups": 20,
-        "largest": False,
-        "nsubgroups": 10,
-        "top_words": 50,
-        "stopword": True,
-        "lemmatize": False,
-        "stem": False,
-        "sort_by": "alphabet",
-        "sort_ascending": False,
-    }
-    barchart = BarChart()
-    barchart = barchart.from_dict(dict_data)
-    for key in dict_data.keys():
-        assert barchart.__dict__[key] == dict_data[key]
-
-
-def test_word() -> None:
-    dict_data = {"top_words": 20, "stopword": False, "lemmatize": False, "stem": False}
-    wordcloud = WordCloud()
-    wordcloud = wordcloud.from_dict(dict_data)
-    for key in dict_data.keys():
-        assert wordcloud.__dict__[key] == dict_data[key]
+# from ...eda.basic.configs import BarChart, Histogram, WordCloud
+#
+#
+# def test_hist() -> None:
+#     dict_data = {"bins": 20, "agg": "mean", "value_range": [0.1, 0.5]}
+#     histogram = Histogram()
+#     histogram = histogram.from_dict(dict_data)
+#     for key in dict_data.keys():
+#         assert histogram.__dict__[key] == dict_data[key]
+#
+#
+# def test_bar() -> None:
+#     dict_data = {
+#         "ngroups": 20,
+#         "largest": False,
+#         "nsubgroups": 10,
+#         "top_words": 50,
+#         "stopword": True,
+#         "lemmatize": False,
+#         "stem": False,
+#         "sort_by": "alphabet",
+#         "sort_ascending": False,
+#     }
+#     barchart = BarChart()
+#     barchart = barchart.from_dict(dict_data)
+#     for key in dict_data.keys():
+#         assert barchart.__dict__[key] == dict_data[key]
+#
+#
+# def test_word() -> None:
+#     dict_data = {"top_words": 20, "stopword": False, "lemmatize": False, "stem": False}
+#     wordcloud = WordCloud()
+#     wordcloud = wordcloud.from_dict(dict_data)
+#     for key in dict_data.keys():
+#         assert wordcloud.__dict__[key] == dict_data[key]
diff --git a/dataprep/tests/eda/test_plot_correlation.py b/dataprep/tests/eda/test_plot_correlation.py
index 3e15d1508..54ca425bb 100644
--- a/dataprep/tests/eda/test_plot_correlation.py
+++ b/dataprep/tests/eda/test_plot_correlation.py
@@ -25,6 +25,7 @@
 )
 from ...eda.utils import to_dask
 from ...eda.data_array import DataArray
+from ...eda.configs import Config
 
 
 @pytest.fixture(scope="module")  # type: ignore
@@ -38,83 +39,103 @@ def simpledf() -> dd.DataFrame:
 
 
 def test_sanity_compute_1(simpledf: dd.DataFrame) -> None:
-    compute_correlation(simpledf)
+    display = ["Stats", "Pearson"]
+    config = {
+        "height": 500,
+        "width": 500,
+    }
+    cfg = Config.from_dict(display=display, config=config)
+    compute_correlation(simpledf, cfg=cfg)
     plot_correlation(simpledf)
 
 
 def test_sanity_compute_2(simpledf: dd.DataFrame) -> None:
-    compute_correlation(simpledf, k=1)
+    cfg = Config.from_dict()
+    compute_correlation(simpledf, cfg=cfg, k=1)
     plot_correlation(simpledf, k=1)
 
 
 def test_sanity_compute_3(simpledf: dd.DataFrame) -> None:
-    compute_correlation(simpledf, x="a")
+    cfg = Config.from_dict()
+    compute_correlation(simpledf, x="a", cfg=cfg)
     plot_correlation(simpledf, x="a")
 
 
 def test_sanity_compute_4(simpledf: dd.DataFrame) -> None:
-    compute_correlation(simpledf, x="a", value_range=(0.5, 0.8))
+    cfg = Config.from_dict()
+    compute_correlation(simpledf, x="a", cfg=cfg, value_range=(0.5, 0.8))
     plot_correlation(simpledf, x="a", value_range=(0.5, 0.8))
 
 
 def test_sanity_compute_5(simpledf: dd.DataFrame) -> None:
-    compute_correlation(simpledf, x="a", k=1)
+    cfg = Config.from_dict()
+    compute_correlation(simpledf, x="a", cfg=cfg, k=1)
     plot_correlation(simpledf, x="a", k=1)
 
 
 def test_sanity_compute_6(simpledf: dd.DataFrame) -> None:
-    compute_correlation(simpledf, x="a", k=0)
+    cfg = Config.from_dict()
+    compute_correlation(simpledf, x="a", cfg=cfg, k=0)
     plot_correlation(simpledf, x="a", k=0)
 
 
 def test_sanity_compute_7(simpledf: dd.DataFrame) -> None:
-    compute_correlation(simpledf, x="b", y="a")
+    cfg = Config.from_dict()
+    compute_correlation(simpledf, x="b", cfg=cfg, y="a")
     plot_correlation(simpledf, x="b", y="a")
 
 
 def test_sanity_compute_8(simpledf: dd.DataFrame) -> None:
-    compute_correlation(simpledf, x="b", y="a", k=1)
+    cfg = Config.from_dict()
+    compute_correlation(simpledf, x="b", cfg=cfg, y="a", k=1)
     plot_correlation(simpledf, x="b", y="a", k=1)
 
 
 def test_sanity_compute_9(simpledf: dd.DataFrame) -> None:
-    compute_correlation(simpledf, value_range=(0.3, 0.7))
+    cfg = Config.from_dict()
+    compute_correlation(simpledf, cfg=cfg, value_range=(0.3, 0.7))
     plot_correlation(simpledf, value_range=(0.3, 0.7))
 
 
 @pytest.mark.xfail  # type: ignore
 def test_sanity_compute_fail_2(simpledf: dd.DataFrame) -> None:
-    compute_correlation(simpledf, k=3, value_range=(0.3, 0.7))
+    cfg = Config.from_dict()
+    compute_correlation(simpledf, cfg=cfg, k=3, value_range=(0.3, 0.7))
     plot_correlation(simpledf, k=3, value_range=(0.3, 0.7))
 
 
 @pytest.mark.xfail  # type: ignore
 def test_sanity_compute_fail_3(simpledf: dd.DataFrame) -> None:
-    compute_correlation(simpledf, x="a", value_range=(0.5, 0.8), k=3)
+    cfg = Config.from_dict()
+    compute_correlation(simpledf, x="a", cfg=cfg, value_range=(0.5, 0.8), k=3)
     plot_correlation(simpledf, x="a", value_range=(0.5, 0.8), k=3)
 
 
 @pytest.mark.xfail  # type: ignore
 def test_sanity_compute_fail_4(simpledf: dd.DataFrame) -> None:
-    compute_correlation(simpledf, y="a")
+    cfg = Config.from_dict()
+    compute_correlation(simpledf, y="a", cfg=cfg)
     plot_correlation(simpledf, y="a")
 
 
 @pytest.mark.xfail  # type: ignore
 def test_sanity_compute_fail_5(simpledf: dd.DataFrame) -> None:
-    compute_correlation(simpledf, x="d")
+    cfg = Config.from_dict()
+    compute_correlation(simpledf, x="d", cfg=cfg)
     plot_correlation(simpledf, x="d")
 
 
 @pytest.mark.xfail  # type: ignore
 def test_test_sanity_compute_fail_6(simpledf: dd.DataFrame) -> None:
-    compute_correlation(simpledf, x="b", y="a", value_range=(0.5, 0.8))
+    cfg = Config.from_dict()
+    compute_correlation(simpledf, x="b", y="a", cfg=cfg, value_range=(0.5, 0.8))
     plot_correlation(simpledf, x="b", y="a", value_range=(0.5, 0.8))
 
 
 @pytest.mark.xfail  # type: ignore
 def test_sanity_compute_fail_7(simpledf: dd.DataFrame) -> None:
-    compute_correlation(simpledf, x="b", y="a", value_range=(0.5, 0.8), k=3)
+    cfg = Config.from_dict()
+    compute_correlation(simpledf, x="b", y="a", cfg=cfg, value_range=(0.5, 0.8), k=3)
     plot_correlation(simpledf, x="b", y="a", value_range=(0.5, 0.8), k=3)
 
 
diff --git a/dataprep/tests/eda/test_plot_missing.py b/dataprep/tests/eda/test_plot_missing.py
index 23376d090..dbc048f77 100644
--- a/dataprep/tests/eda/test_plot_missing.py
+++ b/dataprep/tests/eda/test_plot_missing.py
@@ -9,6 +9,7 @@
 from ...eda.dtypes import Numerical
 from ...eda.missing import compute_missing, render_missing, plot_missing
 from ...eda.utils import to_dask
+from ...eda.configs import Config
 
 
 @pytest.fixture(scope="module")  # type: ignore
@@ -28,51 +29,83 @@ def simpledf() -> dd.DataFrame:
 
 
 def test_sanity_compute_1(simpledf: dd.DataFrame) -> None:
-    itmdt = compute_missing(simpledf)
-    render_missing(itmdt)
+    display = ["Stats", "Bar Chart", "Spectrum"]
+    config = {
+        "spectrum.bins": 10,
+        "height": 500,
+        "width": 500,
+    }
+    cfg = Config.from_dict(display=display, config=config)
+
+    itmdt = compute_missing(simpledf, cfg=cfg)
+    render_missing(itmdt, cfg)
 
 
 def test_sanity_compute_2(simpledf: dd.DataFrame) -> None:
-    itmdt = compute_missing(simpledf, x="a")
-    render_missing(itmdt)
+    config = {"hist.bins": 20, "bar.bars": 15}
+    cfg = Config.from_dict(config=config)
+
+    itmdt = compute_missing(simpledf, x="a", cfg=cfg)
+    render_missing(itmdt, cfg)
 
 
 def test_sanity_compute_3(simpledf: dd.DataFrame) -> None:
-    itmdt = compute_missing(simpledf, x="d")
-    render_missing(itmdt)
+    config = {"hist.bins": 20, "bar.bars": 15}
+    cfg = Config.from_dict(config=config)
+    itmdt = compute_missing(simpledf, x="d", cfg=cfg)
+    render_missing(itmdt, cfg)
 
 
 def test_sanity_compute_4(simpledf: dd.DataFrame) -> None:
-    itmdt = compute_missing(simpledf, x="a", y="b")
-    render_missing(itmdt)
+    display = ["Histogram", "PDF"]
+    config = {
+        "hist.bins": 30,
+        "hist.yscale": "linear",
+        "height": 500,
+        "width": 500,
+    }
+    cfg = Config.from_dict(display=display, config=config)
+    itmdt = compute_missing(simpledf, x="a", y="b", cfg=cfg)
+    render_missing(itmdt, cfg)
 
 
 def test_sanity_compute_5(simpledf: dd.DataFrame) -> None:
-    itmdt = compute_missing(simpledf, x="a", y="d")
-    render_missing(itmdt)
+    display = ["Histogram", "PDF"]
+    config = {
+        "hist.bins": 30,
+        "hist.yscale": "linear",
+        "height": 500,
+        "width": 500,
+    }
+    cfg = Config.from_dict(display=display, config=config)
+    itmdt = compute_missing(simpledf, x="a", y="d", cfg=cfg)
+    render_missing(itmdt, cfg)
 
 
 def test_specify_column_type(simpledf: dd.DataFrame) -> None:
-    itmdt = compute_missing(simpledf, x="b", dtype={"a": Numerical()})
-    render_missing(itmdt)
+    cfg = Config.from_dict()
+    itmdt = compute_missing(simpledf, x="b", cfg=cfg, dtype={"a": Numerical()})
+    render_missing(itmdt, cfg)
 
 
 @pytest.mark.xfail  # type: ignore
 def test_sanity_compute_6(simpledf: dd.DataFrame) -> None:
-    compute_missing(simpledf, y="b")
+    cfg = Config.from_dict()
+    compute_missing(simpledf, y="b", cfg=cfg)
 
 
 def test_sanity_compute_7() -> None:
     df = pd.DataFrame([[1, 2, 3]])
     simpledf = to_dask(df)
-    itmdt = compute_missing(simpledf)
-    render_missing(itmdt)
+    cfg = Config.from_dict()
+    itmdt = compute_missing(simpledf, cfg=cfg)
+    render_missing(itmdt, cfg)
 
 
 def test_no_missing() -> None:
     from sys import platform
 
-    if platform == "darwin":
+    if platform == "darwin" or platform == "win32":
         import matplotlib
 
         matplotlib.use("PS")