Repository: iterative/dvc

Issue:
`exp show`: Rename / Split to separate command the current `--html`
> My biggest question is whether this belongs in `dvc exp show` or might be better as a separate command, like `dvc exp plot`? A `dvc exp plot` command could also potentially show other things, like `dvc plots diff` between experiments, or checkpoint trends (like dvclive plots).

_Originally posted by @dberenbaum in https://github.com/iterative/dvc/issues/6933#issuecomment-973159973_

> Why something as generic and conceptually disconnected as "HTML"? How about `--parallel-coordinates-plot`/`--pcp`? Or a separate command as @dberenbaum mentioned

_Originally posted by @jorgeorpinel in https://github.com/iterative/dvc/issues/6933#issuecomment-996059215_

> To be honest `--html` for me would mean an HTML table, not plot. I would consider renaming this to be explicit about its meaning. I think `--plot` is a bit more explicit. But can limit us later if we decide to introduce more types of plots. Probably `--pcp` is the best unfortunately.

_Originally posted by @shcheklein in https://github.com/iterative/dvc/issues/6933#issuecomment-996459692_


Discussion:
I think the best option would be to move to a separate command `dvc exp plot` with no flags for now. If more type of plots are added, we can introduce `--pcp` flags for whatever new type of plot.

It also opens possibility for further integration of `plots` and `experiments`
Yeah I can see why it was included to `exp show` initially, as it's basically the same info. But the presentation is completely different plus indeed maybe we want to add other plots later so I'd vote for a separate command.
> Yeah I can see why it was included to `exp show` initially, as it's basically the same info. But the presentation is completely different plus indeed maybe we want to add other plots later so I'd vote for a separate command.

One worry that I have is that adding a separate command, no matter the name, might create questions from the user's perspective.

If we name `dvc exp plot`/`dvc exp report` with the current features, users might ask where are the other plots (i.e. live plots) or why it's named report if it's only a PCP plot.

Proper `exp plot`/`exp report` requires mid-term engineering and product work in order to be implemented properly but the issue with the `html` name needs to be fixed short-term. 

I'm wondering if just rename `--html` to `--pcp` for now until the features for separate command are consolidated 😅

cc @dberenbaum  

`--pcp` is the option with the most implicit votes already and definitely an improvement so I believe we can go ahead with that for now indeed.

I don't think users would be confused about `exp plot` only having one plot though.

BTW where are live plots documented?

Resolving patch:
diff --git a/dvc/command/experiments/show.py b/dvc/command/experiments/show.py
--- a/dvc/command/experiments/show.py
+++ b/dvc/command/experiments/show.py
@@ -1,9 +1,9 @@
 import argparse
 import logging
+import os
 from collections import Counter, OrderedDict, defaultdict
 from datetime import date, datetime
 from fnmatch import fnmatch
-from pathlib import Path
 from typing import TYPE_CHECKING, Dict, Iterable, Optional
 
 from funcy import lmap
@@ -384,7 +384,7 @@ def show_experiments(
     no_timestamp=False,
     csv=False,
     markdown=False,
-    html=False,
+    pcp=False,
     **kwargs,
 ):
     from funcy.seqs import flatten as flatten_list
@@ -432,7 +432,7 @@ def show_experiments(
         kwargs.get("iso"),
     )
 
-    if no_timestamp or html:
+    if no_timestamp or pcp:
         td.drop("Created")
 
     for col in ("State", "Executor"):
@@ -472,11 +472,10 @@ def show_experiments(
         }
     )
 
-    if kwargs.get("only_changed", False) or html:
+    if kwargs.get("only_changed", False) or pcp:
         td.drop_duplicates("cols", ignore_empty=False)
 
-    html_args = {}
-    if html:
+    if pcp:
         td.dropna("rows", how="all")
         td.column("Experiment")[:] = [
             # remove tree characters
@@ -484,23 +483,25 @@ def show_experiments(
             for x in td.column("Experiment")
         ]
         out = kwargs.get("out") or "dvc_plots"
-        html_args["output_path"] = (Path.cwd() / out).resolve()
-        html_args["color_by"] = kwargs.get("sort_by") or "Experiment"
-
-    td.render(
-        pager=pager,
-        borders="horizontals",
-        rich_table=True,
-        header_styles=styles,
-        row_styles=row_styles,
-        csv=csv,
-        markdown=markdown,
-        html=html,
-        **html_args,
-    )
+        ui.write(
+            td.to_parallel_coordinates(
+                output_path=os.path.abspath(out),
+                color_by=kwargs.get("sort_by") or "Experiment",
+            )
+        )
+        if kwargs.get("open"):
+            return ui.open_browser(os.path.join(out, "index.html"))
 
-    if html and kwargs.get("open"):
-        return ui.open_browser(Path(out) / "index.html")
+    else:
+        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):
@@ -559,7 +560,7 @@ def run(self):
                 csv=self.args.csv,
                 markdown=self.args.markdown,
                 only_changed=self.args.only_changed,
