Repository: great-expectations/great_expectations

Issue:
Use cleaner solution for non-truncating division in python 2
Prefer `from __future__ import division` to `1.*x/y`


Discussion:
Done inline during improve_partitions push.

Resolving patch:
diff --git a/great_expectations/rule_based_profiler/helpers/util.py b/great_expectations/rule_based_profiler/helpers/util.py
--- a/great_expectations/rule_based_profiler/helpers/util.py
+++ b/great_expectations/rule_based_profiler/helpers/util.py
@@ -715,6 +715,7 @@ def get_validator_with_expectation_suite(
     expectation_suite: Optional["ExpectationSuite"] = None,  # noqa: F821
     expectation_suite_name: Optional[str] = None,
     component_name: str = "test",
+    persist: bool = False,
 ) -> "Validator":  # noqa: F821
     """
     Instantiates and returns "Validator" object using "data_context", "batch_request", and other available information.
@@ -729,6 +730,7 @@ def get_validator_with_expectation_suite(
         expectation_suite=expectation_suite,
         expectation_suite_name=expectation_suite_name,
         component_name=component_name,
+        persist=persist,
     )
 
     batch_request = materialize_batch_request(batch_request=batch_request)
@@ -745,6 +747,7 @@ def get_or_create_expectation_suite(
     expectation_suite: Optional["ExpectationSuite"] = None,  # noqa: F821
     expectation_suite_name: Optional[str] = None,
     component_name: Optional[str] = None,
+    persist: bool = False,
 ) -> "ExpectationSuite":  # noqa: F821
     """
     Use "expectation_suite" if provided.  If not, then if "expectation_suite_name" is specified, then create
