Skip to content

Commit e553511

Browse files
paredefiop
andauthored
Metrics - plotting for multiple revisions initial (#3577)
* init * rename to plot data insertion basig on dicts update * revision support * roll back revision * plot makedirs for backward compatibility * log path * pretty plot link to visualization page * make target default title * efiop review * efiop review * plot multiple initial * add some missing metric file tests * proper id generation * proper id generation * add confusion matrix template * refactor tests * plot from dvct file * plot from dvct * brush up commands * fix confusion matrix multiple plot * plot: change confusion matrix data schema * should be working as intended * support for src file in dvct files * minor fixes * plot: support json templates * plot: rename confusion template * plot: polish command behaviour * fix test for json * plot: test command * some minor fixes for tests * plot: unit test loading * plot: unit test loading * plot: handle TODOS * cleanup * use mocker * plot: support tsv * plot: command refactoring * plot: fix windows issues with tests * plot: test: some more windows fixes * plot: _load_from_revisions complexity fix * plot: reduce complexity * plot: complexity reduction * plot: deepsource suggestions * plot: move template path evaluation * fixup * fixup * exception on no datafile and no template * json metric load with OrderedDict * plot: improve handling non-existing files on revisions * plot: improve handling non-existing files on revisions * change default plot path * some exceptions and fixes * add yaml metrics support * fixup * some more suggestions * default filename fix * efiop review requests * log exception on failur * move revisions deduction to commands * json templates * extract template filling to separate method * some parsing improvements * add columns functionality * extract default data transformation to separate method * plot: initial support for jsonpath * plot: rename columns to filters, tests are dict based * plot: fixups * plot: refactoring * repo: plot: convert to package * plot: data loading refactor, support searching for data * plot: raise if wrong fields provided * plot: command description * plot: default: pass y axis info for default plot * plot: get rid of fieldnames, expect ordered data * plot: handle default plot in separate method * plot: fix default * plot: command option names fixes * refactoring * fixes * plot: provide option for stdout redirection * plot: rename show-json to no-html * plot: add no-csv-header option * plot: improve error message for wrongly structured metric * plot: match template name exactly, whit suffix appended only * plot: dmpetrov and ivan review * plot: refactor --stdout help message * plot: move template to repo/plot * plot: add -x and -y options * plot: add -x and -y options * plot: command: order change * plot: scatter * plot: rename confusion matrix template, new name generation format * plot: add title anchor * plot: review from jorgeorpinel * plot: rename filter and result options to select and file * plot: add --title, --x-title, --y-title * plot: xlab ylab * Update dvc/repo/plot/template.py Co-authored-by: Ruslan Kuprieiev <[email protected]> * Update dvc/repo/plot/template.py Co-authored-by: Ruslan Kuprieiev <[email protected]> * efiop review * plot: bash completion * plot: static code analysis fixes Co-authored-by: Ruslan Kuprieiev <[email protected]>
1 parent 827c994 commit e553511

File tree

12 files changed

+1662
-2
lines changed

12 files changed

+1662
-2
lines changed

dvc/cli.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
update,
3636
version,
3737
git_hook,
38+
plot,
3839
)
3940
from .command.base import fix_subparsers
4041
from .exceptions import DvcParserError
@@ -74,6 +75,7 @@
7475
version,
7576
update,
7677
git_hook,
78+
plot,
7779
]
7880

7981

