_graphviz.py 3.6 KB

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