Repository: iterative/dvc

Issue:
Deprecate `dvc exp show --html`
We should probably do this outside of the CLI if we want a parallel coordinates plot
Part of 3.0 release checklist (#7093)


Discussion:

Resolving patch:
diff --git a/dvc/commands/experiments/show.py b/dvc/commands/experiments/show.py
--- a/dvc/commands/experiments/show.py
+++ b/dvc/commands/experiments/show.py
@@ -1,13 +1,11 @@
 import argparse
 import logging
-import os
 import re
 from datetime import date, datetime
 from typing import TYPE_CHECKING, Dict, Iterable
 
 from funcy import lmap
 
-from dvc.cli import completion
 from dvc.cli.command import CmdBase
 from dvc.cli.utils import append_doc_link
 from dvc.commands.metrics import DEFAULT_PRECISION
@@ -59,7 +57,7 @@ def baseline_styler(typ):
     return {"style": "bold"} if typ == "baseline" else {}
 
 
-def show_experiments(  # noqa: C901, PLR0912
+def show_experiments(  # noqa: C901
     td: "TabularData",
     headers: Dict[str, Iterable[str]],
     keep=None,
@@ -67,7 +65,6 @@ def show_experiments(  # noqa: C901, PLR0912
     pager=True,
     csv=False,
     markdown=False,
-    pcp=False,
     **kwargs,
 ):
     if keep:
@@ -110,51 +107,23 @@ def show_experiments(  # noqa: C901, PLR0912
         }
     )
 
-    if kwargs.get("only_changed", False) or pcp:
+    if kwargs.get("only_changed", False):
         td.drop_duplicates("cols", ignore_empty=False)
 
     cols_to_drop = set()
     if drop is not None:
         cols_to_drop = {col for col in td.keys() if re.match(drop, col)}  # noqa: SIM118
-    if pcp:
-        cols_to_drop.add("Created")
     td.drop(*cols_to_drop)
 
-    if pcp:
-        subset = {x for x in td.keys() if x != "Experiment"}  # noqa: SIM118
-        td.dropna(
-            "rows",
-            how="all",
-            subset=subset,
-        )
-        td.drop_duplicates("rows", subset=subset)
-        if "Experiment" in td:
-            td.column("Experiment")[:] = [
-                # remove tree characters
-                str(x).encode("ascii", "ignore").strip().decode()
-                for x in td.column("Experiment")
-            ]
-        out = kwargs.get("out") or "dvc_plots"
-        output_file = os.path.join(out, "index.html")
-        ui.write(
-            td.to_parallel_coordinates(
-                output_path=os.path.abspath(output_file),
-                color_by=kwargs.get("sort_by") or "Experiment",
-            )
-        )
-        if kwargs.get("open"):
-            return ui.open_browser(output_file)
-
-    else:
-        td.render(
-            pager=pager,
-            borders="horizontals",
-            rich_table=True,
-            header_styles=styles,
-            row_styles=row_styles,
-            csv=csv,
-            markdown=markdown,
-        )
+    td.render(
+        pager=pager,
+        borders="horizontals",
+        rich_table=True,
+        header_styles=styles,
+        row_styles=row_styles,
+        csv=csv,
+        markdown=markdown,
+    )
 
 
 def _normalize_headers(names, count):
@@ -221,9 +190,6 @@ def run(self):
                 csv=self.args.csv,
                 markdown=self.args.markdown,
                 only_changed=self.args.only_changed,
-                pcp=self.args.pcp,
-                out=self.args.out,
-                open=self.args.open,
             )
         return 0
 
@@ -342,27 +308,6 @@ def add_parser(experiments_subparsers, parent_parser):
         ),
         metavar="<n>",
     )