@@ -777,17 +780,23 @@ def get_or_create_expectation_suite(
         expectation_suite_name = f"{TEMPORARY_EXPECTATION_SUITE_NAME_PREFIX}.{component_name}.{TEMPORARY_EXPECTATION_SUITE_NAME_STEM}.{str(uuid.uuid4())[:8]}"
 
     if create_expectation_suite:
-        try:
-            # noinspection PyUnusedLocal
-            expectation_suite = data_context.get_expectation_suite(
-                expectation_suite_name=expectation_suite_name
-            )
-        except ge_exceptions.DataContextError:
-            expectation_suite = data_context.create_expectation_suite(
-                expectation_suite_name=expectation_suite_name
-            )
-            logger.info(
-                f'Created ExpectationSuite "{expectation_suite.expectation_suite_name}".'
+        if persist:
+            try:
+                # noinspection PyUnusedLocal
+                expectation_suite = data_context.get_expectation_suite(
+                    expectation_suite_name=expectation_suite_name
+                )
+            except ge_exceptions.DataContextError:
+                expectation_suite = data_context.create_expectation_suite(
+                    expectation_suite_name=expectation_suite_name
+                )
+                logger.info(
+                    f'Created ExpectationSuite "{expectation_suite.expectation_suite_name}".'
+                )
+        else:
+            expectation_suite = ExpectationSuite(
+                expectation_suite_name=expectation_suite_name,
+                data_context=data_context,
             )
 
     return expectation_suite
diff --git a/great_expectations/rule_based_profiler/types/data_assistant_result/data_assistant_result.py b/great_expectations/rule_based_profiler/types/data_assistant_result/data_assistant_result.py
--- a/great_expectations/rule_based_profiler/types/data_assistant_result/data_assistant_result.py
+++ b/great_expectations/rule_based_profiler/types/data_assistant_result/data_assistant_result.py
@@ -6,10 +6,13 @@
 import pandas as pd
 from IPython.display import HTML, display
 
-from great_expectations.core import ExpectationConfiguration
+from great_expectations.core import ExpectationConfiguration, ExpectationSuite
 from great_expectations.core.util import convert_to_json_serializable, nested_update
 from great_expectations.execution_engine.execution_engine import MetricDomainTypes
-from great_expectations.rule_based_profiler.helpers.util import sanitize_parameter_name
+from great_expectations.rule_based_profiler.helpers.util import (
+    get_or_create_expectation_suite,
+    sanitize_parameter_name,
+)
 from great_expectations.rule_based_profiler.types import (
     FULLY_QUALIFIED_PARAMETER_NAME_ATTRIBUTED_VALUE_KEY,
     FULLY_QUALIFIED_PARAMETER_NAME_METADATA_KEY,
@@ -68,160 +71,367 @@ class DataAssistantResult(SerializableDictDot):
     citation: Optional[dict] = None
     execution_time: Optional[float] = None  # Execution time (in seconds).
 
-    @staticmethod
-    def get_quantitative_metric_chart(
-        df: pd.DataFrame,
-        metric_name: str,
-        metric_type: alt.StandardType,
-        sequential: bool,
-        subtitle: Optional[str] = None,
-    ) -> alt.Chart:
+    def to_dict(self) -> dict:
         """
-        Args:
-            df: A pandas dataframe containing the data to be plotted
-            metric_name: The name of the metric as it exists in the pandas dataframe
-            metric_type: The altair data type for the metric being plotted
-            sequential: Whether batches are sequential in nature
-            subtitle: The subtitle, if applicable
+        Returns: This DataAssistantResult as dictionary (JSON-serializable dictionary for DataAssistantResult objects).
+        """
+        domain: Domain
+        parameter_values_for_fully_qualified_parameter_names: Dict[str, ParameterNode]
+        expectation_configuration: ExpectationConfiguration
+        return {
+            "batch_id_to_batch_identifier_display_name_map": convert_to_json_serializable(
+                data=self.batch_id_to_batch_identifier_display_name_map
+            ),
+            "profiler_config": self.profiler_config.to_json_dict(),
+            "metrics_by_domain": [
+                {
+                    "domain_id": domain.id,
+                    "domain": domain.to_json_dict(),
+                    "parameter_values_for_fully_qualified_parameter_names": convert_to_json_serializable(
+                        data=parameter_values_for_fully_qualified_parameter_names
+                    ),
+                }
+                for domain, parameter_values_for_fully_qualified_parameter_names in self.metrics_by_domain.items()
+            ],
+            "expectation_configurations": [
+                expectation_configuration.to_json_dict()
+                for expectation_configuration in self.expectation_configurations
+            ],
+            "execution_time": convert_to_json_serializable(data=self.execution_time),
+        }
 
-        Returns:
-            An altair line chart
+    def to_json_dict(self) -> dict:
         """
-        metric_component: MetricPlotComponent = MetricPlotComponent(
-            name=metric_name, alt_type=metric_type
-        )
+        Returns: This DataAssistantResult as JSON-serializable dictionary.
+        """
+        return self.to_dict()
 
-        batch_name: str = "batch"
-        batch_identifiers: List[str] = [
-            column for column in df.columns if column not in [metric_name, batch_name]
-        ]
-        batch_component: BatchPlotComponent = BatchPlotComponent(
-            name=batch_name,
-            alt_type=AltairDataTypes.NOMINAL.value,
-            batch_identifiers=batch_identifiers,
+    def get_expectation_suite(self, expectation_suite_name: str) -> ExpectationSuite:
+        """
+        Returns: "ExpectationSuite" object, built from properties, populated into this "DataAssistantResult" object.
+        """
+        expectation_suite: ExpectationSuite = get_or_create_expectation_suite(
+            data_context=None,
+            expectation_suite=None,
+            expectation_suite_name=expectation_suite_name,
+            component_name=self.__class__.__name__,
+            persist=False,
         )
-
-        domain_component: DomainPlotComponent = DomainPlotComponent(
-            name=None,
-            alt_type=None,
-            subtitle=subtitle,
+        expectation_suite.add_expectation_configurations(
+            expectation_configurations=self.expectation_configurations,
+            send_usage_event=False,
+            match_type="domain",
+            overwrite_existing=True,
         )
+        expectation_suite.add_citation(
+            **self.citation,
+        )
+        return expectation_suite
 
-        if sequential:
-            return DataAssistantResult._get_line_chart(
-                df=df,
-                metric_component=metric_component,
-                batch_component=batch_component,
-                domain_component=domain_component,
-            )
-        else:
-            return DataAssistantResult._get_bar_chart(
-                df=df,
-                metric_component=metric_component,
-                batch_component=batch_component,
-                domain_component=domain_component,
-            )
+    def get_attributed_metrics_by_domain(
+        self,
+    ) -> Dict[Domain, Dict[str, ParameterNode]]:
+        domain: Domain
+        parameter_values_for_fully_qualified_parameter_names: Dict[str, ParameterNode]
+        fully_qualified_parameter_name: str
+        parameter_value: ParameterNode
+        metrics_attributed_values_by_domain: Dict[Domain, Dict[str, ParameterNode]] = {
+            domain: {
+                parameter_value[
+                    FULLY_QUALIFIED_PARAMETER_NAME_METADATA_KEY
+                ].metric_configuration.metric_name: parameter_value[
+                    FULLY_QUALIFIED_PARAMETER_NAME_ATTRIBUTED_VALUE_KEY
+                ]
+                for fully_qualified_parameter_name, parameter_value in parameter_values_for_fully_qualified_parameter_names.items()
+            }
+            for domain, parameter_values_for_fully_qualified_parameter_names in self.metrics_by_domain.items()
+        }
 
-    @staticmethod
-    def _get_line_chart(
-        df: pd.DataFrame,
-        metric_component: MetricPlotComponent,
-        batch_component: BatchPlotComponent,
-        domain_component: DomainPlotComponent,
-    ) -> alt.Chart:
-        title: alt.TitleParams = determine_plot_title(
-            metric_plot_component=metric_component,
-            batch_plot_component=batch_component,
-            domain_plot_component=domain_component,
-        )
+        return metrics_attributed_values_by_domain
 
-        tooltip: List[alt.Tooltip] = batch_component.generate_tooltip() + [
-            metric_component.generate_tooltip(format=","),
-        ]
+    def plot_metrics(
+        self,
+        sequential: bool = True,
+        theme: Optional[Dict[str, Any]] = None,
+        include_column_names: Optional[List[str]] = None,
+        exclude_column_names: Optional[List[str]] = None,
+    ) -> PlotResult:
+        """
+        Use contents of "DataAssistantResult" object to display metrics for visualization purposes.
 
-        line: alt.Chart = (
-            alt.Chart(data=df, title=title)
-            .mark_line()
-            .encode(
-                x=batch_component.plot_on_axis(),
-                y=metric_component.plot_on_axis(),
-                tooltip=tooltip,
-            )
-        )
+        Altair theme configuration reference:
+            https://altair-viz.github.io/user_guide/configuration.html#top-level-chart-configuration
 
-        points: alt.Chart = (
-            alt.Chart(data=df, title=title)
-            .mark_point()
-            .encode(
-                x=batch_component.plot_on_axis(),
-                y=metric_component.plot_on_axis(),
-                tooltip=tooltip,
-            )
+        Args:
+            sequential: Whether the batches are sequential or not
+            theme: Altair top-level chart configuration dictionary
+            include_column_names: Columns to include in metrics plot
+            exclude_column_names: Columns to exclude from metrics plot
+
+        Returns:
+            PlotResult wrapper object around Altair charts.
+        """
+        return self._plot(
+            plot_mode=PlotMode.DESCRIPTIVE,
+            sequential=sequential,
+            theme=theme,
+            include_column_names=include_column_names,
+            exclude_column_names=exclude_column_names,
         )
 
-        return line + points
+    def plot_expectations_and_metrics(
+        self,
+        sequential: bool = True,
+        theme: Optional[Dict[str, Any]] = None,
+        include_column_names: Optional[List[str]] = None,
+        exclude_column_names: Optional[List[str]] = None,
+    ) -> PlotResult:
+        """
+        Use contents of "DataAssistantResult" object to display metrics and expectations for visualization purposes.
 
-    @staticmethod
-    def _get_bar_chart(
-        df: pd.DataFrame,
-        metric_component: MetricPlotComponent,
-        batch_component: BatchPlotComponent,
-        domain_component: DomainPlotComponent,
-    ) -> alt.Chart:
-        title: alt.TitleParams = determine_plot_title(
-            metric_plot_component=metric_component,
-            batch_plot_component=batch_component,
-            domain_plot_component=domain_component,
-        )
+        Altair theme configuration reference:
+            https://altair-viz.github.io/user_guide/configuration.html#top-level-chart-configuration
 
-        tooltip: List[alt.Tooltip] = batch_component.generate_tooltip() + [
-            metric_component.generate_tooltip(format=","),
-        ]
+        Args:
+            sequential: Whether the batches are sequential or not
+            theme: Altair top-level chart configuration dictionary
+            include_column_names: Columns to include in expectations and metrics plot
+            exclude_column_names: Columns to exclude from expectations and metrics plot
 
-        bars: alt.Chart = (
-            alt.Chart(data=df, title=title)
-            .mark_bar()
-            .encode(
-                x=alt.X(
-                    batch_component.name,
-                    type=batch_component.alt_type,
-                    title=batch_component.title,
-                    axis=alt.Axis(labels=False, grid=False),
-                ),
-                y=metric_component.plot_on_axis(),
-                tooltip=tooltip,
-            )
+        Returns:
+            PlotResult wrapper object around Altair charts.
+        """
+        return self._plot(
+            plot_mode=PlotMode.PRESCRIPTIVE,
+            sequential=sequential,
+            theme=theme,
+            include_column_names=include_column_names,
+            exclude_column_names=exclude_column_names,
         )
 
-        return bars
-
-    @staticmethod
-    def get_expect_domain_values_to_be_between_chart(
-        df: pd.DataFrame,
-        metric_name: str,
-        metric_type: alt.StandardType,
+    def _plot(
+        self,
+        plot_mode: PlotMode,
         sequential: bool,
-        subtitle: Optional[str],
-    ) -> alt.Chart:
+        theme: Optional[Dict[str, Any]] = None,
+        include_column_names: Optional[List[str]] = None,
+        exclude_column_names: Optional[List[str]] = None,
+    ) -> PlotResult:
         """
+        VolumeDataAssistant-specific plots are defined with Altair and passed to "display()" for presentation.
+        Display Charts are condensed and interactive while Return Charts are separated into an individual chart for
+        each metric-domain/expectation-domain combination.
+
+        Altair theme configuration reference:
+            https://altair-viz.github.io/user_guide/configuration.html#top-level-chart-configuration
+
         Args:
-            df: A pandas dataframe containing the data to be plotted
-            metric_name: The name of the metric as it exists in the pandas dataframe
-            metric_type: The altair data type for the metric being plotted
+            plot_mode: Type of plot to generate, prescriptive or descriptive
             sequential: Whether batches are sequential in nature
-            subtitle: The subtitle, if applicable
+            theme: Altair top-level chart configuration dictionary
+            include_column_names: A list of columns to chart
+            exclude_column_names: A list of columns not to chart
 
         Returns:
-            An altair line chart with confidence intervals corresponding to "between" expectations
+            A PlotResult object consisting of an individual chart for each metric-domain/expectation-domain
         """
+        if include_column_names is not None and exclude_column_names is not None:
+            raise ValueError(
+                "You may either use `include_column_names` or `exclude_column_names` (but not both)."
+            )
 
-        if subtitle:
-            domain_type: MetricDomainTypes = MetricDomainTypes.COLUMN
-        else:
-            domain_type: MetricDomainTypes = MetricDomainTypes.TABLE
-
-        column_name: str = "column"
-        batch_name: str = "batch"
+        display_charts: Union[
+            List[alt.Chart], List[alt.LayerChart], List[alt.VConcatChart]
+        ] = []
+        return_charts: Union[List[alt.Chart], List[alt.LayerChart]] = []
+
+        expectation_configurations: List[
+            ExpectationConfiguration
+        ] = self.expectation_configurations
+
+        table_domain_charts: List[
+            Union[List[alt.Chart], List[alt.LayerChart]]
+        ] = self._plot_table_domain_charts(
+            expectation_configurations=expectation_configurations,
+            plot_mode=plot_mode,
+            sequential=sequential,
+        )
+        display_charts.extend(table_domain_charts)
+        return_charts.extend(table_domain_charts)
+
+        column_domain_display_chart: List[alt.VConcatChart]
+        column_domain_return_charts: List[alt.Chart]
+        (
+            column_domain_display_charts,
+            column_domain_return_charts,
+        ) = self._plot_column_domain_charts(
+            expectation_configurations=expectation_configurations,
+            plot_mode=plot_mode,
+            sequential=sequential,
+            include_column_names=include_column_names,
+            exclude_column_names=exclude_column_names,
+        )
+        display_charts.extend(column_domain_display_charts)
+        return_charts.extend(column_domain_return_charts)
+
+        self.display(charts=display_charts, theme=theme)
+
+        return_charts = self.apply_theme(charts=return_charts, theme=theme)
+        return PlotResult(charts=return_charts)
+
+    @staticmethod
+    def display(
+        charts: Union[List[alt.Chart], List[alt.VConcatChart]],
+        theme: Optional[Dict[str, Any]],
+    ) -> None:
+        """
+        Display each chart passed by DataAssistantResult.plot()
+
+        Altair theme configuration reference:
+            https://altair-viz.github.io/user_guide/configuration.html#top-level-chart-configuration
+
+        Args:
+            charts: A list of Altair chart objects to display
+            theme: An Optional Altair top-level chart configuration dictionary to apply over the default theme
+        """
+        altair_theme: Dict[str, Any]
+        if theme:
+            altair_theme = DataAssistantResult._get_theme(theme=theme)
+        else:
+            altair_theme = copy.deepcopy(AltairThemes.DEFAULT_THEME.value)
+
+        themed_charts: List[alt.chart] = DataAssistantResult.apply_theme(
+            charts=charts, theme=altair_theme
+        )
+
+        # Altair does not have a way to format the dropdown input so the rendered CSS must be altered directly
+        dropdown_title_color: str = altair_theme["legend"]["titleColor"]
+        dropdown_title_font: str = altair_theme["font"]
+        dropdown_css: str = f"""
+            <style>
+            span.vega-bind-name {{
+                color: {dropdown_title_color};
+                font-family: "{dropdown_title_font}";
+                font-weight: bold;
+            }}
+            form.vega-bindings {{
+              position: absolute;
+              left: 75px;
+              top: 30px;
+            }}
+            </style>
+        """
+        display(HTML(dropdown_css))
+
+        # max rows for Altair charts is set to 5,000 without this
+        alt.data_transformers.disable_max_rows()
+
+        chart: alt.Chart
+        for chart in themed_charts:
+            chart.display()
+
+    @staticmethod
+    def apply_theme(
+        charts: List[alt.Chart],
+        theme: Optional[Dict[str, Any]],
+    ) -> List[alt.Chart]:
+        """
+        Apply the Great Expectations default theme and any user-provided theme overrides to each chart
+
+        Altair theme configuration reference:
+            https://altair-viz.github.io/user_guide/configuration.html#top-level-chart-configuration
+
+        Args:
+            charts: A list of Altair chart objects to apply a theme to
+            theme: An Optional Altair top-level chart configuration dictionary to apply over the base_theme
+
+        Returns:
+            A list of Altair charts with the theme applied
+        """
+        theme: Dict[str, Any] = DataAssistantResult._get_theme(theme=theme)
+        return [chart.configure(**theme) for chart in charts]
+
+    @staticmethod
+    def get_quantitative_metric_chart(
+        df: pd.DataFrame,
+        metric_name: str,
+        metric_type: alt.StandardType,
+        sequential: bool,
+        subtitle: Optional[str] = None,
+    ) -> alt.Chart:
+        """
+        Args:
+            df: A pandas dataframe containing the data to be plotted
+            metric_name: The name of the metric as it exists in the pandas dataframe
+            metric_type: The altair data type for the metric being plotted
+            sequential: Whether batches are sequential in nature
+            subtitle: The subtitle, if applicable
+
+        Returns:
+            An altair line chart
+        """
+        metric_component: MetricPlotComponent = MetricPlotComponent(
+            name=metric_name, alt_type=metric_type
+        )
+
+        batch_name: str = "batch"
+        batch_identifiers: List[str] = [
+            column for column in df.columns if column not in [metric_name, batch_name]
+        ]
+        batch_component: BatchPlotComponent = BatchPlotComponent(
+            name=batch_name,
+            alt_type=AltairDataTypes.NOMINAL.value,
+            batch_identifiers=batch_identifiers,
+        )
+
+        domain_component: DomainPlotComponent = DomainPlotComponent(
+            name=None,
+            alt_type=None,
+            subtitle=subtitle,
+        )
+
+        if sequential:
+            return DataAssistantResult._get_line_chart(
+                df=df,
+                metric_component=metric_component,
+                batch_component=batch_component,
+                domain_component=domain_component,
+            )
+        else:
+            return DataAssistantResult._get_bar_chart(
+                df=df,
+                metric_component=metric_component,
+                batch_component=batch_component,
+                domain_component=domain_component,
+            )
+
+    @staticmethod
+    def get_expect_domain_values_to_be_between_chart(
+        df: pd.DataFrame,
+        metric_name: str,
+        metric_type: alt.StandardType,
+        sequential: bool,
+        subtitle: Optional[str],
+    ) -> alt.Chart:
+        """
+        Args:
+            df: A pandas dataframe containing the data to be plotted
+            metric_name: The name of the metric as it exists in the pandas dataframe
+            metric_type: The altair data type for the metric being plotted
+            sequential: Whether batches are sequential in nature
+            subtitle: The subtitle, if applicable
+
+        Returns:
+            An altair line chart with confidence intervals corresponding to "between" expectations
+        """
+
+        if subtitle:
+            domain_type: MetricDomainTypes = MetricDomainTypes.COLUMN
+        else:
+            domain_type: MetricDomainTypes = MetricDomainTypes.TABLE
+
+        column_name: str = "column"
+        batch_name: str = "batch"
         max_value: str = "max_value"
         min_value: str = "min_value"
         strict_max: str = "strict_max"
@@ -303,36 +513,329 @@ def get_expect_domain_values_to_be_between_chart(
                 alt_type=None,
                 subtitle=subtitle,
             )
-            tooltip = batch_component.generate_tooltip() + [
-                min_value_component.generate_tooltip(format=","),
-                max_value_component.generate_tooltip(format=","),
-                metric_component.generate_tooltip(format=","),
-            ]
+            tooltip = batch_component.generate_tooltip() + [
+                min_value_component.generate_tooltip(format=","),
+                max_value_component.generate_tooltip(format=","),
+                metric_component.generate_tooltip(format=","),
+            ]
+
+        if sequential:
+            return (
+                DataAssistantResult._get_expect_domain_values_to_be_between_line_chart(
+                    df=df,
+                    metric_component=metric_component,
+                    batch_component=batch_component,
+                    domain_component=domain_component,
+                    min_value_component=min_value_component,
+                    max_value_component=max_value_component,
+                    tooltip=tooltip,
+                )
+            )
+        else:
+            return (
+                DataAssistantResult._get_expect_domain_values_to_be_between_bar_chart(
+                    df=df,
+                    metric_component=metric_component,
+                    batch_component=batch_component,
+                    domain_component=domain_component,
+                    min_value_component=min_value_component,
+                    max_value_component=max_value_component,
+                    tooltip=tooltip,
+                )
+            )
+
+    @staticmethod
+    def get_interactive_detail_multi_chart(
+        column_dfs: List[Tuple[str, pd.DataFrame]],
+        metric_name: str,
+        metric_type: alt.StandardType,
+        sequential: bool,
+    ) -> Union[alt.Chart, alt.VConcatChart]:
+        """
+        Args:
+            column_dfs: A list of tuples pairing pandas dataframes with the columns they correspond to
+            metric_name: The name of the metric as it exists in the pandas dataframe
+            metric_type: The altair data type for the metric being plotted
+            sequential: Whether batches are sequential in nature
+
+        Returns:
+            A interactive detail altair multi-chart
+        """
+        batch_name: str = "batch"
+        batch_identifiers: List[str] = [
+            column
+            for column in column_dfs[0][1].columns
+            if column not in [metric_name, batch_name]
+        ]
+        batch_component: BatchPlotComponent = BatchPlotComponent(
+            name=batch_name,
+            alt_type=AltairDataTypes.NOMINAL.value,
+            batch_identifiers=batch_identifiers,
+        )
+        metric_component: MetricPlotComponent = MetricPlotComponent(
+            name=metric_name, alt_type=metric_type
+        )
+
+        domain_name: str = "column"
+        domain_component: DomainPlotComponent = DomainPlotComponent(
+            name=domain_name,
+            alt_type=AltairDataTypes.NOMINAL.value,
+        )
+
+        df: pd.DataFrame = pd.DataFrame(
+            columns=[batch_name, metric_name] + batch_identifiers
+        )
+        for column, column_df in column_dfs:
+            column_df[domain_name] = column
+            df = pd.concat([df, column_df], axis=0)
+
+        if sequential:
+            return DataAssistantResult._get_interactive_detail_multi_line_chart(
+                df=df,
+                metric_component=metric_component,
+                batch_component=batch_component,
+                domain_component=domain_component,
+            )
+        else:
+            return DataAssistantResult._get_interactive_detail_multi_bar_chart(
+                df=df,
+                metric_component=metric_component,
+                batch_component=batch_component,
+                domain_component=domain_component,
+            )
+
+    @staticmethod
+    def get_interactive_detail_expect_column_values_to_be_between_chart(
+        column_dfs: List[Tuple[str, pd.DataFrame]],
+        metric_name: str,
+        metric_type: alt.StandardType,
+        sequential: bool,
+    ) -> alt.VConcatChart:
+        """
+        Args:
+            column_dfs: A list of tuples pairing pandas dataframes with the columns they correspond to
+            metric_name: The name of the metric as it exists in the pandas dataframe
+            metric_type: The altair data type for the metric being plotted
+            sequential: Whether batches are sequential in nature
+
+        Returns:
+            An interactive detail multi line expect_column_values_to_be_between chart
+        """
+        column_name: str = "column"
+        min_value: str = "min_value"
+        max_value: str = "max_value"
+        strict_min: str = "strict_min"
+        strict_max: str = "strict_max"
+
+        batch_name: str = "batch"
+        batch_identifiers: List[str] = [
+            column
+            for column in column_dfs[0][1].columns
+            if column
+            not in [
+                metric_name,
+                batch_name,
+                column_name,
+                min_value,
+                max_value,
+                strict_min,
+                strict_max,
+            ]
+        ]
+        batch_type: alt.StandardType = AltairDataTypes.NOMINAL.value
+        batch_component: BatchPlotComponent = BatchPlotComponent(
+            name=batch_name,
+            alt_type=batch_type,
+            batch_identifiers=batch_identifiers,
+        )
+        metric_component: MetricPlotComponent = MetricPlotComponent(
+            name=metric_name, alt_type=metric_type
+        )
+
+        domain_component: DomainPlotComponent = DomainPlotComponent(
+            name="column",
+            alt_type=AltairDataTypes.NOMINAL.value,
+        )
+
+        min_value_component: ExpectationKwargPlotComponent = (
+            ExpectationKwargPlotComponent(
+                name=min_value,
+                alt_type=AltairDataTypes.QUANTITATIVE.value,
+                metric_plot_component=metric_component,
+            )
+        )
+        max_value_component: ExpectationKwargPlotComponent = (
+            ExpectationKwargPlotComponent(
+                name=max_value,
+                alt_type=AltairDataTypes.QUANTITATIVE.value,
+                metric_plot_component=metric_component,
+            )
+        )
+        strict_min_component: ExpectationKwargPlotComponent = (
+            ExpectationKwargPlotComponent(
+                name=strict_min,
+                alt_type=AltairDataTypes.NOMINAL.value,
+                metric_plot_component=metric_component,
+            )
+        )
+        strict_max_component: ExpectationKwargPlotComponent = (
+            ExpectationKwargPlotComponent(
+                name=strict_max,
+                alt_type=AltairDataTypes.NOMINAL.value,
+                metric_plot_component=metric_component,
+            )
+        )
+
+        df: pd.DataFrame = pd.DataFrame(
+            columns=[
+                batch_name,
+            ]
+            + batch_identifiers
+            + [
+                metric_name,
+                column_name,
+                min_value,
+                max_value,
+                strict_min,
+                strict_max,
+            ]
+        )
+
+        for _, column_df in column_dfs:
+            df = pd.concat([df, column_df], axis=0)
+
+        # encode point color based on anomalies
+        predicate: Union[bool, int]
+        if strict_min and strict_max:
+            predicate = (
+                (alt.datum.min_value > alt.datum[metric_component.name])
+                & (alt.datum.max_value > alt.datum[metric_component.name])
+            ) | (
+                (alt.datum.min_value < alt.datum[metric_component.name])
+                & (alt.datum.max_value < alt.datum[metric_component.name])
+            )
+        elif strict_min:
+            predicate = (
+                (alt.datum.min_value > alt.datum[metric_component.name])
+                & (alt.datum.max_value >= alt.datum[metric_component.name])
+            ) | (
+                (alt.datum.min_value < alt.datum[metric_component.name])
+                & (alt.datum.max_value <= alt.datum[metric_component.name])
+            )
+        elif strict_max:
+            predicate = (
+                (alt.datum.min_value >= alt.datum[metric_component.name])
+                & (alt.datum.max_value > alt.datum[metric_component.name])
+            ) | (
+                (alt.datum.min_value <= alt.datum[metric_component.name])
+                & (alt.datum.max_value < alt.datum[metric_component.name])
+            )
+        else:
+            predicate = (
+                (alt.datum.min_value >= alt.datum[metric_component.name])
+                & (alt.datum.max_value >= alt.datum[metric_component.name])
+            ) | (
+                (alt.datum.min_value <= alt.datum[metric_component.name])
+                & (alt.datum.max_value <= alt.datum[metric_component.name])
+            )
+
+        if sequential:
+            return DataAssistantResult._get_interactive_detail_expect_column_values_to_be_between_line_chart(
+                df=df,
+                metric_component=metric_component,
+                batch_component=batch_component,
+                domain_component=domain_component,
+                min_value_component=min_value_component,
+                max_value_component=max_value_component,
+                strict_min_component=strict_min_component,
+                strict_max_component=strict_max_component,
+                predicate=predicate,
+            )
+        else:
+            return DataAssistantResult._get_interactive_detail_expect_column_values_to_be_between_bar_chart(
+                df=df,
+                metric_component=metric_component,
+                batch_component=batch_component,
+                domain_component=domain_component,
+                min_value_component=min_value_component,
+                max_value_component=max_value_component,
+                strict_min_component=strict_min_component,
+                strict_max_component=strict_max_component,
+                predicate=predicate,
+            )
+
+    @staticmethod
+    def _get_line_chart(
+        df: pd.DataFrame,
+        metric_component: MetricPlotComponent,
+        batch_component: BatchPlotComponent,
+        domain_component: DomainPlotComponent,
+    ) -> alt.Chart:
+        title: alt.TitleParams = determine_plot_title(
+            metric_plot_component=metric_component,
+            batch_plot_component=batch_component,
+            domain_plot_component=domain_component,
+        )
+
+        tooltip: List[alt.Tooltip] = batch_component.generate_tooltip() + [
+            metric_component.generate_tooltip(format=","),
+        ]
+
+        line: alt.Chart = (
+            alt.Chart(data=df, title=title)
+            .mark_line()
+            .encode(
+                x=batch_component.plot_on_axis(),
+                y=metric_component.plot_on_axis(),
+                tooltip=tooltip,
+            )
+        )
 
-        if sequential:
-            return (
-                DataAssistantResult._get_expect_domain_values_to_be_between_line_chart(
-                    df=df,
-                    metric_component=metric_component,
-                    batch_component=batch_component,
-                    domain_component=domain_component,
-                    min_value_component=min_value_component,
-                    max_value_component=max_value_component,
-                    tooltip=tooltip,
-                )
+        points: alt.Chart = (
+            alt.Chart(data=df, title=title)
+            .mark_point()
+            .encode(
+                x=batch_component.plot_on_axis(),
+                y=metric_component.plot_on_axis(),
+                tooltip=tooltip,
             )
-        else:
-            return (
-                DataAssistantResult._get_expect_domain_values_to_be_between_bar_chart(
-                    df=df,
-                    metric_component=metric_component,
-                    batch_component=batch_component,
-                    domain_component=domain_component,
-                    min_value_component=min_value_component,
-                    max_value_component=max_value_component,
-                    tooltip=tooltip,
-                )
+        )
+
+        return line + points
+
+    @staticmethod
+    def _get_bar_chart(
+        df: pd.DataFrame,
+        metric_component: MetricPlotComponent,
+        batch_component: BatchPlotComponent,
+        domain_component: DomainPlotComponent,
+    ) -> alt.Chart:
+        title: alt.TitleParams = determine_plot_title(
+            metric_plot_component=metric_component,
+            batch_plot_component=batch_component,
+            domain_plot_component=domain_component,
+        )
+
+        tooltip: List[alt.Tooltip] = batch_component.generate_tooltip() + [
+            metric_component.generate_tooltip(format=","),
+        ]
+
+        bars: alt.Chart = (
+            alt.Chart(data=df, title=title)
+            .mark_bar()
+            .encode(
+                x=alt.X(
+                    batch_component.name,
+                    type=batch_component.alt_type,
+                    title=batch_component.title,
+                    axis=alt.Axis(labels=False, grid=False),
+                ),
+                y=metric_component.plot_on_axis(),
+                tooltip=tooltip,
             )
+        )
+
+        return bars
 
     @staticmethod
     def _get_expect_domain_values_to_be_between_line_chart(
@@ -509,66 +1012,6 @@ def _get_expect_domain_values_to_be_between_bar_chart(
 
         return band + lower_limit + upper_limit + anomaly_coded_bars
 
-    @staticmethod
-    def get_interactive_detail_multi_chart(
-        column_dfs: List[Tuple[str, pd.DataFrame]],
-        metric_name: str,
-        metric_type: alt.StandardType,
-        sequential: bool,
-    ) -> Union[alt.Chart, alt.VConcatChart]:
-        """
-        Args:
-            column_dfs: A list of tuples pairing pandas dataframes with the columns they correspond to
-            metric_name: The name of the metric as it exists in the pandas dataframe
-            metric_type: The altair data type for the metric being plotted
-            sequential: Whether batches are sequential in nature
-
-        Returns:
-            A interactive detail altair multi-chart
-        """
-        batch_name: str = "batch"
-        batch_identifiers: List[str] = [
-            column
-            for column in column_dfs[0][1].columns
-            if column not in [metric_name, batch_name]
-        ]
-        batch_component: BatchPlotComponent = BatchPlotComponent(
-            name=batch_name,
-            alt_type=AltairDataTypes.NOMINAL.value,
-            batch_identifiers=batch_identifiers,
-        )
-        metric_component: MetricPlotComponent = MetricPlotComponent(
-            name=metric_name, alt_type=metric_type
-        )
-
-        domain_name: str = "column"
-        domain_component: DomainPlotComponent = DomainPlotComponent(
-            name=domain_name,
-            alt_type=AltairDataTypes.NOMINAL.value,
-        )
-
-        df: pd.DataFrame = pd.DataFrame(
-            columns=[batch_name, metric_name] + batch_identifiers
-        )
-        for column, column_df in column_dfs:
-            column_df[domain_name] = column
-            df = pd.concat([df, column_df], axis=0)
-
-        if sequential:
-            return DataAssistantResult._get_interactive_detail_multi_line_chart(
-                df=df,
-                metric_component=metric_component,
-                batch_component=batch_component,
-                domain_component=domain_component,
-            )
-        else:
-            return DataAssistantResult._get_interactive_detail_multi_bar_chart(
-                df=df,
-                metric_component=metric_component,
-                batch_component=batch_component,
-                domain_component=domain_component,
-            )
-
     @staticmethod
     def _get_interactive_detail_multi_line_chart(
         df: pd.DataFrame,
@@ -854,192 +1297,32 @@ def _get_interactive_detail_multi_bar_chart(
 
         columns: List[str] = pd.unique(df[domain_component.name]).tolist()
         input_dropdown: alt.binding_select = alt.binding_select(
-            options=columns, name="Select Column: "
-        )
-        selection: alt.selection_single = alt.selection_single(
-            bind=input_dropdown,
-            fields=[domain_component.name],
-            init={domain_component.name: " "},
-        )
-
-        bars: alt.Chart = (
-            alt.Chart(data=df, title=title)
-            .mark_bar()
-            .encode(
-                x=alt.X(
-                    batch_component.name,
-                    type=batch_component.alt_type,
-                    title=batch_component.title,
-                    axis=alt.Axis(labels=False, grid=False),
-                ),
-                y=metric_component.plot_on_axis(),
-                tooltip=tooltip,
-            )
-            .add_selection(selection)
-            .transform_filter(selection)
-        )
-
-        return bars
-
-    @staticmethod
-    def get_interactive_detail_expect_column_values_to_be_between_chart(
-        column_dfs: List[Tuple[str, pd.DataFrame]],
-        metric_name: str,
-        metric_type: alt.StandardType,
-        sequential: bool,
-    ) -> alt.VConcatChart:
-        """
-        Args:
-            column_dfs: A list of tuples pairing pandas dataframes with the columns they correspond to
-            metric_name: The name of the metric as it exists in the pandas dataframe
-            metric_type: The altair data type for the metric being plotted
-            sequential: Whether batches are sequential in nature
-
-        Returns:
-            An interactive detail multi line expect_column_values_to_be_between chart
-        """
-        column_name: str = "column"
-        min_value: str = "min_value"
-        max_value: str = "max_value"
-        strict_min: str = "strict_min"
-        strict_max: str = "strict_max"
-
-        batch_name: str = "batch"
-        batch_identifiers: List[str] = [
-            column
-            for column in column_dfs[0][1].columns
-            if column
-            not in [
-                metric_name,
-                batch_name,
-                column_name,
-                min_value,
-                max_value,
-                strict_min,
-                strict_max,
-            ]
-        ]
-        batch_type: alt.StandardType = AltairDataTypes.NOMINAL.value
-        batch_component: BatchPlotComponent = BatchPlotComponent(
-            name=batch_name,
-            alt_type=batch_type,
-            batch_identifiers=batch_identifiers,
-        )
-        metric_component: MetricPlotComponent = MetricPlotComponent(
-            name=metric_name, alt_type=metric_type
-        )
-
-        domain_component: DomainPlotComponent = DomainPlotComponent(
-            name="column",
-            alt_type=AltairDataTypes.NOMINAL.value,
-        )
-
-        min_value_component: ExpectationKwargPlotComponent = (
-            ExpectationKwargPlotComponent(
-                name=min_value,
-                alt_type=AltairDataTypes.QUANTITATIVE.value,
-                metric_plot_component=metric_component,
-            )
-        )
-        max_value_component: ExpectationKwargPlotComponent = (
-            ExpectationKwargPlotComponent(
-                name=max_value,
-                alt_type=AltairDataTypes.QUANTITATIVE.value,
-                metric_plot_component=metric_component,
-            )
-        )
-        strict_min_component: ExpectationKwargPlotComponent = (
-            ExpectationKwargPlotComponent(
-                name=strict_min,
-                alt_type=AltairDataTypes.NOMINAL.value,
-                metric_plot_component=metric_component,
-            )
-        )
-        strict_max_component: ExpectationKwargPlotComponent = (
-            ExpectationKwargPlotComponent(
-                name=strict_max,
-                alt_type=AltairDataTypes.NOMINAL.value,
-                metric_plot_component=metric_component,
-            )
-        )
-
-        df: pd.DataFrame = pd.DataFrame(
-            columns=[
-                batch_name,
-            ]
-            + batch_identifiers
-            + [
-                metric_name,
-                column_name,
-                min_value,
-                max_value,
-                strict_min,
-                strict_max,
-            ]
+            options=columns, name="Select Column: "
+        )
+        selection: alt.selection_single = alt.selection_single(
+            bind=input_dropdown,
+            fields=[domain_component.name],
+            init={domain_component.name: " "},
         )
 
-        for _, column_df in column_dfs:
-            df = pd.concat([df, column_df], axis=0)
-
-        # encode point color based on anomalies
-        predicate: Union[bool, int]
-        if strict_min and strict_max:
-            predicate = (
-                (alt.datum.min_value > alt.datum[metric_component.name])
-                & (alt.datum.max_value > alt.datum[metric_component.name])
-            ) | (
-                (alt.datum.min_value < alt.datum[metric_component.name])
-                & (alt.datum.max_value < alt.datum[metric_component.name])
-            )
-        elif strict_min:
-            predicate = (
-                (alt.datum.min_value > alt.datum[metric_component.name])
-                & (alt.datum.max_value >= alt.datum[metric_component.name])
-            ) | (
-                (alt.datum.min_value < alt.datum[metric_component.name])
-                & (alt.datum.max_value <= alt.datum[metric_component.name])
-            )
-        elif strict_max:
-            predicate = (
-                (alt.datum.min_value >= alt.datum[metric_component.name])
-                & (alt.datum.max_value > alt.datum[metric_component.name])
-            ) | (
-                (alt.datum.min_value <= alt.datum[metric_component.name])
-                & (alt.datum.max_value < alt.datum[metric_component.name])
-            )
-        else:
-            predicate = (
-                (alt.datum.min_value >= alt.datum[metric_component.name])
-                & (alt.datum.max_value >= alt.datum[metric_component.name])
-            ) | (
-                (alt.datum.min_value <= alt.datum[metric_component.name])
-                & (alt.datum.max_value <= alt.datum[metric_component.name])
+        bars: alt.Chart = (
+            alt.Chart(data=df, title=title)
+            .mark_bar()
+            .encode(
+                x=alt.X(
+                    batch_component.name,
+                    type=batch_component.alt_type,
+                    title=batch_component.title,
+                    axis=alt.Axis(labels=False, grid=False),
+                ),
+                y=metric_component.plot_on_axis(),
+                tooltip=tooltip,
             )
+            .add_selection(selection)
+            .transform_filter(selection)
+        )
 
-        if sequential:
-            return DataAssistantResult._get_interactive_detail_expect_column_values_to_be_between_line_chart(
-                df=df,
-                metric_component=metric_component,
-                batch_component=batch_component,
-                domain_component=domain_component,
-                min_value_component=min_value_component,
-                max_value_component=max_value_component,
-                strict_min_component=strict_min_component,
-                strict_max_component=strict_max_component,
-                predicate=predicate,
-            )
-        else:
-            return DataAssistantResult._get_interactive_detail_expect_column_values_to_be_between_bar_chart(
-                df=df,
-                metric_component=metric_component,
-                batch_component=batch_component,
-                domain_component=domain_component,
-                min_value_component=min_value_component,
-                max_value_component=max_value_component,
-                strict_min_component=strict_min_component,
-                strict_max_component=strict_max_component,
-                predicate=predicate,
-            )
+        return bars
 
     @staticmethod
     def _get_interactive_detail_expect_column_values_to_be_between_line_chart(
@@ -1194,228 +1477,100 @@ def _get_interactive_detail_expect_column_values_to_be_between_bar_chart(
         bars.transform = alt.Undefined
 
         input_dropdown_initial_state: pd.DataFrame = (
-            df.groupby([batch_component.name]).max().reset_index()
-        )
-        input_dropdown_initial_state[
-            batch_component.batch_identifiers
-            + [
-                domain_component.name,
-                min_value_component.name,
-                max_value_component.name,
-                strict_min_component.name,
-                strict_max_component.name,
-            ]
-        ] = " "
-        df = pd.concat([input_dropdown_initial_state, df], axis=0)
-
-        columns: List[str] = pd.unique(df[domain_component.name]).tolist()
-        input_dropdown: alt.binding_select = alt.binding_select(
-            options=columns, name="Select Column: "
-        )
-        selection: alt.selection_single = alt.selection_single(
-            empty="none",
-            bind=input_dropdown,
-            fields=[domain_component.name],
-        )
-
-        lower_limit: alt.Chart = (
-            alt.Chart(data=df)
-            .mark_line(color=line_color)
-            .encode(
-                x=alt.X(
-                    batch_component.name,
-                    type=batch_component.alt_type,
-                    title=batch_component.title,
-                    axis=alt.Axis(labels=False, grid=False),
-                ),
-                y=alt.Y(
-                    min_value_component.name,
-                    type=min_value_component.alt_type,
-                ),
-                tooltip=tooltip,
-            )
-            .transform_filter(selection)
-        )
-
-        upper_limit: alt.Chart = (
-            alt.Chart(data=df)
-            .mark_line(color=line_color)
-            .encode(
-                x=alt.X(
-                    batch_component.name,
-                    type=batch_component.alt_type,
-                    title=batch_component.title,
-                    axis=alt.Axis(labels=False, grid=False),
-                ),
-                y=alt.Y(
-                    max_value_component.name,
-                    type=max_value_component.alt_type,
-                ),
-                tooltip=tooltip,
-            )
-            .transform_filter(selection)
-        )
-
-        band: alt.Chart = (
-            alt.Chart(data=df)
-            .mark_area()
-            .encode(
-                x=alt.X(
-                    batch_component.name,
-                    type=batch_component.alt_type,
-                    title=batch_component.title,
-                    axis=alt.Axis(labels=False, grid=False),
-                ),
-                y=alt.Y(
-                    min_value_component.name,
-                    type=min_value_component.alt_type,
-                ),
-                y2=alt.Y2(max_value_component.name),
-            )
-            .transform_filter(selection)
-        )
-
-        bar_color_condition: alt.condition = alt.condition(
-            predicate=predicate,
-            if_false=alt.value(Colors.GREEN.value),
-            if_true=alt.value(Colors.PINK.value),
-        )
-
-        anomaly_coded_bars = (
-            bars.encode(color=bar_color_condition, tooltip=tooltip)
-            .add_selection(selection)
-            .transform_filter(selection)
-        )
-
-        return band + lower_limit + upper_limit + anomaly_coded_bars
-
-    def to_dict(self) -> dict:
-        """
-        Returns: This DataAssistantResult as dictionary (JSON-serializable dictionary for DataAssistantResult objects).
-        """
-        domain: Domain
-        parameter_values_for_fully_qualified_parameter_names: Dict[str, ParameterNode]
-        expectation_configuration: ExpectationConfiguration
-        return {
-            "batch_id_to_batch_identifier_display_name_map": convert_to_json_serializable(
-                data=self.batch_id_to_batch_identifier_display_name_map
-            ),
-            "profiler_config": self.profiler_config.to_json_dict(),
-            "metrics_by_domain": [
-                {
-                    "domain_id": domain.id,
-                    "domain": domain.to_json_dict(),
-                    "parameter_values_for_fully_qualified_parameter_names": convert_to_json_serializable(
-                        data=parameter_values_for_fully_qualified_parameter_names
-                    ),
-                }
-                for domain, parameter_values_for_fully_qualified_parameter_names in self.metrics_by_domain.items()
-            ],
-            "expectation_configurations": [
-                expectation_configuration.to_json_dict()
-                for expectation_configuration in self.expectation_configurations
-            ],
-            "execution_time": convert_to_json_serializable(data=self.execution_time),
-        }
-
-    def to_json_dict(self) -> dict:
-        """
-        Returns: This DataAssistantResult as JSON-serializable dictionary.
-        """
-        return self.to_dict()
-
-    def get_attributed_metrics_by_domain(
-        self,
-    ) -> Dict[Domain, Dict[str, ParameterNode]]:
-        domain: Domain
-        parameter_values_for_fully_qualified_parameter_names: Dict[str, ParameterNode]
-        fully_qualified_parameter_name: str
-        parameter_value: ParameterNode
-        metrics_attributed_values_by_domain: Dict[Domain, Dict[str, ParameterNode]] = {
-            domain: {
-                parameter_value[
-                    FULLY_QUALIFIED_PARAMETER_NAME_METADATA_KEY
-                ].metric_configuration.metric_name: parameter_value[
-                    FULLY_QUALIFIED_PARAMETER_NAME_ATTRIBUTED_VALUE_KEY
-                ]
-                for fully_qualified_parameter_name, parameter_value in parameter_values_for_fully_qualified_parameter_names.items()
-            }
-            for domain, parameter_values_for_fully_qualified_parameter_names in self.metrics_by_domain.items()
-        }
-
-        return metrics_attributed_values_by_domain
-
-    @staticmethod
-    def display(
-        charts: Union[List[alt.Chart], List[alt.VConcatChart]],
-        theme: Optional[Dict[str, Any]],
-    ) -> None:
-        """
-        Display each chart passed by DataAssistantResult.plot()
-
-        Altair theme configuration reference:
-            https://altair-viz.github.io/user_guide/configuration.html#top-level-chart-configuration
-
-        Args:
-            charts: A list of Altair chart objects to display
-            theme: An Optional Altair top-level chart configuration dictionary to apply over the default theme
-        """
-        altair_theme: Dict[str, Any]
-        if theme:
-            altair_theme = DataAssistantResult._get_theme(theme=theme)
-        else:
-            altair_theme = copy.deepcopy(AltairThemes.DEFAULT_THEME.value)
-
-        themed_charts: List[alt.chart] = DataAssistantResult.apply_theme(
-            charts=charts, theme=altair_theme
+            df.groupby([batch_component.name]).max().reset_index()
         )
+        input_dropdown_initial_state[
+            batch_component.batch_identifiers
+            + [
+                domain_component.name,
+                min_value_component.name,
+                max_value_component.name,
+                strict_min_component.name,
+                strict_max_component.name,
+            ]
+        ] = " "
+        df = pd.concat([input_dropdown_initial_state, df], axis=0)
 
-        # Altair does not have a way to format the dropdown input so the rendered CSS must be altered directly
-        dropdown_title_color: str = altair_theme["legend"]["titleColor"]
-        dropdown_title_font: str = altair_theme["font"]
-        dropdown_css: str = f"""
-            <style>
-            span.vega-bind-name {{
-                color: {dropdown_title_color};
-                font-family: "{dropdown_title_font}";
-                font-weight: bold;
-            }}
-            form.vega-bindings {{
-              position: absolute;
-              left: 75px;
-              top: 30px;
-            }}
-            </style>
-        """
-        display(HTML(dropdown_css))
+        columns: List[str] = pd.unique(df[domain_component.name]).tolist()
+        input_dropdown: alt.binding_select = alt.binding_select(
+            options=columns, name="Select Column: "
+        )
+        selection: alt.selection_single = alt.selection_single(
+            empty="none",
+            bind=input_dropdown,
+            fields=[domain_component.name],
+        )
 
-        # max rows for Altair charts is set to 5,000 without this
-        alt.data_transformers.disable_max_rows()
+        lower_limit: alt.Chart = (
+            alt.Chart(data=df)
+            .mark_line(color=line_color)
+            .encode(
+                x=alt.X(
+                    batch_component.name,
+                    type=batch_component.alt_type,
+                    title=batch_component.title,
+                    axis=alt.Axis(labels=False, grid=False),
+                ),
+                y=alt.Y(
+                    min_value_component.name,
+                    type=min_value_component.alt_type,
+                ),
+                tooltip=tooltip,
+            )
+            .transform_filter(selection)
+        )
 
-        chart: alt.Chart
-        for chart in themed_charts:
-            chart.display()
+        upper_limit: alt.Chart = (
+            alt.Chart(data=df)
+            .mark_line(color=line_color)
+            .encode(
+                x=alt.X(
+                    batch_component.name,
+                    type=batch_component.alt_type,
+                    title=batch_component.title,
+                    axis=alt.Axis(labels=False, grid=False),
+                ),
+                y=alt.Y(
+                    max_value_component.name,
+                    type=max_value_component.alt_type,
+                ),
+                tooltip=tooltip,
+            )
+            .transform_filter(selection)
+        )
 
-    @staticmethod
-    def apply_theme(
-        charts: List[alt.Chart],
-        theme: Optional[Dict[str, Any]],
-    ) -> List[alt.Chart]:
-        """
-        Apply the Great Expectations default theme and any user-provided theme overrides to each chart
+        band: alt.Chart = (
+            alt.Chart(data=df)
+            .mark_area()
+            .encode(
+                x=alt.X(
+                    batch_component.name,
+                    type=batch_component.alt_type,
+                    title=batch_component.title,
+                    axis=alt.Axis(labels=False, grid=False),
+                ),
+                y=alt.Y(
+                    min_value_component.name,
+                    type=min_value_component.alt_type,
+                ),
+                y2=alt.Y2(max_value_component.name),
+            )
+            .transform_filter(selection)
+        )
 
-        Altair theme configuration reference:
-            https://altair-viz.github.io/user_guide/configuration.html#top-level-chart-configuration
+        bar_color_condition: alt.condition = alt.condition(
+            predicate=predicate,
+            if_false=alt.value(Colors.GREEN.value),
+            if_true=alt.value(Colors.PINK.value),
+        )
 
-        Args:
-            charts: A list of Altair chart objects to apply a theme to
-            theme: An Optional Altair top-level chart configuration dictionary to apply over the base_theme
+        anomaly_coded_bars = (
+            bars.encode(color=bar_color_condition, tooltip=tooltip)
+            .add_selection(selection)
+            .transform_filter(selection)
+        )
 
-        Returns:
-            A list of Altair charts with the theme applied
-        """
-        theme: Dict[str, Any] = DataAssistantResult._get_theme(theme=theme)
-        return [chart.configure(**theme) for chart in charts]
+        return band + lower_limit + upper_limit + anomaly_coded_bars
 
     @staticmethod
     def _get_theme(theme: Optional[Dict[str, Any]]) -> Dict[str, Any]:
@@ -1872,133 +2027,3 @@ def _create_chart_for_table_domain_expectation(
                     sequential=sequential,
                     subtitle=None,
                 )
-
-    def plot_metrics(
-        self,
-        sequential: bool = True,
-        theme: Optional[Dict[str, Any]] = None,
-        include_column_names: Optional[List[str]] = None,
-        exclude_column_names: Optional[List[str]] = None,
-    ) -> PlotResult:
-        """
-        Use contents of "DataAssistantResult" object to display metrics for visualization purposes.
-
-        Altair theme configuration reference:
-            https://altair-viz.github.io/user_guide/configuration.html#top-level-chart-configuration
-
-        Args:
-            sequential: Whether the batches are sequential or not
-            theme: Altair top-level chart configuration dictionary
-            include_column_names: Columns to include in metrics plot
-            exclude_column_names: Columns to exclude from metrics plot
-
-        Returns:
-            PlotResult wrapper object around Altair charts.
-        """
-        return self._plot(
-            plot_mode=PlotMode.DESCRIPTIVE,
-            sequential=sequential,
-            theme=theme,
-            include_column_names=include_column_names,
-            exclude_column_names=exclude_column_names,
-        )
-
-    def plot_expectations_and_metrics(
-        self,
-        sequential: bool = True,
-        theme: Optional[Dict[str, Any]] = None,
-        include_column_names: Optional[List[str]] = None,
-        exclude_column_names: Optional[List[str]] = None,
-    ) -> PlotResult:
-        """
-        Use contents of "DataAssistantResult" object to display metrics and expectations for visualization purposes.
-
-        Altair theme configuration reference:
-            https://altair-viz.github.io/user_guide/configuration.html#top-level-chart-configuration
-
-        Args:
-            sequential: Whether the batches are sequential or not
-            theme: Altair top-level chart configuration dictionary
-            include_column_names: Columns to include in expectations and metrics plot
-            exclude_column_names: Columns to exclude from expectations and metrics plot
-
-        Returns:
-            PlotResult wrapper object around Altair charts.
-        """
-        return self._plot(
-            plot_mode=PlotMode.PRESCRIPTIVE,
-            sequential=sequential,
-            theme=theme,
-            include_column_names=include_column_names,
-            exclude_column_names=exclude_column_names,
-        )
-
-    def _plot(
-        self,
-        plot_mode: PlotMode,
-        sequential: bool,
-        theme: Optional[Dict[str, Any]] = None,
-        include_column_names: Optional[List[str]] = None,
-        exclude_column_names: Optional[List[str]] = None,
-    ) -> PlotResult:
-        """
-        VolumeDataAssistant-specific plots are defined with Altair and passed to "display()" for presentation.
-        Display Charts are condensed and interactive while Return Charts are separated into an individual chart for
-        each metric-domain/expectation-domain combination.
-
-        Altair theme configuration reference:
-            https://altair-viz.github.io/user_guide/configuration.html#top-level-chart-configuration
-
-        Args:
-            plot_mode: Type of plot to generate, prescriptive or descriptive
-            sequential: Whether batches are sequential in nature
-            theme: Altair top-level chart configuration dictionary
-            include_column_names: A list of columns to chart
-            exclude_column_names: A list of columns not to chart
-
-        Returns:
-            A PlotResult object consisting of an individual chart for each metric-domain/expectation-domain
-        """
-        if include_column_names is not None and exclude_column_names is not None:
-            raise ValueError(
-                "You may either use `include_column_names` or `exclude_column_names` (but not both)."
-            )
-
-        display_charts: Union[
-            List[alt.Chart], List[alt.LayerChart], List[alt.VConcatChart]
-        ] = []
-        return_charts: Union[List[alt.Chart], List[alt.LayerChart]] = []
-
-        expectation_configurations: List[
-            ExpectationConfiguration
-        ] = self.expectation_configurations
-
-        table_domain_charts: List[
-            Union[List[alt.Chart], List[alt.LayerChart]]
-        ] = self._plot_table_domain_charts(
-            expectation_configurations=expectation_configurations,
-            plot_mode=plot_mode,
-            sequential=sequential,
-        )
-        display_charts.extend(table_domain_charts)
-        return_charts.extend(table_domain_charts)
-
-        column_domain_display_chart: List[alt.VConcatChart]
-        column_domain_return_charts: List[alt.Chart]
-        (
-            column_domain_display_charts,
-            column_domain_return_charts,
-        ) = self._plot_column_domain_charts(
-            expectation_configurations=expectation_configurations,
-            plot_mode=plot_mode,
-            sequential=sequential,
-            include_column_names=include_column_names,
-            exclude_column_names=exclude_column_names,
-        )
-        display_charts.extend(column_domain_display_charts)
-        return_charts.extend(column_domain_return_charts)
-
-        self.display(charts=display_charts, theme=theme)
-
-        return_charts = self.apply_theme(charts=return_charts, theme=theme)
-        return PlotResult(charts=return_charts)


Test patch:
diff --git a/tests/integration/profiling/rule_based_profilers/test_profiler_user_workflows.py b/tests/integration/profiling/rule_based_profilers/test_profiler_user_workflows.py
--- a/tests/integration/profiling/rule_based_profilers/test_profiler_user_workflows.py
+++ b/tests/integration/profiling/rule_based_profilers/test_profiler_user_workflows.py
@@ -65,6 +65,7 @@ def alice_validator(alice_columnar_table_single_batch_context) -> Validator:
         expectation_suite_name=None,
         expectation_suite=None,
         component_name="profiler",
+        persist=False,
     )
 
     assert len(validator.batches) == 1
@@ -89,6 +90,7 @@ def bobby_validator(
         expectation_suite_name=None,
         expectation_suite=None,
         component_name="profiler",
+        persist=False,
     )
 
     assert len(validator.batches) == 3
@@ -118,6 +120,7 @@ def bobster_validator(
         expectation_suite_name=None,
         expectation_suite=None,
         component_name="profiler",
+        persist=False,
     )
 
     assert len(validator.batches) == 36
@@ -144,6 +147,7 @@ def quentin_validator(
         expectation_suite_name=None,
         expectation_suite=None,
         component_name="profiler",
+        persist=False,
     )
 
     assert len(validator.batches) == 36
@@ -1111,6 +1115,7 @@ def test_bobby_expect_column_values_to_be_between_auto_yes_default_profiler_conf
         expectation_suite_name=None,
         expectation_suite=None,
         component_name="profiler",
+        persist=False,
     )
     assert len(validator.batches) == 1
 
@@ -1356,6 +1361,7 @@ def test_bobby_expect_column_values_to_be_between_auto_yes_default_profiler_conf
         expectation_suite_name=None,
         expectation_suite=None,
         component_name="profiler",
+        persist=False,
     )
     assert len(validator.batches) == 1
 
diff --git a/tests/rule_based_profiler/data_assistant/test_onboarding_data_assistant.py b/tests/rule_based_profiler/data_assistant/test_onboarding_data_assistant.py
--- a/tests/rule_based_profiler/data_assistant/test_onboarding_data_assistant.py
+++ b/tests/rule_based_profiler/data_assistant/test_onboarding_data_assistant.py
@@ -145,6 +145,7 @@ def run_onboarding_data_assistant_result_jupyter_notebook_with_new_cell(
         expectation_suite_name=None,
         expectation_suite=None,
         component_name="onboarding_data_assistant",
+        persist=False,
     )
 
     data_assistant: DataAssistant = OnboardingDataAssistant(
diff --git a/tests/rule_based_profiler/data_assistant/test_volume_data_assistant.py b/tests/rule_based_profiler/data_assistant/test_volume_data_assistant.py
--- a/tests/rule_based_profiler/data_assistant/test_volume_data_assistant.py
+++ b/tests/rule_based_profiler/data_assistant/test_volume_data_assistant.py
@@ -2066,7 +2066,7 @@ def _expectation_suite(name: str) -> ExpectationSuite:
         expected_expectation_suite_meta: Dict[str, Any] = {
             "citations": [
                 {
-                    "citation_date": "2019-09-26T13:42:41+00:00",
+                    "citation_date": "2019-09-26T13:42:41.000000Z",
                     "profiler_config": quentin_expected_rule_based_profiler_configuration(
                         name=name
                     ).to_json_dict(),
@@ -2120,6 +2120,7 @@ def quentin_explicit_instantiation_result_actual_time(
         expectation_suite_name=None,
         expectation_suite=None,
         component_name="volume_data_assistant",
+        persist=False,
     )
     assert len(validator.batches) == 36
 
@@ -2155,6 +2156,7 @@ def quentin_explicit_instantiation_result_frozen_time(
         expectation_suite_name=None,
         expectation_suite=None,
         component_name="volume_data_assistant",
+        persist=False,
     )
     assert len(validator.batches) == 36
 
@@ -2270,6 +2272,7 @@ def run_volume_data_assistant_result_jupyter_notebook_with_new_cell(
         expectation_suite_name=None,
         expectation_suite=None,
         component_name="volume_data_assistant",
+        persist=False,
     )
 
     data_assistant: DataAssistant = VolumeDataAssistant(
@@ -2392,11 +2395,22 @@ def test_get_metrics_and_expectations_using_explicit_instantiation(
     data_assistant_result.citation.pop("profiler_config", None)
     expected_expectation_suite.meta["citations"][0].pop("profiler_config", None)
 
+    data_assistant_result.citation.pop("citation_date", None)
+    expected_expectation_suite.meta["citations"][0].pop("citation_date", None)
     assert (
         data_assistant_result.citation
         == expected_expectation_suite.meta["citations"][0]
     )
 
+    actual_expectation_suite: ExpectationSuite = (
+        data_assistant_result.get_expectation_suite(expectation_suite_name="my_suite")
+    )
+    actual_expectation_suite.meta.pop("great_expectations_version", None)
+    expected_expectation_suite.meta.pop("great_expectations_version", None)
+    actual_expectation_suite.meta["citations"][0].pop("citation_date", None)
+    expected_expectation_suite.meta["citations"][0].pop("citation_date", None)
+    assert actual_expectation_suite == expected_expectation_suite
+
 
 @freeze_time("09/26/2019 13:42:41")
 def test_get_metrics_and_expectations_using_implicit_invocation(
@@ -2442,11 +2456,22 @@ def test_get_metrics_and_expectations_using_implicit_invocation(
     data_assistant_result.citation.pop("profiler_config", None)
     expected_expectation_suite.meta["citations"][0].pop("profiler_config", None)
 
+    data_assistant_result.citation.pop("citation_date", None)
+    expected_expectation_suite.meta["citations"][0].pop("citation_date", None)
     assert (
         data_assistant_result.citation
         == expected_expectation_suite.meta["citations"][0]
     )
 
+    actual_expectation_suite: ExpectationSuite = (
+        data_assistant_result.get_expectation_suite(expectation_suite_name="my_suite")
+    )
+    actual_expectation_suite.meta.pop("great_expectations_version", None)
+    expected_expectation_suite.meta.pop("great_expectations_version", None)
+    actual_expectation_suite.meta["citations"][0].pop("citation_date", None)
+    expected_expectation_suite.meta["citations"][0].pop("citation_date", None)
+    assert actual_expectation_suite == expected_expectation_suite
+
 
 @freeze_time("09/26/2019 13:42:41")
 def test_get_metrics_and_expectations_using_implicit_invocation_with_exclude_column_names_directive(
@@ -2542,11 +2567,22 @@ def test_get_metrics_and_expectations_using_implicit_invocation_with_exclude_col
     data_assistant_result.citation.pop("profiler_config", None)
     expected_expectation_suite.meta["citations"][0].pop("profiler_config", None)
 
+    data_assistant_result.citation.pop("citation_date", None)
+    expected_expectation_suite.meta["citations"][0].pop("citation_date", None)
     assert (
         data_assistant_result.citation
         == expected_expectation_suite.meta["citations"][0]
     )
 
+    actual_expectation_suite: ExpectationSuite = (
+        data_assistant_result.get_expectation_suite(expectation_suite_name="my_suite")
+    )
+    actual_expectation_suite.meta.pop("great_expectations_version", None)
+    expected_expectation_suite.meta.pop("great_expectations_version", None)
+    actual_expectation_suite.meta["citations"][0].pop("citation_date", None)
+    expected_expectation_suite.meta["citations"][0].pop("citation_date", None)
+    assert actual_expectation_suite == expected_expectation_suite
+
 
 def test_execution_time_within_proper_bounds_using_explicit_instantiation(
     quentin_explicit_instantiation_result_actual_time,