_graphviz.py 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. import typing
  2. import graphviz
  3. from yamily import Person, PersonCollection
  4. def _add_person_node(graph: graphviz.dot.Digraph, person: Person) -> None:
  5. label = person.name or person.identifier
  6. graph.node(person.identifier, label=label, shape="box")
  7. def digraph(collection: PersonCollection) -> graphviz.dot.Digraph:
  8. """
  9. >>> bob = Person('bob')
  10. >>> bob.father = Person('frank')
  11. >>> carol = Person('carol')
  12. >>> carol.mother = Person('grace')
  13. >>> alice = Person('alice')
  14. >>> alice.mother = carol
  15. >>> alice.father = bob
  16. >>> david = Person('david')
  17. >>> david.mother = carol
  18. >>> david.father = bob
  19. >>> collection = PersonCollection()
  20. >>> collection.add_person(alice)
  21. Person(alice)
  22. >>> collection.add_person(david)
  23. Person(david)
  24. >>> graph = digraph(collection)
  25. >>> print(graph.source)
  26. digraph yamily {
  27. {
  28. rank=same
  29. grace [label=grace shape=box]
  30. }
  31. {
  32. rank=same
  33. carol [label=carol shape=box]
  34. bob [label=bob shape=box]
  35. "relation-bob-carol" [shape=point width=0]
  36. carol -> "relation-bob-carol" [arrowhead=none constraint=False]
  37. bob -> "relation-bob-carol" [arrowhead=none constraint=False]
  38. }
  39. {
  40. rank=same
  41. frank [label=frank shape=box]
  42. }
  43. {
  44. rank=same
  45. alice [label=alice shape=box]
  46. }
  47. {
  48. rank=same
  49. david [label=david shape=box]
  50. }
  51. grace -> carol
  52. frank -> bob
  53. "relation-bob-carol" -> alice
  54. "relation-bob-carol" -> david
  55. }
  56. >>> graph.render('/tmp/yamily.gv')
  57. '/tmp/yamily.gv.pdf'
  58. """
  59. graph = graphviz.Digraph("yamily")
  60. nodes: typing.Set[Person] = set()
  61. parent_node_names: typing.Dict[typing.Tuple[Person, Person], str] = dict()
  62. for person in collection:
  63. if person in nodes:
  64. continue
  65. with graph.subgraph() as subgraph:
  66. subgraph.attr(rank="same")
  67. _add_person_node(subgraph, person)
  68. nodes.add(person)
  69. for coparent in collection.get_coparents(person):
  70. _add_person_node(subgraph, coparent)
  71. nodes.add(coparent)
  72. parents = tuple(sorted((person, coparent), key=lambda p: p.identifier))
  73. parent_node_name = "relation-{}-{}".format(
  74. parents[0].identifier, parents[1].identifier
  75. )
  76. subgraph.node(parent_node_name, shape="point", width="0")
  77. subgraph.edge(
  78. person.identifier,
  79. parent_node_name,
  80. constraint="False",
  81. arrowhead="none",
  82. )
  83. subgraph.edge(
  84. coparent.identifier,
  85. parent_node_name,
  86. constraint="False",
  87. arrowhead="none",
  88. )
  89. parent_node_names[parents] = parent_node_name
  90. parent_node_names[tuple(parents[::-1])] = parent_node_name
  91. for child in collection:
  92. if child.mother is not None and child.father is not None:
  93. parents = sorted(child.parents, key=lambda p: p.identifier)
  94. parent_node_name = parent_node_names[tuple(parents)]
  95. graph.edge(parent_node_name, child.identifier)
  96. elif child.mother is not None:
  97. graph.edge(child.mother.identifier, child.identifier)
  98. elif child.father is not None:
  99. graph.edge(child.father.identifier, child.identifier)
  100. return graph