From 58da64b190728c2a096d70d93defcb46f9d17bac Mon Sep 17 00:00:00 2001 From: lincer_x42 Date: Mon, 6 Mar 2023 13:14:06 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E4=BA=86?= =?UTF-8?q?=E4=B8=80=E4=B8=AA=E5=88=86=E6=9E=90task=E7=9A=84=E8=BD=AC?= =?UTF-8?q?=E7=A7=BB=E5=85=B3=E7=B3=BB=E7=9A=84py=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tools/TasksTransitionVisualizer/.gitignore | 3 + tools/TasksTransitionVisualizer/README.md | 13 ++ .../requirements.txt | 37 ++++++ .../tasks_transition_visualizer.py | 122 ++++++++++++++++++ 4 files changed, 175 insertions(+) create mode 100644 tools/TasksTransitionVisualizer/.gitignore create mode 100644 tools/TasksTransitionVisualizer/README.md create mode 100644 tools/TasksTransitionVisualizer/requirements.txt create mode 100644 tools/TasksTransitionVisualizer/tasks_transition_visualizer.py diff --git a/tools/TasksTransitionVisualizer/.gitignore b/tools/TasksTransitionVisualizer/.gitignore new file mode 100644 index 0000000000..00913b92a6 --- /dev/null +++ b/tools/TasksTransitionVisualizer/.gitignore @@ -0,0 +1,3 @@ + +*.pdf +testenv/ \ No newline at end of file diff --git a/tools/TasksTransitionVisualizer/README.md b/tools/TasksTransitionVisualizer/README.md new file mode 100644 index 0000000000..f7651e8a60 --- /dev/null +++ b/tools/TasksTransitionVisualizer/README.md @@ -0,0 +1,13 @@ +# 说明 + +一个简单脚本,读取tasks.json, 根据指定的前缀词筛选task,将获得的task按照next关系绘制成有向图。 + +Generate a transition graph for nodes whose name begin with a specific keyword + +optional arguments: + -h, --help show this help message and exit + --keyword KEYWORD the keyword to generate the graph for, case sensitive + --nodesize [400-800] the size of nodes in the graph + --arrowsize [20-50] the size of arrows in the graph + --fontsize [5-10] the size of font in the graph + --list if list all the names of nodes matched \ No newline at end of file diff --git a/tools/TasksTransitionVisualizer/requirements.txt b/tools/TasksTransitionVisualizer/requirements.txt new file mode 100644 index 0000000000..7f8dfa8520 --- /dev/null +++ b/tools/TasksTransitionVisualizer/requirements.txt @@ -0,0 +1,37 @@ +backcall==0.2.0 +colorama==0.4.4 +contourpy==1.0.7 +cycler==0.11.0 +decorator==5.0.9 +ffmpeg==1.4 +fonttools==4.38.0 +importlib-resources==5.12.0 +ipykernel==5.5.5 +ipython==7.24.1 +ipython-genutils==0.2.0 +jedi==0.18.0 +jupyter-client==6.1.12 +jupyter-core==4.7.1 +kiwisolver==1.4.4 +matplotlib==3.7.1 +matplotlib-inline==0.1.2 +networkx==3.0 +numpy==1.24.2 +packaging==23.0 +parso==0.8.2 +pickleshare==0.7.5 +Pillow==9.4.0 +prompt-toolkit==3.0.18 +pydot==1.4.2 +Pygments==2.9.0 +pyparsing==3.0.9 +python-dateutil==2.8.1 +pywin32==301 +pyzmq==22.1.0 +six==1.16.0 +tornado==6.1 +traitlets==5.0.5 +urllib3==1.25.11 +wcwidth==0.2.5 +you-get==0.4.1536 +zipp==3.15.0 diff --git a/tools/TasksTransitionVisualizer/tasks_transition_visualizer.py b/tools/TasksTransitionVisualizer/tasks_transition_visualizer.py new file mode 100644 index 0000000000..efeb15a9cf --- /dev/null +++ b/tools/TasksTransitionVisualizer/tasks_transition_visualizer.py @@ -0,0 +1,122 @@ +import os +import sys +import json +import logging +import warnings +import argparse +import matplotlib.pyplot as plt +import networkx as nx +import webbrowser + +# nx.nx_pydot.graphviz_layout reports DeprecationWarning +warnings.filterwarnings('ignore', category=DeprecationWarning) + +logging.basicConfig(format='%(message)s', level=logging.INFO) + +parser = argparse.ArgumentParser( + description='Generate a transition graph for nodes whose name begin with a specific keyword') +parser.add_argument('--keyword', type=str, default='Mizuki@Roguelike', + help='the keyword to generate the graph for, case sensitive') +parser.add_argument('--nodesize', type=int, default=500, choices=range(400, 801), metavar='[400-800]', + help='the size of nodes in the graph') +parser.add_argument('--arrowsize', type=int, default=30, choices=range(20, 51), metavar='[20-50]', + help='the size of arrows in the graph') +parser.add_argument('--fontsize', type=int, default=8, choices=range(5, 11), metavar='[5-10]', + help='the size of font in the graph') +parser.add_argument('--list', action='store_true', + help='if list all the names of nodes matched') +args = parser.parse_args() + +project_root_path = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), + os.pardir, os.pardir)) +task_json_path = os.path.join(project_root_path, 'resource', 'tasks.json') +try: + with open(task_json_path, 'r', encoding='utf-8') as f: + json_dict = json.load(f) +except Exception as e: + logging.error( + "Failed to read task json at {}: {}".format(task_json_path), e) + exit(-1) + +keyword = args.keyword +if keyword == "Mizuki@Roguelike": + logging.info("Default to generate graph for {}".format(keyword)) +else: + logging.info("Generating graph for %s", keyword) + + +def simplify_node(node_name): + # shorten node name for display in the graph + return node_name.replace(keyword+'@', '') + + +def recover_node(node_name): + # recover the node names, used when build graph + return keyword+'@'+node_name + +# recursively find the nodes +def search_node(node, s, visited, nodes): + # nodes without next field are not considered + if not (node in json_dict.keys() and 'next' in json_dict[node]): + return + if node in visited: + return + visited.add(node) + if node.startswith(s): + nodes.append(simplify_node(node)) + else: + return + for next_node in json_dict[node]['next']: + search_node(next_node, s, visited, nodes) + + +visited = set() +nodes = [] +for node in json_dict: + search_node(node, keyword, visited, nodes) + +if len(nodes) == 0: + logging.warning("Found 0 tasks starting with {}".format(keyword)) + exit(0) +else: + logging.info('Found {} tasks starting with {}'.format(len(nodes), keyword)) + if args.list: + logging.info('\n'.join(nodes)) +nodes.sort() + +# generate networkx.MultiDiGraph +G = {} +for node in nodes: + G[node] = json_dict[recover_node(node)]['next'] + for i in range(len(G[node])): + G[node][i] = simplify_node(G[node][i]) +G = nx.MultiDiGraph(G) +logging.info("Built graph model completed.") + +# export pdf +plt.figure(figsize=(60, 60)) +pos = nx.nx_pydot.graphviz_layout(G, prog='dot') +nx.draw_networkx_nodes(G, pos, node_size=args.nodesize, + alpha=0.8, node_color='lightblue') +nx.draw_networkx_edges(G, pos, alpha=1, arrows=True, arrowsize=args.arrowsize) +nx.draw_networkx_labels(G, pos, font_size=args.fontsize, + font_family='sans-serif') +plt.axis('off') +fig = plt.gcf() +relative_path = os.path.join( + "tools", "TasksTransitionVisualizer", "graph_{}.pdf".format(keyword)) +file_path = os.path.join(project_root_path, relative_path) + +try: + fig.savefig(file_path) + logging.info("Transition graph file for keyword {} saved to {}".format( + keyword, relative_path)) +except PermissionError: + logging.error( + "File {} is in use. Close and re-run the task.".format(file_path)) +except Exception as e: + logging.error( + "Cannot save graph file at {}. Error: {}".format(relative_path, e)) + + +webbrowser.open(file_path, new=2) From 3f62b06d1871674986e351a4df4ced3c62c4b7c8 Mon Sep 17 00:00:00 2001 From: lincer_x42 Date: Mon, 6 Mar 2023 22:29:08 +0800 Subject: [PATCH 2/2] Update README.md --- tools/TasksTransitionVisualizer/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/TasksTransitionVisualizer/README.md b/tools/TasksTransitionVisualizer/README.md index f7651e8a60..f719ea1a6c 100644 --- a/tools/TasksTransitionVisualizer/README.md +++ b/tools/TasksTransitionVisualizer/README.md @@ -4,10 +4,12 @@ Generate a transition graph for nodes whose name begin with a specific keyword +``` optional arguments: -h, --help show this help message and exit --keyword KEYWORD the keyword to generate the graph for, case sensitive --nodesize [400-800] the size of nodes in the graph --arrowsize [20-50] the size of arrows in the graph --fontsize [5-10] the size of font in the graph - --list if list all the names of nodes matched \ No newline at end of file + --list if list all the names of nodes matched +```