Browse Source

Fix and improve tagdag command

- Allows exporting a tag DAG for a specific publication
- Allows exporting the complete tag DAG. Before a root node had to be
  specified.
- Allows limiting tag DAG to specified number of relevant publications.
- Use record nodes.
master
Maximilian Blochberger 4 years ago
parent
commit
e04d5c889c
No known key found for this signature in database GPG Key ID: A2A8F3CC6591A5FA
  1. 5
      README.md
  2. 101
      sok/management/commands/tagdag.py

5
README.md

@ -85,7 +85,10 @@ Tags and citations can be exported in DOT format, which can be rendered using Gr
./manage.py citations --min-citations 10 > citations.dot && dot -Tsvg -ocitations.svg citations.dot ./manage.py citations --min-citations 10 > citations.dot && dot -Tsvg -ocitations.svg citations.dot
# Render tag DAG # Render tag DAG
./manage.py tagtag --root 'Literature on Systematic Mapping' > sysmap.dot && dot -Tsvg -osysmap.svg sysmap.dot ./manage.py tagtag --root 'Literature on Systematic Mapping' --threshold 1 > sysmap.dot && dot -Tsvg -osysmap.svg sysmap.dot
# Render classification for a single publication
./manage.py tagtag 'DBLP:conf/ease/PetersenFMM08' > petersen08.dot && dot -Tsvg -opetersen08.svg petersen08.dot
# Render with TIKZ # Render with TIKZ
pip install dot2tex pip install dot2tex

101
sok/management/commands/tagdag.py

@ -1,8 +1,10 @@
from typing import Set, Tuple import html
from typing import Optional, Set, Tuple
from django.core.management.base import BaseCommand, CommandParser from django.core.management.base import BaseCommand, CommandParser
from sok.models import Tag from sok.models import Publication, Tag
class Command(BaseCommand): class Command(BaseCommand):
@ -10,29 +12,98 @@ class Command(BaseCommand):
def echo(self, msg: str): def echo(self, msg: str):
self.stdout.write(msg) self.stdout.write(msg)
# BaseCommand def add_node(
self,
node: Tag,
publication: Optional[Publication] = None,
threshold: int = 0,
include_publications: bool = False,
):
publications = node.transitive_publications
num = len(publications)
def add_arguments(self, parser: CommandParser): if node.pk in self.nodes:
parser.add_argument('--root', default='CAPI Misuse') return # Already printed this node
if num < threshold:
return
if not (publication is None or publication in publications):
return
name = html.escape(node.name)
self.echo(f"\tT{node.pk} [")
if include_publications:
pubs = ','.join([str(t.pk) for t in publications])
self.echo(f'\t\tlabel="{name}|{{{num}|{pubs}}}",')
else:
self.echo(f'\t\tlabel="{name}|{num}",')
if 0 == num:
self.echo("\t\tcolor=red,")
self.echo("\t];")
def _graphviz(self, root: Tag) -> None: self.nodes.add(node.pk)
for tag in root.implied_by.all(): for predecessor in node.implied_by.all():
edge = (tag.pk, root.pk) self.add_node(predecessor, publication, threshold, include_publications)
def add_edge(self, node: Tag):
for predecessor in node.implied_by.all():
if predecessor.pk not in self.nodes:
continue
edge = (predecessor.pk, node.pk)
if edge in self.graph: if edge in self.graph:
continue continue
if edge[::-1] in self.graph: if edge[::-1] in self.graph:
self.stderr.write(self.style.ERROR(f"CYCLE: '{root}' <-> '{tag}'")) self.stderr.write(self.style.ERROR(f"CYCLE: '{node}' <-> '{predecessor}'"))
self.graph.add(edge) self.graph.add(edge)
self._graphviz(tag) self.echo(f'\t"T{predecessor.pk}" -> "T{node.pk}";')
self.echo(f'\t"{tag}" -> "{root}";') self.add_edge(predecessor)
def graphviz(self, root: Tag) -> None: def graphviz(
self,
root: Optional[Tag] = None,
publication: Optional[Publication] = None,
threshold: int = 0,
include_publications: bool = False,
):
self.echo("digraph G {") self.echo("digraph G {")
self.echo("\trankdir = RL;") self.echo("\trankdir = RL;")
self._graphviz(root) self.echo("\tnode [shape=record];")
# Add nodes
if root is None:
for tag in Tag.objects.filter(implies__isnull=True):
self.add_node(tag, publication, threshold, include_publications)
else:
self.add_node(root, publication, threshold, include_publications)
# Add edges
if root is None:
for tag in Tag.objects.filter(implies__isnull=True):
self.add_edge(tag)
else:
self.add_edge(root)
self.echo("}") self.echo("}")
# BaseCommand
def add_arguments(self, parser: CommandParser):
parser.add_argument('--root', default=None)
parser.add_argument('--include-publications', action='store_true')
parser.add_argument('--threshold', type=int, default=0)
parser.add_argument('publication', nargs='?')
def handle(self, *args, **options) -> None: def handle(self, *args, **options) -> None:
include_publications: bool = options['include_publications']
threshold: int = options['threshold']
root: Optional[Tag] = None
if tag_name := options.get('root', None):
root = Tag.objects.get(name=tag_name)
publication: Optional[Publication] = None
if cite_key := options.get('publication', None):
publication = Publication.objects.get(cite_key=cite_key)
self.graph: Set[Tuple[int, int]] = set() self.graph: Set[Tuple[int, int]] = set()
root = Tag.objects.get(name=options['root']) self.nodes: Set[int] = set()
self.graphviz(root) self.graphviz(root, publication, threshold, include_publications)

Loading…
Cancel
Save