dvc/command/plot.py

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
import argparse
2+
import logging
3+
import os
4+
5+
from dvc.command.base import append_doc_link, CmdBase, fix_subparsers
6+
from dvc.exceptions import DvcException
7+
from dvc.repo.plot.data import WORKSPACE_REVISION_NAME
8+
9+
logger = logging.getLogger(__name__)
10+
11+
12+
class CmdPLot(CmdBase):
13+
def _revisions(self):
14+
raise NotImplementedError
15+
16+
def _result_file(self):
17+
if self.args.file:
18+
return self.args.file
19+
20+
extension = self._result_extension()
21+
base = self._result_basename()
22+
23+
result_file = base + extension
24+
return result_file
25+
26+
def _result_basename(self):
27+
if self.args.datafile:
28+
return self.args.datafile
29+
return "plot"
30+
31+
def _result_extension(self):
32+
if not self.args.no_html:
33+
return ".html"
34+
elif self.args.template:
35+
return os.path.splitext(self.args.template)[-1]
36+
return ".json"
37+
38+
def run(self):
39+
fields = None
40+
jsonpath = None
41+
if self.args.select:
42+
if self.args.select.startswith("$"):
43+
jsonpath = self.args.select
44+
else:
45+
fields = set(self.args.select.split(","))
46+
try:
47+
plot_string = self.repo.plot(
48+
datafile=self.args.datafile,
49+
template=self.args.template,
50+
revisions=self._revisions(),
51+
fields=fields,
52+
x_field=self.args.x,
53+
y_field=self.args.y,
54+
path=jsonpath,
55+
embed=not self.args.no_html,
56+
csv_header=not self.args.no_csv_header,
57+
title=self.args.title,
58+
x_title=self.args.xlab,
59+
y_title=self.args.ylab,
60+
)
61+
62+
if self.args.stdout:
63+
logger.info(plot_string)
64+
else:
65+
result_path = self._result_file()
66+
with open(result_path, "w") as fobj:
67+
fobj.write(plot_string)
68+
69+
logger.info(
70+
"file://{}".format(
71+
os.path.join(self.repo.root_dir, result_path)
72+
)
73+
)
74+
75+
except DvcException:
76+
logger.exception("")
77+
return 1
78+
79+
return 0
80+
81+
82+
class CmdPlotShow(CmdPLot):
83+
def _revisions(self):
84+
return None
85+
86+
87+
class CmdPlotDiff(CmdPLot):
88+
def _revisions(self):
89+
revisions = self.args.revisions or []
90+
if len(revisions) <= 1:
91+
if len(revisions) == 0 and self.repo.scm.is_dirty():
92+
revisions.append("HEAD")
93+
revisions.append(WORKSPACE_REVISION_NAME)
94+
return revisions
95+
96+
97+
def add_parser(subparsers, parent_parser):
98+
PLOT_HELP = (
99+
"Generating plots for continuous metrics stored in structured files "
100+
"(JSON, CSV, TSV)."
101+
)
102+
103+
plot_parser = subparsers.add_parser(
104+
"plot",
105+
parents=[parent_parser],
106+
description=append_doc_link(PLOT_HELP, "plot"),
107+
help=PLOT_HELP,
108+
formatter_class=argparse.RawDescriptionHelpFormatter,
109+
)
110+
plot_subparsers = plot_parser.add_subparsers(
111+
dest="cmd",
112+
help="Use `dvc plot CMD --help` to display command-specific help.",
113+
)
114+
115+
fix_subparsers(plot_subparsers)
116+
117+
SHOW_HELP = "Generate a plot image file from a continuous metrics file."
118+
plot_show_parser = plot_subparsers.add_parser(
119+
"show",
120+
parents=[parent_parser],
121+
description=append_doc_link(SHOW_HELP, "plot/show"),
122+
help=SHOW_HELP,
123+
formatter_class=argparse.RawDescriptionHelpFormatter,
124+
)
125+
plot_show_parser.add_argument(
126+
"-t",
127+
"--template",
128+
nargs="?",
129+
default=None,
130+
help="File to be injected with data.",
131+
)
132+
plot_show_parser.add_argument(
133+
"-f", "--file", default=None, help="Name of the generated file."
134+
)
135+
plot_show_parser.add_argument(
136+
"-s",
137+
"--select",
138+
default=None,
139+
help="Choose which field(s) or JSONPath to include in the plot.",
140+
)
141+
plot_show_parser.add_argument(
142+
"-x", default=None, help="Field name for x axis."
143+
)
144+
plot_show_parser.add_argument(
145+
"-y", default=None, help="Field name for y axis."
146+
)
147+
plot_show_parser.add_argument(
148+
"--stdout",
149+
action="store_true",
150+
default=False,
151+
help="Print plot specification to stdout.",
152+
)
153+
plot_show_parser.add_argument(
154+
"--no-csv-header",
155+
action="store_true",
156+
default=False,
157+
help="Required when CSV or TSV datafile does not have a header.",
158+
)
159+
plot_show_parser.add_argument(
160+
"--no-html",
161+
action="store_true",
162+
default=False,
163+
help="Do not wrap Vega plot JSON with HTML.",
164+
)
165+
plot_show_parser.add_argument("--title", default=None, help="Plot title.")
166+
plot_show_parser.add_argument("--xlab", default=None, help="X axis title.")
167+
plot_show_parser.add_argument("--ylab", default=None, help="Y axis title.")
168+
plot_show_parser.add_argument(
169+
"datafile",
170+
nargs="?",
171+
default=None,
172+
help="Continuous metrics file to visualize.",
173+
)
174+
plot_show_parser.set_defaults(func=CmdPlotShow)
175+
176+
PLOT_DIFF_HELP = (
177+
"Plot continuous metrics differences between commits in the DVC "
178+
"repository, or between the last commit and the workspace."
179+
)
180+
plot_diff_parser = plot_subparsers.add_parser(
181+
"diff",
182+
parents=[parent_parser],
183+
description=append_doc_link(PLOT_DIFF_HELP, "plot/diff"),
184+
help=PLOT_DIFF_HELP,
185+
formatter_class=argparse.RawDescriptionHelpFormatter,
186+
)
187+
plot_diff_parser.add_argument(
188+
"-t",
189+
"--template",
190+
nargs="?",
191+
default=None,
192+
help="File to be injected with data.",
193+
)
194+
plot_diff_parser.add_argument(
195+
"-d",
196+
"--datafile",
197+
nargs="?",
198+
default=None,
199+
help="Continuous metrics file to visualize.",
200+
)
201+
plot_diff_parser.add_argument(
202+
"-f", "--file", default=None, help="Name of the generated file."
203+
)
204+
plot_diff_parser.add_argument(
205+
"-s",
206+
"--select",
207+
default=None,
208+
help="Choose which field(s) or JSONPath to include in the plot.",
209+
)
210+
plot_diff_parser.add_argument(
211+
"-x", default=None, help="Field name for x axis."
212+
)
213+
plot_diff_parser.add_argument(
214+
"-y", default=None, help="Field name for y axis."
215+
)
216+
plot_diff_parser.add_argument(
217+
"--stdout",
218+
action="store_true",
219+
default=False,
220+
help="Print plot specification to stdout.",
221+
)
222+
plot_diff_parser.add_argument(
223+
"--no-csv-header",
224+
action="store_true",
225+
default=False,
226+
help="Provided CSV ot TSV datafile does not have a header.",
227+
)
228+
plot_diff_parser.add_argument(
229+
"--no-html",
230+
action="store_true",
231+
default=False,
232+
help="Do not wrap Vega plot JSON with HTML.",
233+
)
234+
plot_diff_parser.add_argument("--title", default=None, help="Plot title.")
235+
plot_diff_parser.add_argument("--xlab", default=None, help="X axis title.")
236+
plot_diff_parser.add_argument("--ylab", default=None, help="Y axis title.")
237+
plot_diff_parser.add_argument(
238+
"revisions",
239+
nargs="*",
240+
default=None,
241+
help="Git revisions to plot from",
242+
)
243+
plot_diff_parser.set_defaults(func=CmdPlotDiff)

dvc/repo/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ class Repo(object):
6161
from dvc.repo.get import get
6262
from dvc.repo.get_url import get_url
6363
from dvc.repo.update import update
64+
from dvc.repo.plot import plot
6465

6566
def __init__(self, root_dir=None):
6667
from dvc.state import State
@@ -426,6 +427,12 @@ def stages(self):
426427
"""
427428
return self._collect_stages()
428429

430+
@cached_property
431+
def plot_templates(self):
432+
from dvc.repo.plot.template import PlotTemplates
433+
434+
return PlotTemplates(self.dvc_dir)
435+
429436
def _collect_stages(self):
430437
from dvc.dvcfile import Dvcfile, is_valid_filename
431438

dvc/repo/init.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ def init(root_dir=os.curdir, no_scm=False, force=False, subdir=False):
102102

103103
proj = Repo(root_dir)
104104

105-
scm.add([config.files["repo"]])
105+
scm.add([config.files["repo"], proj.plot_templates.templates_dir])
106106

107107
if scm.ignore_file:
108108
scm.add([os.path.join(dvc_dir, scm.ignore_file)])

0 commit comments

Comments
 (0)