mirror of
https://github.com/Stone-Red-Code/dvc.git
synced 2026-11-10 15:28:34 +01:00
dvc: pipeline show: create ASCII visualizations for DAG
Signed-off-by: Ruslan Kuprieiev <[email protected]>
This commit is contained in:
@@ -755,6 +755,11 @@ def parse_args(argv=None):
|
||||
default=False,
|
||||
help='Print output files instead of '
|
||||
'paths to DVC files.')
|
||||
pipeline_show_parser.add_argument(
|
||||
'--ascii',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Output DAG as ASCII.')
|
||||
pipeline_show_parser.add_argument(
|
||||
'targets',
|
||||
nargs='+',
|
||||
|
||||
+51
-1
@@ -2,6 +2,7 @@ import os
|
||||
|
||||
from dvc.exceptions import DvcException
|
||||
from dvc.command.base import CmdBase
|
||||
from dvc.dagascii import Dagascii
|
||||
|
||||
|
||||
class CmdPipelineShow(CmdBase):
|
||||
@@ -23,10 +24,59 @@ class CmdPipelineShow(CmdBase):
|
||||
else:
|
||||
self.project.logger.info(n)
|
||||
|
||||
def _show_ascii(self, target, commands, outs):
|
||||
import networkx
|
||||
from dvc.stage import Stage
|
||||
|
||||
stage = Stage.load(self.project, target)
|
||||
node = os.path.relpath(stage.path, self.project.root_dir)
|
||||
|
||||
pipelines = list(filter(lambda g: node in g.nodes(),
|
||||
self.project.pipelines()))
|
||||
if len(pipelines) == 0:
|
||||
return
|
||||
|
||||
assert len(pipelines) == 1
|
||||
G = pipelines[0]
|
||||
stages = networkx.get_node_attributes(G, 'stage')
|
||||
|
||||
nodes = set()
|
||||
edges = []
|
||||
for e in G.edges():
|
||||
from_stage = stages[e[0]]
|
||||
to_stage = stages[e[1]]
|
||||
if commands:
|
||||
if to_stage.cmd is None:
|
||||
continue
|
||||
nodes.add(from_stage.cmd)
|
||||
nodes.add(to_stage.cmd)
|
||||
edges.append((from_stage.cmd, to_stage.cmd))
|
||||
elif outs:
|
||||
for from_out in from_stage.outs:
|
||||
for to_out in to_stage.outs:
|
||||
nodes.add(from_out.rel_path)
|
||||
nodes.add(to_out.rel_path)
|
||||
edges.append((from_out.rel_path,
|
||||
to_out.rel_path))
|
||||
else:
|
||||
nodes.add(from_stage.relpath)
|
||||
nodes.add(to_stage.relpath)
|
||||
edges.append((from_stage.relpath, to_stage.relpath))
|
||||
|
||||
d = Dagascii(nodes, edges)
|
||||
d.draw()
|
||||
|
||||
def run(self, unlock=False):
|
||||
for target in self.args.targets:
|
||||
try:
|
||||
self._show(target, self.args.commands, self.args.outs)
|
||||
if self.args.ascii:
|
||||
self._show_ascii(target,
|
||||
self.args.commands,
|
||||
self.args.outs)
|
||||
else:
|
||||
self._show(target,
|
||||
self.args.commands,
|
||||
self.args.outs)
|
||||
except DvcException as ex:
|
||||
msg = 'Failed to show pipeline for \'{}\''.format(target)
|
||||
self.project.logger.error(msg, ex)
|
||||
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
import math
|
||||
|
||||
from asciicanvas.asciicanvas import AsciiCanvas
|
||||
from asciicanvas.style import Style
|
||||
|
||||
from grandalf.graphs import Vertex, Edge, Graph
|
||||
from grandalf.layouts import SugiyamaLayout
|
||||
from grandalf.routing import route_with_lines, EdgeViewer
|
||||
|
||||
|
||||
class Dagascii(object):
|
||||
def __init__(self, vertexes, edges):
|
||||
#
|
||||
# Just a reminder about naming conventions:
|
||||
# +------------X
|
||||
# |
|
||||
# |
|
||||
# |
|
||||
# |
|
||||
# Y
|
||||
#
|
||||
|
||||
V = {v: Vertex(" {} ".format(v)) for v in vertexes}
|
||||
# NOTE: reverting edges to correctly orientate the graph
|
||||
E = [Edge(V[e], V[s]) for s, e in edges]
|
||||
V = V.values()
|
||||
g = Graph(V, E)
|
||||
|
||||
class VertexViewer(object):
|
||||
h = 3 # top and bottom box edges + text
|
||||
|
||||
def __init__(self, name):
|
||||
self.w = len(name) + 2 # right and left bottom edges + text
|
||||
|
||||
for v in V:
|
||||
v.view = VertexViewer(v.data)
|
||||
|
||||
# NOTE: determine min box length to create the best layout
|
||||
minw = min([v.view.w for v in V])
|
||||
|
||||
for e in E:
|
||||
e.view = EdgeViewer()
|
||||
|
||||
sug = SugiyamaLayout(g.C[0])
|
||||
gr = g.C[0]
|
||||
r = list(filter(lambda x: len(x.e_in()) == 0, gr.sV))
|
||||
|
||||
sug.init_all(roots=r, optimize=True)
|
||||
|
||||
sug.yspace = 2*VertexViewer.h
|
||||
sug.xspace = minw
|
||||
sug.route_edge = route_with_lines
|
||||
|
||||
sug.draw()
|
||||
|
||||
self.sug = sug
|
||||
|
||||
def draw(self):
|
||||
# NOTE: coordinates might me negative, so we need to shift
|
||||
# everything to the positive plane before we actually draw it.
|
||||
Xs = []
|
||||
Ys = []
|
||||
|
||||
for v in self.sug.g.sV:
|
||||
# NOTE: moving boxes w/2 to the left
|
||||
Xs.append(v.view.xy[0] - v.view.w/2.0)
|
||||
Xs.append(v.view.xy[0] + v.view.w/2.0)
|
||||
Ys.append(v.view.xy[1])
|
||||
Ys.append(v.view.xy[1] + v.view.h)
|
||||
|
||||
for e in self.sug.g.sE:
|
||||
for xy in e.view._pts:
|
||||
Xs.append(xy[0])
|
||||
Ys.append(xy[1])
|
||||
|
||||
minx = min(Xs)
|
||||
miny = min(Ys)
|
||||
maxx = max(Xs)
|
||||
maxy = max(Ys)
|
||||
|
||||
canvas_cols = int(math.ceil(math.ceil(maxx) - math.floor(minx))) + 1
|
||||
canvas_lines = int(round(maxy - miny))
|
||||
|
||||
canvas = AsciiCanvas(canvas_cols, canvas_lines)
|
||||
|
||||
# NOTE: first draw edges so that node boxes could overwrite them
|
||||
for e in self.sug.g.sE:
|
||||
assert len(e.view._pts) > 1
|
||||
for index in range(1, len(e.view._pts)):
|
||||
start = e.view._pts[index - 1]
|
||||
end = e.view._pts[index]
|
||||
|
||||
start_x = int(round(start[0] - minx))
|
||||
start_y = int(round(start[1] - miny))
|
||||
end_x = int(round(end[0] - minx))
|
||||
end_y = int(round(end[1] - miny))
|
||||
|
||||
assert start_x >= 0
|
||||
assert start_y >= 0
|
||||
assert end_x >= 0
|
||||
assert end_y >= 0
|
||||
|
||||
canvas.add_line(start_x, start_y,
|
||||
end_x, end_y,
|
||||
style=Style('*'))
|
||||
|
||||
for v in self.sug.g.sV:
|
||||
# NOTE: moving boxes w/2 to the left
|
||||
x = v.view.xy[0] - v.view.w/2.0
|
||||
y = v.view.xy[1]
|
||||
|
||||
canvas.add_nine_patch_rect(int(round(x - minx)),
|
||||
int(round(y - miny)),
|
||||
v.view.w,
|
||||
v.view.h)
|
||||
|
||||
canvas.add_text(int(round(x - minx)) + 1,
|
||||
int(round(y - miny)) + 1,
|
||||
v.data)
|
||||
|
||||
canvas.print_out()
|
||||
@@ -22,3 +22,5 @@ reflink==0.2.0
|
||||
requests>=2.18.4
|
||||
wheel>=0.31.1
|
||||
futures>=3.2.0; python_version == "2.7"
|
||||
grandalf==0.55555
|
||||
asciicanvas==0.0.3
|
||||
|
||||
Reference in New Issue
Block a user