-    experiments_show_parser.add_argument(
-        "--parallel-coordinates-plot",
-        "--pcp",
-        dest="pcp",
-        action="store_true",
-        default=False,
-        help="Generate a Parallel Coordinates Plot from the tabulated output.",
-    )
-    experiments_show_parser.add_argument(
-        "-o",
-        "--out",
-        default=None,
-        help="Destination folder to save the Parallel Coordinates Plot to",
-        metavar="<path>",
-    ).complete = completion.DIR
-    experiments_show_parser.add_argument(
-        "--open",
-        action="store_true",
-        default=False,
-        help="Open the Parallel Coordinates Plot directly in the browser.",
-    )
     experiments_show_parser.add_argument(
         "--no-fetch",
         dest="fetch_running",
diff --git a/dvc/compare.py b/dvc/compare.py
--- a/dvc/compare.py
+++ b/dvc/compare.py
@@ -22,7 +22,6 @@
 from funcy import reraise
 
 if TYPE_CHECKING:
-    from dvc.types import StrPath
     from dvc.ui.table import CellT
 
 
@@ -161,24 +160,6 @@ def to_csv(self) -> str:
             writer.writerow(row)
         return buff.getvalue()
 
-    def to_parallel_coordinates(
-        self, output_path: "StrPath", color_by: Optional[str] = None
-    ) -> "StrPath":
-        from dvc_render.html import render_html
-        from dvc_render.plotly import ParallelCoordinatesRenderer
-
-        render_html(
-            renderers=[
-                ParallelCoordinatesRenderer(
-                    self.as_dict(),
-                    color_by=color_by,
-                    fill_value=self._fill_value,
-                )
-            ],
-            output_file=output_path,
-        )
-        return output_path
-
     def add_column(self, name: str) -> None:
         self._columns[name] = Column([self._fill_value] * len(self))
         self._keys.append(name)


Test patch:
diff --git a/tests/func/experiments/test_show.py b/tests/func/experiments/test_show.py
--- a/tests/func/experiments/test_show.py
+++ b/tests/func/experiments/test_show.py
@@ -551,85 +551,6 @@ def test_show_only_changed(tmp_dir, dvc, scm, capsys, copy_script):
     assert "metrics.yaml:goobar" in cap.out
 
 
-def test_show_parallel_coordinates(tmp_dir, dvc, scm, mocker, capsys, copy_script):
-    from dvc.commands.experiments import show
-
-    webbroser_open = mocker.patch("webbrowser.open")
-    show_experiments = mocker.spy(show, "show_experiments")
-
-    params_file = tmp_dir / "params.yaml"
-    params_data = {
-        "foo": 1,
-        "bar": 1,
-    }
-    (tmp_dir / params_file).dump(params_data)
-
-    dvc.run(
-        cmd="python copy.py params.yaml metrics.yaml",
-        metrics_no_cache=["metrics.yaml"],
-        params=["foo", "bar"],
-        name="copy-file",
-        deps=["copy.py"],
-    )
-    scm.add(
-        [
-            "dvc.yaml",
-            "dvc.lock",
-            "copy.py",
-            "params.yaml",
-            "metrics.yaml",
-            ".gitignore",
-        ]
-    )
-    scm.commit("init")
-
-    dvc.experiments.run(params=["foo=2"])
-
-    assert main(["exp", "show", "--pcp"]) == 0
-    kwargs = show_experiments.call_args[1]
-
-    html_text = (tmp_dir / "dvc_plots" / "index.html").read_text()
-    assert all(rev in html_text for rev in ["workspace", "master"])
-    assert "[exp-" not in html_text
-
-    assert '{"label": "metrics.yaml:foo", "values": [2.0, 1.0]}' in html_text
-    assert '{"label": "params.yaml:foo", "values": [2.0, 1.0]}' in html_text
-    assert '"line": {"color": [1, 0]' in html_text
-    assert '"label": "metrics.yaml:bar"' not in html_text
-    assert '"label": "Created"' not in html_text
-
-    assert main(["exp", "show", "--pcp", "--sort-by", "metrics.yaml:foo"]) == 0
-    kwargs = show_experiments.call_args[1]
-
-    html_text = (tmp_dir / "dvc_plots" / "index.html").read_text()
-    assert '"line": {"color": [2.0, 1.0]' in html_text
-
-    assert main(["exp", "show", "--pcp", "--out", "experiments"]) == 0
-    kwargs = show_experiments.call_args[1]
-
-    assert kwargs["out"] == "experiments"
-    assert (tmp_dir / "experiments" / "index.html").exists()
-
-    assert main(["exp", "show", "--pcp", "--open"]) == 0
-
-    webbroser_open.assert_called()
-
-    params_data = {"foo": 1, "bar": 1, "foobar": 2}
-    (tmp_dir / params_file).dump(params_data)
-    assert main(["exp", "show", "--pcp"]) == 0
-    html_text = (tmp_dir / "dvc_plots" / "index.html").read_text()
-    assert '{"label": "foobar", "values": [2.0, null, null]}' in html_text
-
-    assert main(["exp", "show", "--pcp", "--drop", "foobar"]) == 0
-    html_text = (tmp_dir / "dvc_plots" / "index.html").read_text()
-    assert '"label": "Created"' not in html_text
-    assert '"label": "foobar"' not in html_text
-
-    assert main(["exp", "show", "--pcp", "--drop", "Experiment"]) == 0
-    html_text = (tmp_dir / "dvc_plots" / "index.html").read_text()
-    assert '"label": "Experiment"' not in html_text
-
-
 @pytest.mark.vscode
 def test_show_outs(tmp_dir, dvc, scm, erepo_dir, copy_script):
     params_file = tmp_dir / "params.yaml"
diff --git a/tests/unit/test_tabular_data.py b/tests/unit/test_tabular_data.py
--- a/tests/unit/test_tabular_data.py
+++ b/tests/unit/test_tabular_data.py
@@ -374,21 +374,3 @@ def test_drop_duplicates_invalid_axis():
 
     with pytest.raises(ValueError, match="Invalid 'axis' value foo."):
         td.drop_duplicates("foo")
-
-
-def test_to_parallel_coordinates(tmp_dir, mocker):
-    (tmp_dir / "foo").mkdir()
-    td = TabularData(["categorical", "scalar"])
-    td.extend([["foo", "0.1"], ["bar", "2"]])
-
-    write = mocker.patch("dvc_render.html.render_html")
-    renderer_class = mocker.patch("dvc_render.plotly.ParallelCoordinatesRenderer")
-    renderer = renderer_class.return_value
-
-    td.to_parallel_coordinates(output_path="foo")
-
-    renderer_class.assert_called_with(
-        td.as_dict(), color_by=None, fill_value=td._fill_value
-    )
-
-    write.assert_called_with(renderers=[renderer], output_file="foo")