We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
plots modify minor issues
```
$ dvc -V
1.0.0a6+4cfc2d
```

1. ~~Misleading message~~ (#3978)
```
$ dvc plots modify plots.csv --unset y title
Adding stage 'trainme' to 'dvc.yaml'
```
~~It should be `Modifying stage 'trainme' to 'dvc.yaml'`~~

2. ~~`--unset` should handle errors properly~~
```
$ dvc plots modify plots.csv --unset y_fake_param
Adding stage 'trainme' to 'dvc.yaml'
$ echo $?
0
```
~~It supposed to fail with proper message.~~ https://github.com/iterative/dvc/pull/4044

3. ~~No template file check before setting~~ (#3995)
```
$ dvc plots modify -t confusion1 plots.csv
Adding stage 'trainme' to 'dvc.yaml'
```
While there is no template `confusion1`. It should fail with a proper error message.

4. ~~not easy to understand `--unset` syntax
It supposed to be in the end, right? Initially I tried `dvc plots modify --unset y plots.csv` that didn't work.~~ Same as in https://github.com/iterative/dvc/issues/3962

5. ~~message~~

```
$ dvc plots -h
    modify            Modify plot props associated with a target file.
```
~~`target file ` info is not needed. It can be `Modify plot properties`.~~

6. ~~message~~  (#3983)

```
$ dvc plots modify -h
```

~~`props` term is often used. It was not clear what foes it mean. We should probably clarify.~~

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `dvc/repo/plots/__init__.py`
Content:
```
1 import logging
2 
3 from funcy import first, project
4 
5 from dvc.exceptions import DvcException, NoPlotsError, OutputNotFoundError
6 from dvc.repo.tree import RepoTree
7 from dvc.schema import PLOT_PROPS
8 from dvc.utils import relpath
9 
10 logger = logging.getLogger(__name__)
11 
12 
13 class NotAPlotError(DvcException):
14     def __init__(self, out):
15         super().__init__(
16             f"'{out}' is not a plot. Use `dvc plots modify` to change that."
17         )
18 
19 
20 class Plots:
21     def __init__(self, repo):
22         self.repo = repo
23 
24     def collect(self, targets=None, revs=None):
25         """Collects all props and data for plots.
26 
27         Returns a structure like:
28             {rev: {plots.csv: {
29                 props: {x: ..., "header": ..., ...},
30                 data: "...data as a string...",
31             }}}
32         Data parsing is postponed, since it's affected by props.
33         """
34         targets = [targets] if isinstance(targets, str) else targets or []
35         data = {}
36         for rev in self.repo.brancher(revs=revs):
37             # .brancher() adds unwanted workspace
38             if revs is not None and rev not in revs:
39                 continue
40             rev = rev or "workspace"
41 
42             tree = RepoTree(self.repo)
43             plots = _collect_plots(self.repo, targets, rev)
44             for path_info, props in plots.items():
45                 datafile = relpath(path_info, self.repo.root_dir)
46                 if rev not in data:
47                     data[rev] = {}
48                 data[rev].update({datafile: {"props": props}})
49 
50                 # Load data from git or dvc cache
51                 try:
52                     with tree.open(path_info) as fd:
53                         data[rev][datafile]["data"] = fd.read()
54                 except FileNotFoundError as e:
55                     # This might happen simply because cache is absent
56                     print(e)
57                     pass
58 
59         return data
60 
61     @staticmethod
62     def render(data, revs=None, props=None, templates=None):
63         """Renders plots"""
64         props = props or {}
65 
66         # Merge data by plot file and apply overriding props
67         plots = _prepare_plots(data, revs, props)
68 
69         return {
70             datafile: _render(datafile, desc["data"], desc["props"], templates)
71             for datafile, desc in plots.items()
72         }
73 
74     def show(self, targets=None, revs=None, props=None):
75         from .data import NoMetricInHistoryError
76 
77         data = self.collect(targets, revs)
78 
79         # If any mentioned plot doesn't have any data then that's an error
80         targets = [targets] if isinstance(targets, str) else targets or []
81         for target in targets:
82             if not any("data" in d[target] for d in data.values()):
83                 raise NoMetricInHistoryError(target)
84 
85         # No data at all is a special error with a special message
86         if not data:
87             raise NoPlotsError()
88 
89         return self.render(data, revs, props, self.repo.plot_templates)
90 
91     def diff(self, *args, **kwargs):
92         from .diff import diff
93 
94         return diff(self.repo, *args, **kwargs)
95 
96     def modify(self, path, props=None, unset=None):
97         from dvc.dvcfile import Dvcfile
98 
99         props = props or {}
100         template = props.get("template")
101         if template:
102             self.repo.plot_templates.get_template(template)
103 
104         (out,) = self.repo.find_outs_by_path(path)
105         if not out.plot and unset is not None:
106             raise NotAPlotError(out)
107 
108         # This out will become a plot unless it is one already
109         if not isinstance(out.plot, dict):
110             out.plot = {}
111 
112         for field in unset or ():
113             out.plot.pop(field, None)
114         out.plot.update(props)
115 
116         # Empty dict will move it to non-plots
117         if not out.plot:
118             out.plot = True
119 
120         out.verify_metric()
121 
122         dvcfile = Dvcfile(self.repo, out.stage.path)
123         dvcfile.dump(out.stage, update_pipeline=True, no_lock=True)
124 
125 
126 def _collect_plots(repo, targets=None, rev=None):
127     def _targets_to_outs(targets):
128         for t in targets:
129             try:
130                 (out,) = repo.find_outs_by_path(t)
131                 yield out
132             except OutputNotFoundError:
133                 logger.warning(
134                     "File '{}' was not found at: '{}'. It will not be "
135                     "plotted.".format(t, rev)
136                 )
137 
138     if targets:
139         outs = _targets_to_outs(targets)
140     else:
141         outs = (out for stage in repo.stages for out in stage.outs if out.plot)
142 
143     return {out.path_info: _plot_props(out) for out in outs}
144 
145 
146 def _plot_props(out):
147     if not out.plot:
148         raise NotAPlotError(out)
149     if isinstance(out.plot, list):
150         raise DvcException("Multiple plots per data file not supported.")
151     if isinstance(out.plot, bool):
152         return {}
153 
154     return project(out.plot, PLOT_PROPS)
155 
156 
157 def _prepare_plots(data, revs, props):
158     """Groups data by plot file.
159 
160     Also resolves props conflicts between revs and applies global props.
161     """
162     # we go in order revs are supplied on props conflict first ones win.
163     revs = iter(data) if revs is None else revs
164 
165     plots, props_revs = {}, {}
166     for rev in revs:
167         # Asked for revision without data
168         if rev not in data:
169             continue
170 
171         for datafile, desc in data[rev].items():
172             # props from command line overwrite plot props from out definition
173             full_props = {**desc["props"], **props}
174 
175             if datafile in plots:
176                 saved = plots[datafile]
177                 if saved["props"] != full_props:
178                     logger.warning(
179                         f"Inconsistent plot props for '{datafile}' in "
180                         f"'{props_revs[datafile]}' and '{rev}'. "
181                         f"Going to use ones from '{props_revs[datafile]}'"
182                     )
183 
184                 saved["data"][rev] = desc["data"]
185             else:
186                 plots[datafile] = {
187                     "props": full_props,
188                     "data": {rev: desc["data"]},
189                 }
190                 # Save rev we got props from
191                 props_revs[datafile] = rev
192 
193     return plots
194 
195 
196 def _render(datafile, datas, props, templates):
197     from .data import plot_data, PlotData
198 
199     # Copy it to not modify a passed value
200     props = props.copy()
201 
202     # Add x and y to fields if set
203     fields = props.get("fields")
204     if fields is not None:
205         fields = {*fields, props.get("x"), props.get("y")} - {None}
206 
207     template = templates.load(props.get("template") or "default")
208 
209     # If x is not set add index field
210     if not props.get("x") and template.has_anchor("x"):
211         props["append_index"] = True
212         props["x"] = PlotData.INDEX_FIELD
213 
214     # Parse all data, preprocess it and collect as a list of dicts
215     data = []
216     for rev, datablob in datas.items():
217         rev_data = plot_data(datafile, rev, datablob).to_datapoints(
218             fields=fields,
219             path=props.get("path"),
220             header=props.get("header", True),
221             append_index=props.get("append_index", False),
222         )
223         data.extend(rev_data)
224 
225     # If y is not set then use last field not used yet
226     if not props.get("y") and template.has_anchor("y"):
227         fields = list(first(data))
228         skip = (PlotData.REVISION_FIELD, props.get("x"))
229         props["y"] = first(f for f in reversed(fields) if f not in skip)
230 
231     return template.render(data, props=props)
232
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/dvc/repo/plots/__init__.py b/dvc/repo/plots/__init__.py
--- a/dvc/repo/plots/__init__.py
+++ b/dvc/repo/plots/__init__.py
@@ -17,6 +17,10 @@
         )
 
 
+class PropsNotFoundError(DvcException):
+    pass
+
+
 class Plots:
     def __init__(self, repo):
         self.repo = repo
@@ -93,6 +97,17 @@
 
         return diff(self.repo, *args, **kwargs)
 
+    @staticmethod
+    def _unset(out, props):
+        missing = list(set(props) - set(out.plot.keys()))
+        if missing:
+            raise PropsNotFoundError(
+                f"display properties {missing} not found in plot '{out}'"
+            )
+
+        for prop in props:
+            out.plot.pop(prop)
+
     def modify(self, path, props=None, unset=None):
         from dvc.dvcfile import Dvcfile
 
@@ -109,8 +124,9 @@
         if not isinstance(out.plot, dict):
             out.plot = {}
 
-        for field in unset or ():
-            out.plot.pop(field, None)
+        if unset:
+            self._unset(out, unset)
+
         out.plot.update(props)
 
         # Empty dict will move it to non-plots

{"golden_diff": "diff --git a/dvc/repo/plots/__init__.py b/dvc/repo/plots/__init__.py\n--- a/dvc/repo/plots/__init__.py\n+++ b/dvc/repo/plots/__init__.py\n@@ -17,6 +17,10 @@\n         )\n \n \n+class PropsNotFoundError(DvcException):\n+    pass\n+\n+\n class Plots:\n     def __init__(self, repo):\n         self.repo = repo\n@@ -93,6 +97,17 @@\n \n         return diff(self.repo, *args, **kwargs)\n \n+    @staticmethod\n+    def _unset(out, props):\n+        missing = list(set(props) - set(out.plot.keys()))\n+        if missing:\n+            raise PropsNotFoundError(\n+                f\"display properties {missing} not found in plot '{out}'\"\n+            )\n+\n+        for prop in props:\n+            out.plot.pop(prop)\n+\n     def modify(self, path, props=None, unset=None):\n         from dvc.dvcfile import Dvcfile\n \n@@ -109,8 +124,9 @@\n         if not isinstance(out.plot, dict):\n             out.plot = {}\n \n-        for field in unset or ():\n-            out.plot.pop(field, None)\n+        if unset:\n+            self._unset(out, unset)\n+\n         out.plot.update(props)\n \n         # Empty dict will move it to non-plots\n", "issue": "plots modify minor issues\n```\r\n$ dvc -V\r\n1.0.0a6+4cfc2d\r\n```\r\n\r\n1. ~~Misleading message~~ (#3978)\r\n```\r\n$ dvc plots modify plots.csv --unset y title\r\nAdding stage 'trainme' to 'dvc.yaml'\r\n```\r\n~~It should be `Modifying stage 'trainme' to 'dvc.yaml'`~~\r\n\r\n2. ~~`--unset` should handle errors properly~~\r\n```\r\n$ dvc plots modify plots.csv --unset y_fake_param\r\nAdding stage 'trainme' to 'dvc.yaml'\r\n$ echo $?\r\n0\r\n```\r\n~~It supposed to fail with proper message.~~ https://github.com/iterative/dvc/pull/4044\r\n\r\n3. ~~No template file check before setting~~ (#3995)\r\n```\r\n$ dvc plots modify -t confusion1 plots.csv\r\nAdding stage 'trainme' to 'dvc.yaml'\r\n```\r\nWhile there is no template `confusion1`. It should fail with a proper error message.\r\n\r\n4. ~~not easy to understand `--unset` syntax\r\nIt supposed to be in the end, right? Initially I tried `dvc plots modify --unset y plots.csv` that didn't work.~~ Same as in https://github.com/iterative/dvc/issues/3962\r\n\r\n5. ~~message~~\r\n\r\n```\r\n$ dvc plots -h\r\n    modify            Modify plot props associated with a target file.\r\n```\r\n~~`target file ` info is not needed. It can be `Modify plot properties`.~~\r\n\r\n6. ~~message~~  (#3983)\r\n\r\n```\r\n$ dvc plots modify -h\r\n```\r\n\r\n~~`props` term is often used. It was not clear what foes it mean. We should probably clarify.~~\n", "before_files": [{"content": "import logging\n\nfrom funcy import first, project\n\nfrom dvc.exceptions import DvcException, NoPlotsError, OutputNotFoundError\nfrom dvc.repo.tree import RepoTree\nfrom dvc.schema import PLOT_PROPS\nfrom dvc.utils import relpath\n\nlogger = logging.getLogger(__name__)\n\n\nclass NotAPlotError(DvcException):\n    def __init__(self, out):\n        super().__init__(\n            f\"'{out}' is not a plot. Use `dvc plots modify` to change that.\"\n        )\n\n\nclass Plots:\n    def __init__(self, repo):\n        self.repo = repo\n\n    def collect(self, targets=None, revs=None):\n        \"\"\"Collects all props and data for plots.\n\n        Returns a structure like:\n            {rev: {plots.csv: {\n                props: {x: ..., \"header\": ..., ...},\n                data: \"...data as a string...\",\n            }}}\n        Data parsing is postponed, since it's affected by props.\n        \"\"\"\n        targets = [targets] if isinstance(targets, str) else targets or []\n        data = {}\n        for rev in self.repo.brancher(revs=revs):\n            # .brancher() adds unwanted workspace\n            if revs is not None and rev not in revs:\n                continue\n            rev = rev or \"workspace\"\n\n            tree = RepoTree(self.repo)\n            plots = _collect_plots(self.repo, targets, rev)\n            for path_info, props in plots.items():\n                datafile = relpath(path_info, self.repo.root_dir)\n                if rev not in data:\n                    data[rev] = {}\n                data[rev].update({datafile: {\"props\": props}})\n\n                # Load data from git or dvc cache\n                try:\n                    with tree.open(path_info) as fd:\n                        data[rev][datafile][\"data\"] = fd.read()\n                except FileNotFoundError as e:\n                    # This might happen simply because cache is absent\n                    print(e)\n                    pass\n\n        return data\n\n    @staticmethod\n    def render(data, revs=None, props=None, templates=None):\n        \"\"\"Renders plots\"\"\"\n        props = props or {}\n\n        # Merge data by plot file and apply overriding props\n        plots = _prepare_plots(data, revs, props)\n\n        return {\n            datafile: _render(datafile, desc[\"data\"], desc[\"props\"], templates)\n            for datafile, desc in plots.items()\n        }\n\n    def show(self, targets=None, revs=None, props=None):\n        from .data import NoMetricInHistoryError\n\n        data = self.collect(targets, revs)\n\n        # If any mentioned plot doesn't have any data then that's an error\n        targets = [targets] if isinstance(targets, str) else targets or []\n        for target in targets:\n            if not any(\"data\" in d[target] for d in data.values()):\n                raise NoMetricInHistoryError(target)\n\n        # No data at all is a special error with a special message\n        if not data:\n            raise NoPlotsError()\n\n        return self.render(data, revs, props, self.repo.plot_templates)\n\n    def diff(self, *args, **kwargs):\n        from .diff import diff\n\n        return diff(self.repo, *args, **kwargs)\n\n    def modify(self, path, props=None, unset=None):\n        from dvc.dvcfile import Dvcfile\n\n        props = props or {}\n        template = props.get(\"template\")\n        if template:\n            self.repo.plot_templates.get_template(template)\n\n        (out,) = self.repo.find_outs_by_path(path)\n        if not out.plot and unset is not None:\n            raise NotAPlotError(out)\n\n        # This out will become a plot unless it is one already\n        if not isinstance(out.plot, dict):\n            out.plot = {}\n\n        for field in unset or ():\n            out.plot.pop(field, None)\n        out.plot.update(props)\n\n        # Empty dict will move it to non-plots\n        if not out.plot:\n            out.plot = True\n\n        out.verify_metric()\n\n        dvcfile = Dvcfile(self.repo, out.stage.path)\n        dvcfile.dump(out.stage, update_pipeline=True, no_lock=True)\n\n\ndef _collect_plots(repo, targets=None, rev=None):\n    def _targets_to_outs(targets):\n        for t in targets:\n            try:\n                (out,) = repo.find_outs_by_path(t)\n                yield out\n            except OutputNotFoundError:\n                logger.warning(\n                    \"File '{}' was not found at: '{}'. It will not be \"\n                    \"plotted.\".format(t, rev)\n                )\n\n    if targets:\n        outs = _targets_to_outs(targets)\n    else:\n        outs = (out for stage in repo.stages for out in stage.outs if out.plot)\n\n    return {out.path_info: _plot_props(out) for out in outs}\n\n\ndef _plot_props(out):\n    if not out.plot:\n        raise NotAPlotError(out)\n    if isinstance(out.plot, list):\n        raise DvcException(\"Multiple plots per data file not supported.\")\n    if isinstance(out.plot, bool):\n        return {}\n\n    return project(out.plot, PLOT_PROPS)\n\n\ndef _prepare_plots(data, revs, props):\n    \"\"\"Groups data by plot file.\n\n    Also resolves props conflicts between revs and applies global props.\n    \"\"\"\n    # we go in order revs are supplied on props conflict first ones win.\n    revs = iter(data) if revs is None else revs\n\n    plots, props_revs = {}, {}\n    for rev in revs:\n        # Asked for revision without data\n        if rev not in data:\n            continue\n\n        for datafile, desc in data[rev].items():\n            # props from command line overwrite plot props from out definition\n            full_props = {**desc[\"props\"], **props}\n\n            if datafile in plots:\n                saved = plots[datafile]\n                if saved[\"props\"] != full_props:\n                    logger.warning(\n                        f\"Inconsistent plot props for '{datafile}' in \"\n                        f\"'{props_revs[datafile]}' and '{rev}'. \"\n                        f\"Going to use ones from '{props_revs[datafile]}'\"\n                    )\n\n                saved[\"data\"][rev] = desc[\"data\"]\n            else:\n                plots[datafile] = {\n                    \"props\": full_props,\n                    \"data\": {rev: desc[\"data\"]},\n                }\n                # Save rev we got props from\n                props_revs[datafile] = rev\n\n    return plots\n\n\ndef _render(datafile, datas, props, templates):\n    from .data import plot_data, PlotData\n\n    # Copy it to not modify a passed value\n    props = props.copy()\n\n    # Add x and y to fields if set\n    fields = props.get(\"fields\")\n    if fields is not None:\n        fields = {*fields, props.get(\"x\"), props.get(\"y\")} - {None}\n\n    template = templates.load(props.get(\"template\") or \"default\")\n\n    # If x is not set add index field\n    if not props.get(\"x\") and template.has_anchor(\"x\"):\n        props[\"append_index\"] = True\n        props[\"x\"] = PlotData.INDEX_FIELD\n\n    # Parse all data, preprocess it and collect as a list of dicts\n    data = []\n    for rev, datablob in datas.items():\n        rev_data = plot_data(datafile, rev, datablob).to_datapoints(\n            fields=fields,\n            path=props.get(\"path\"),\n            header=props.get(\"header\", True),\n            append_index=props.get(\"append_index\", False),\n        )\n        data.extend(rev_data)\n\n    # If y is not set then use last field not used yet\n    if not props.get(\"y\") and template.has_anchor(\"y\"):\n        fields = list(first(data))\n        skip = (PlotData.REVISION_FIELD, props.get(\"x\"))\n        props[\"y\"] = first(f for f in reversed(fields) if f not in skip)\n\n    return template.render(data, props=props)\n", "path": "dvc/repo/plots/__init__.py"}], "after_files": [{"content": "import logging\n\nfrom funcy import first, project\n\nfrom dvc.exceptions import DvcException, NoPlotsError, OutputNotFoundError\nfrom dvc.repo.tree import RepoTree\nfrom dvc.schema import PLOT_PROPS\nfrom dvc.utils import relpath\n\nlogger = logging.getLogger(__name__)\n\n\nclass NotAPlotError(DvcException):\n    def __init__(self, out):\n        super().__init__(\n            f\"'{out}' is not a plot. Use `dvc plots modify` to change that.\"\n        )\n\n\nclass PropsNotFoundError(DvcException):\n    pass\n\n\nclass Plots:\n    def __init__(self, repo):\n        self.repo = repo\n\n    def collect(self, targets=None, revs=None):\n        \"\"\"Collects all props and data for plots.\n\n        Returns a structure like:\n            {rev: {plots.csv: {\n                props: {x: ..., \"header\": ..., ...},\n                data: \"...data as a string...\",\n            }}}\n        Data parsing is postponed, since it's affected by props.\n        \"\"\"\n        targets = [targets] if isinstance(targets, str) else targets or []\n        data = {}\n        for rev in self.repo.brancher(revs=revs):\n            # .brancher() adds unwanted workspace\n            if revs is not None and rev not in revs:\n                continue\n            rev = rev or \"workspace\"\n\n            tree = RepoTree(self.repo)\n            plots = _collect_plots(self.repo, targets, rev)\n            for path_info, props in plots.items():\n                datafile = relpath(path_info, self.repo.root_dir)\n                if rev not in data:\n                    data[rev] = {}\n                data[rev].update({datafile: {\"props\": props}})\n\n                # Load data from git or dvc cache\n                try:\n                    with tree.open(path_info) as fd:\n                        data[rev][datafile][\"data\"] = fd.read()\n                except FileNotFoundError as e:\n                    # This might happen simply because cache is absent\n                    print(e)\n                    pass\n\n        return data\n\n    @staticmethod\n    def render(data, revs=None, props=None, templates=None):\n        \"\"\"Renders plots\"\"\"\n        props = props or {}\n\n        # Merge data by plot file and apply overriding props\n        plots = _prepare_plots(data, revs, props)\n\n        return {\n            datafile: _render(datafile, desc[\"data\"], desc[\"props\"], templates)\n            for datafile, desc in plots.items()\n        }\n\n    def show(self, targets=None, revs=None, props=None):\n        from .data import NoMetricInHistoryError\n\n        data = self.collect(targets, revs)\n\n        # If any mentioned plot doesn't have any data then that's an error\n        targets = [targets] if isinstance(targets, str) else targets or []\n        for target in targets:\n            if not any(\"data\" in d[target] for d in data.values()):\n                raise NoMetricInHistoryError(target)\n\n        # No data at all is a special error with a special message\n        if not data:\n            raise NoPlotsError()\n\n        return self.render(data, revs, props, self.repo.plot_templates)\n\n    def diff(self, *args, **kwargs):\n        from .diff import diff\n\n        return diff(self.repo, *args, **kwargs)\n\n    @staticmethod\n    def _unset(out, props):\n        missing = list(set(props) - set(out.plot.keys()))\n        if missing:\n            raise PropsNotFoundError(\n                f\"display properties {missing} not found in plot '{out}'\"\n            )\n\n        for prop in props:\n            out.plot.pop(prop)\n\n    def modify(self, path, props=None, unset=None):\n        from dvc.dvcfile import Dvcfile\n\n        props = props or {}\n        template = props.get(\"template\")\n        if template:\n            self.repo.plot_templates.get_template(template)\n\n        (out,) = self.repo.find_outs_by_path(path)\n        if not out.plot and unset is not None:\n            raise NotAPlotError(out)\n\n        # This out will become a plot unless it is one already\n        if not isinstance(out.plot, dict):\n            out.plot = {}\n\n        if unset:\n            self._unset(out, unset)\n\n        out.plot.update(props)\n\n        # Empty dict will move it to non-plots\n        if not out.plot:\n            out.plot = True\n\n        out.verify_metric()\n\n        dvcfile = Dvcfile(self.repo, out.stage.path)\n        dvcfile.dump(out.stage, update_pipeline=True, no_lock=True)\n\n\ndef _collect_plots(repo, targets=None, rev=None):\n    def _targets_to_outs(targets):\n        for t in targets:\n            try:\n                (out,) = repo.find_outs_by_path(t)\n                yield out\n            except OutputNotFoundError:\n                logger.warning(\n                    \"File '{}' was not found at: '{}'. It will not be \"\n                    \"plotted.\".format(t, rev)\n                )\n\n    if targets:\n        outs = _targets_to_outs(targets)\n    else:\n        outs = (out for stage in repo.stages for out in stage.outs if out.plot)\n\n    return {out.path_info: _plot_props(out) for out in outs}\n\n\ndef _plot_props(out):\n    if not out.plot:\n        raise NotAPlotError(out)\n    if isinstance(out.plot, list):\n        raise DvcException(\"Multiple plots per data file not supported.\")\n    if isinstance(out.plot, bool):\n        return {}\n\n    return project(out.plot, PLOT_PROPS)\n\n\ndef _prepare_plots(data, revs, props):\n    \"\"\"Groups data by plot file.\n\n    Also resolves props conflicts between revs and applies global props.\n    \"\"\"\n    # we go in order revs are supplied on props conflict first ones win.\n    revs = iter(data) if revs is None else revs\n\n    plots, props_revs = {}, {}\n    for rev in revs:\n        # Asked for revision without data\n        if rev not in data:\n            continue\n\n        for datafile, desc in data[rev].items():\n            # props from command line overwrite plot props from out definition\n            full_props = {**desc[\"props\"], **props}\n\n            if datafile in plots:\n                saved = plots[datafile]\n                if saved[\"props\"] != full_props:\n                    logger.warning(\n                        f\"Inconsistent plot props for '{datafile}' in \"\n                        f\"'{props_revs[datafile]}' and '{rev}'. \"\n                        f\"Going to use ones from '{props_revs[datafile]}'\"\n                    )\n\n                saved[\"data\"][rev] = desc[\"data\"]\n            else:\n                plots[datafile] = {\n                    \"props\": full_props,\n                    \"data\": {rev: desc[\"data\"]},\n                }\n                # Save rev we got props from\n                props_revs[datafile] = rev\n\n    return plots\n\n\ndef _render(datafile, datas, props, templates):\n    from .data import plot_data, PlotData\n\n    # Copy it to not modify a passed value\n    props = props.copy()\n\n    # Add x and y to fields if set\n    fields = props.get(\"fields\")\n    if fields is not None:\n        fields = {*fields, props.get(\"x\"), props.get(\"y\")} - {None}\n\n    template = templates.load(props.get(\"template\") or \"default\")\n\n    # If x is not set add index field\n    if not props.get(\"x\") and template.has_anchor(\"x\"):\n        props[\"append_index\"] = True\n        props[\"x\"] = PlotData.INDEX_FIELD\n\n    # Parse all data, preprocess it and collect as a list of dicts\n    data = []\n    for rev, datablob in datas.items():\n        rev_data = plot_data(datafile, rev, datablob).to_datapoints(\n            fields=fields,\n            path=props.get(\"path\"),\n            header=props.get(\"header\", True),\n            append_index=props.get(\"append_index\", False),\n        )\n        data.extend(rev_data)\n\n    # If y is not set then use last field not used yet\n    if not props.get(\"y\") and template.has_anchor(\"y\"):\n        fields = list(first(data))\n        skip = (PlotData.REVISION_FIELD, props.get(\"x\"))\n        props[\"y\"] = first(f for f in reversed(fields) if f not in skip)\n\n    return template.render(data, props=props)\n", "path": "dvc/repo/plots/__init__.py"}]}