-                html=self.args.html,
+                pcp=self.args.pcp,
                 out=self.args.out,
                 open=self.args.open,
             )
@@ -712,22 +713,24 @@ def add_parser(experiments_subparsers, parent_parser):
         ),
     )
     experiments_show_parser.add_argument(
-        "--html",
+        "--parallel-coordinates-plot",
+        "--pcp",
+        dest="pcp",
         action="store_true",
         default=False,
-        help="Generate a parallel coordinates plot from the tabulated output.",
+        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 HTML to",
+        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 HTML directly in the browser.",
+        help="Open the Parallel Coordinates Plot directly in the browser.",
     )
     experiments_show_parser.set_defaults(func=CmdExperimentsShow)
diff --git a/dvc/compare.py b/dvc/compare.py
--- a/dvc/compare.py
+++ b/dvc/compare.py
@@ -21,6 +21,7 @@
 from funcy import reraise
 
 if TYPE_CHECKING:
+    from dvc.types import StrPath
     from dvc.ui.table import CellT
 
 
@@ -153,7 +154,9 @@ def to_csv(self) -> str:
             writer.writerow(row)
         return buff.getvalue()
 
-    def to_parallel_coordinates(self, output_path, color_by):
+    def to_parallel_coordinates(
+        self, output_path: "StrPath", color_by: str = None
+    ) -> str:
         from dvc.render.html import write
         from dvc.render.plotly import ParallelCoordinatesRenderer
 
@@ -185,14 +188,6 @@ def render(self, **kwargs: Any):
 
         if kwargs.pop("csv", False):
             ui.write(self.to_csv(), end="")
-
-        elif kwargs.pop("html", False):
-            ui.write(
-                self.to_parallel_coordinates(
-                    kwargs["output_path"], kwargs.get("color_by")
-                )
-            )
-
         else:
             ui.table(self, headers=self.keys(), **kwargs)
 


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
@@ -633,7 +633,7 @@ def test_show_parallel_coordinates(tmp_dir, dvc, scm, mocker):
 
     dvc.experiments.run(params=["foo=2"])
 
-    assert main(["exp", "show", "--html"]) == 0
+    assert main(["exp", "show", "--pcp"]) == 0
     kwargs = show_experiments.call_args[1]
 
     html_text = (tmp_dir / "dvc_plots" / "index.html").read_text()
@@ -648,26 +648,24 @@ def test_show_parallel_coordinates(tmp_dir, dvc, scm, mocker):
     assert '"line": {"color": [2, 1, 0]' in html_text
     assert '"label": "metrics.yaml:bar"' not in html_text
 
-    assert (
-        main(["exp", "show", "--html", "--sort-by", "metrics.yaml:foo"]) == 0
-    )
+    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, 2.0]' in html_text
 
-    assert main(["exp", "show", "--html", "--out", "experiments"]) == 0
+    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", "--html", "--open"]) == 0
+    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", "--html"]) == 0
+    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
diff --git a/tests/unit/command/test_experiments.py b/tests/unit/command/test_experiments.py
--- a/tests/unit/command/test_experiments.py
+++ b/tests/unit/command/test_experiments.py
@@ -761,7 +761,7 @@ def test_experiments_init_type_invalid_choice():
         parse_args(["exp", "init", "--type=invalid", "cmd"])
 
 
-def test_show_experiments_html(tmp_dir, mocker):
+def test_show_experiments_pcp(tmp_dir, mocker):
     all_experiments = {
         "workspace": {
             "baseline": {
@@ -783,13 +783,11 @@ def test_show_experiments_html(tmp_dir, mocker):
     )
     td = experiments_table.return_value
 
-    show_experiments(all_experiments, html=True)
+    show_experiments(all_experiments, pcp=True)
 
     td.dropna.assert_called_with("rows", how="all")
 
-    render_kwargs = td.render.call_args[1]
+    kwargs = td.to_parallel_coordinates.call_args[1]
 
-    for arg in ["html", "output_path", "color_by"]:
-        assert arg in render_kwargs
-    assert render_kwargs["output_path"] == tmp_dir / "dvc_plots"
-    assert render_kwargs["color_by"] == "Experiment"
+    assert kwargs["output_path"] == str(tmp_dir / "dvc_plots")
+    assert kwargs["color_by"] == "Experiment"
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
@@ -337,7 +337,7 @@ def test_to_parallel_coordinates(tmp_dir, mocker):
     )
     renderer = renderer_class.return_value
 
-    td.render(html=True, output_path="foo")
+    td.to_parallel_coordinates(output_path="foo")
 
     renderer_class.assert_called_with(td, None, td._fill_value)