-
Notifications
You must be signed in to change notification settings - Fork 1.3k
[WIP] plot metrics: initital #3569
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
00df3e7
init
pared 4747075
rename to plot data insertion basig on dicts update
pared f0434f5
revision support
pared 4235d29
roll back revision
pared 731b785
plot makedirs for backward compatibility
pared c7edae1
log path
pared b447d5e
pretty plot link to visualization page
pared dc1a120
make target default title
pared b51c9a1
efiop review
pared 5ae2527
efiop review
pared 12093aa
proper id generation
pared 258956c
proper id generation
pared 5369aeb
add confusion matrix template
pared eda5874
refactor tests
pared 2701550
plot from dvct file
pared File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| import argparse | ||
| import logging | ||
| import os | ||
|
|
||
| from dvc.command.base import append_doc_link, CmdBase | ||
| from dvc.utils import format_link | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| class CmdPlot(CmdBase): | ||
| def run(self): | ||
| path = self.repo.plot(self.args.target, template=self.args.template,) | ||
| logger.info( | ||
| "Your can see your plot by opening {} in your " | ||
| "browser!".format( | ||
| format_link( | ||
| "file://{}".format(os.path.join(self.repo.root_dir, path)) | ||
| ) | ||
| ) | ||
| ) | ||
| return 0 | ||
|
|
||
|
|
||
| def add_parser(subparsers, parent_parser): | ||
| PLOT_HELP = "Visualize target metric file using {}.".format( | ||
| format_link("https://vega.github.io") | ||
| ) | ||
|
|
||
| plot_parser = subparsers.add_parser( | ||
| "plot", | ||
| parents=[parent_parser], | ||
| description=append_doc_link(PLOT_HELP, "plot"), | ||
| help=PLOT_HELP, | ||
| formatter_class=argparse.RawDescriptionHelpFormatter, | ||
| ) | ||
| plot_parser.add_argument( | ||
| "--template", nargs="?", help="Template file to choose." | ||
| ) | ||
| plot_parser.add_argument( | ||
| "target", nargs="?", help="Metric files to visualize." | ||
| ) | ||
| plot_parser.set_defaults(func=CmdPlot) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| import json | ||
| import logging | ||
| import os | ||
|
|
||
| from funcy import cached_property | ||
|
|
||
| from dvc.exceptions import DvcException | ||
| from dvc.utils.fs import makedirs | ||
|
|
||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| class Template: | ||
| INDENT = 4 | ||
| SEPARATORS = (",", ": ") | ||
|
|
||
| def __init__(self, templates_dir): | ||
| self.plot_templates_dir = templates_dir | ||
|
|
||
| def dump(self): | ||
| import json | ||
|
|
||
| makedirs(self.plot_templates_dir, exist_ok=True) | ||
|
|
||
| if not os.path.exists(self.plot_templates_dir): | ||
| makedirs(self.plot_templates_dir) | ||
|
|
||
| with open( | ||
| os.path.join(self.plot_templates_dir, self.TEMPLATE_NAME), "w+" | ||
| ) as fd: | ||
| json.dump( | ||
| self.DEFAULT_CONTENT, | ||
| fd, | ||
| indent=self.INDENT, | ||
| separators=self.SEPARATORS, | ||
| ) | ||
|
|
||
| def load_template(self, path): | ||
| try: | ||
| with open(path, "r") as fd: | ||
| return json.load(fd) | ||
| except FileNotFoundError: | ||
| try: | ||
| with open( | ||
| os.path.join(self.plot_templates_dir, path), "r" | ||
| ) as fd: | ||
| return json.load(fd) | ||
| except FileNotFoundError: | ||
| raise DvcException("Not in repo nor in defaults") | ||
|
|
||
| def fill(self, template_path, data, data_src=""): | ||
| assert isinstance(data, list) | ||
| assert all({"x", "y", "revision"} == set(d.keys()) for d in data) | ||
|
|
||
| update_dict = {"data": {"values": data}, "title": data_src} | ||
|
|
||
| vega_spec = self.load_template(template_path) | ||
| vega_spec.update(update_dict) | ||
| return vega_spec | ||
|
|
||
|
|
||
| class DefaultLinearTemplate(Template): | ||
| TEMPLATE_NAME = "default.json" | ||
|
|
||
| DEFAULT_CONTENT = { | ||
| "$schema": "https://vega.github.io/schema/vega-lite/v4.json", | ||
| "data": {"values": []}, | ||
| "mark": {"type": "line"}, | ||
| "encoding": { | ||
| "x": {"field": "x", "type": "quantitative"}, | ||
| "y": {"field": "y", "type": "quantitative"}, | ||
| "color": {"field": "revision", "type": "nominal"}, | ||
| }, | ||
| } | ||
|
|
||
|
|
||
| class DefaultConfusionTemplate(Template): | ||
| TEMPLATE_NAME = "cf.json" | ||
| DEFAULT_CONTENT = { | ||
| "$schema": "https://vega.github.io/schema/vega-lite/v4.json", | ||
| "data": {"values": []}, | ||
| "mark": "rect", | ||
| "encoding": { | ||
| "x": { | ||
| "field": "x", | ||
| "type": "nominal", | ||
| "sort": "ascending", | ||
| "title": "Predicted value", | ||
| }, | ||
| "y": { | ||
| "field": "y", | ||
| "type": "nominal", | ||
| "sort": "ascending", | ||
| "title": "Actual value", | ||
| }, | ||
| "color": {"aggregate": "count", "type": "quantitative"}, | ||
| }, | ||
| } | ||
|
|
||
|
|
||
| class PlotTemplates: | ||
| TEMPLATES_DIR = "plot" | ||
| TEMPLATES = [DefaultLinearTemplate, DefaultConfusionTemplate] | ||
|
|
||
| @cached_property | ||
| def templates_dir(self): | ||
| return os.path.join(self.dvc_dir, self.TEMPLATES_DIR) | ||
|
|
||
| def __init__(self, dvc_dir): | ||
| self.dvc_dir = dvc_dir | ||
|
|
||
| if not os.path.exists(self.templates_dir): | ||
| makedirs(self.templates_dir, exist_ok=True) | ||
| for t in self.TEMPLATES: | ||
| t(self.templates_dir).dump() | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| import json | ||
| import logging | ||
| import random | ||
| import re | ||
| import string | ||
|
|
||
| from dvc.exceptions import DvcException | ||
| from dvc.plot import Template | ||
| from dvc.repo import locked | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
| PAGE_HTML = """<html> | ||
| <head> | ||
| <title>dvc plot</title> | ||
| <script src="https://cdn.jsdelivr.net/npm/vega@5.10.0"></script> | ||
| <script src="https://cdn.jsdelivr.net/npm/vega-lite@4.8.1"></script> | ||
| <script src="https://cdn.jsdelivr.net/npm/vega-embed@6.5.1"></script> | ||
| </head> | ||
| <body> | ||
| {divs} | ||
| </body> | ||
| </html>""" | ||
|
|
||
| DIV_HTML = """<div id = "{id}"></div> | ||
| <script type = "text/javascript"> | ||
| var spec = {vega_json}; | ||
| vegaEmbed('#{id}', spec); | ||
| </script>""" | ||
|
|
||
|
|
||
| def _save_plot_html(divs, path): | ||
| page = PAGE_HTML.format(divs="\n".join(divs)) | ||
| with open(path, "w") as fobj: | ||
| fobj.write(page) | ||
|
|
||
|
|
||
| def _prepare_div(vega_dict): | ||
| id = "".join(random.sample(string.ascii_lowercase, 8)) | ||
| return DIV_HTML.format( | ||
| id=str(id), | ||
| vega_json=json.dumps(vega_dict, indent=4, separators=(",", ": ")), | ||
| ) | ||
|
|
||
|
|
||
| def _load_data(tree, target, revision="current workspace"): | ||
| with tree.open(target, "r") as fobj: | ||
| data = json.load(fobj) | ||
| for d in data: | ||
| d["revision"] = revision | ||
| return data | ||
|
|
||
|
|
||
| def _parse_plots(path): | ||
| with open(path, "r") as fobj: | ||
| content = fobj.read() | ||
|
|
||
| plot_regex = re.compile("<DVC_PLOT::.*>") | ||
|
|
||
| plots = list(plot_regex.findall(content)) | ||
| return False, plots | ||
|
|
||
|
|
||
| def _parse_plot_str(plot_str): | ||
| content = plot_str.replace("<", "") | ||
| content = content.replace(">", "") | ||
| args = content.split("::")[1:] | ||
| if len(args) == 2: | ||
| return args | ||
| elif len(args) == 1: | ||
| return args[0], "default.json" | ||
| raise DvcException("Error parsing") | ||
|
|
||
|
|
||
| def to_div(repo, plot_str): | ||
| datafile, templatefile = _parse_plot_str(plot_str) | ||
|
|
||
| data = _load_data(repo.tree, datafile) | ||
| vega_plot_json = Template(repo.plot_templates.templates_dir).fill( | ||
| templatefile, data, datafile | ||
| ) | ||
| return _prepare_div(vega_plot_json) | ||
|
|
||
|
|
||
| @locked | ||
| def plot(repo, template_file, revisions=None): | ||
| if revisions is None: | ||
| revisions = [] | ||
|
|
||
| is_html, plot_strings = _parse_plots(template_file) | ||
| m = {plot_str: to_div(repo, plot_str) for plot_str in plot_strings} | ||
|
|
||
| result = template_file.replace(".dvct", ".html") | ||
| if not is_html: | ||
| _save_plot_html( | ||
| [m[p] for p in plot_strings], result, | ||
| ) | ||
| return result | ||
| else: | ||
| raise NotImplementedError |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
probably a leftover