diff --git a/mypy.ini b/mypy.ini
--- a/mypy.ini
+++ b/mypy.ini
@@ -13,3 +13,15 @@
[mypy-ndjson.*]
ignore_missing_imports = True
+
+[mypy-dash.*]
+ignore_missing_imports = True
+
+[mypy-dash_core_components.*]
+ignore_missing_imports = True
+
+[mypy-dash_html_components.*]
+ignore_missing_imports = True
+
+[mypy-plotly.*]
+ignore_missing_imports = True
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
--- a/requirements.txt
+++ b/requirements.txt
@@ -8,4 +8,5 @@
plotly
pandas
numpy
+dash
dulwich
diff --git a/swh/scanner/cli.py b/swh/scanner/cli.py
--- a/swh/scanner/cli.py
+++ b/swh/scanner/cli.py
@@ -13,6 +13,8 @@
from .scanner import run
from .model import Tree
+from .plot import generate_sunburst
+from .dashboard import run_app
from .exceptions import InvalidDirectoryPath
from swh.core.cli import CONTEXT_SETTINGS
@@ -80,8 +82,11 @@
default="text",
help="select the output format",
)
+@click.option(
+ "-i", "--interactive", is_flag=True, help="show the result in a dashboard"
+)
@click.pass_context
-def scan(ctx, root_path, api_url, patterns, format):
+def scan(ctx, root_path, api_url, patterns, format, interactive):
"""Scan a source code project to discover files and directories already
present in the archive"""
sre_patterns = set()
@@ -95,7 +100,13 @@
loop = asyncio.get_event_loop()
loop.run_until_complete(run(root_path, api_url, source_tree, sre_patterns))
- source_tree.show(format)
+ if interactive:
+ root = PosixPath(root_path)
+ directories = source_tree.getDirectoriesInfo(root)
+ figure = generate_sunburst(directories, root)
+ run_app(figure, source_tree)
+ else:
+ source_tree.show(format)
if __name__ == "__main__":
diff --git a/swh/scanner/dashboard.py b/swh/scanner/dashboard.py
new file mode 100644
--- /dev/null
+++ b/swh/scanner/dashboard.py
@@ -0,0 +1,24 @@
+# Copyright (C) 2020 The Software Heritage developers
+# See the AUTHORS file at the top-level directory of this distribution
+# License: GNU General Public License version 3, or any later version
+# See top-level LICENSE file for more information
+
+from .model import Tree
+
+import plotly.graph_objects as go
+import dash
+import dash_core_components as dcc
+import dash_html_components as html
+
+
+def run_app(graph_obj: go, source: Tree):
+ app = dash.Dash(__name__)
+ fig = go.Figure().add_trace(graph_obj)
+
+ fig.update_layout(height=800,)
+
+ app.layout = html.Div(
+ [html.Div([html.Div([dcc.Graph(id="sunburst_chart", figure=fig),]),]),]
+ )
+
+ app.run_server(debug=True, use_reloader=False)
diff --git a/swh/scanner/model.py b/swh/scanner/model.py
--- a/swh/scanner/model.py
+++ b/swh/scanner/model.py
@@ -12,7 +12,7 @@
import ndjson
-from .plot import sunburst
+from .plot import generate_sunburst, offline_plot
from .exceptions import InvalidObjectType
from swh.model.identifiers import DIRECTORY, CONTENT
@@ -73,7 +73,8 @@
elif format == "sunburst":
root = self.path
directories = self.getDirectoriesInfo(root)
- sunburst(directories, root)
+ sunburst = generate_sunburst(directories, root)
+ offline_plot(sunburst)
def printChildren(self, isatty: bool, inc: int = 1) -> None:
for path, node in self.children.items():
diff --git a/swh/scanner/plot.py b/swh/scanner/plot.py
--- a/swh/scanner/plot.py
+++ b/swh/scanner/plot.py
@@ -18,8 +18,8 @@
from typing import List, Dict, Tuple
from pathlib import PosixPath
-from plotly.offline import offline # type: ignore
-import plotly.graph_objects as go # type: ignore
+from plotly.offline import offline
+import plotly.graph_objects as go
import pandas as pd # type: ignore
import numpy as np # type: ignore
@@ -236,8 +236,10 @@
return df
-def sunburst(directories: Dict[PosixPath, Tuple[int, int]], root: PosixPath) -> None:
- """Show the sunburst chart from the directories given in input.
+def generate_sunburst(
+ directories: Dict[PosixPath, Tuple[int, int]], root: PosixPath
+) -> go.Sunburst:
+ """Generate a sunburst chart from the directories given in input.
"""
max_depth = compute_max_depth(list(directories.keys()), root)
@@ -251,24 +253,29 @@
dirs_df, levels_columns, metrics_columns, str(root)
)
- fig = go.Figure()
- fig.add_trace(
- go.Sunburst(
- labels=hierarchical_df["id"],
- parents=hierarchical_df["parent"],
- values=hierarchical_df["contents"],
- branchvalues="total",
- marker=dict(
- colors=hierarchical_df["known"],
- colorscale="matter",
- cmid=50,
- showscale=True,
- ),
- hovertemplate="""%{label}
-
Files: %{value}
-
Known: %{color:.2f}%""",
- name="",
- )
+ sunburst = go.Sunburst(
+ labels=hierarchical_df["id"],
+ parents=hierarchical_df["parent"],
+ values=hierarchical_df["contents"],
+ branchvalues="total",
+ marker=dict(
+ colors=hierarchical_df["known"],
+ colorscale="matter",
+ cmid=50,
+ showscale=True,
+ ),
+ hovertemplate="""%{label}
+
Files: %{value}
+
Known: %{color:.2f}%""",
+ name="",
)
- offline.plot(fig, filename="sunburst.html")
+ return sunburst
+
+
+def offline_plot(graph_object: go):
+ """Plot a graph object to an html file
+ """
+ fig = go.Figure()
+ fig.add_trace(graph_object)
+ offline.plot(fig, filename="chart.html")