_graphviz.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. # yamify - define family trees in YAML
  2. #
  3. # Copyright (C) 2020 Fabian Peter Hammerle <fabian@hammerle.me>
  4. #
  5. # This program is free software: you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation, either version 3 of the License, or
  8. # (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. import typing
  18. import graphviz
  19. from yamily import Person, PersonCollection
  20. def _add_person_node(graph: graphviz.dot.Digraph, person: Person) -> None:
  21. label = person.name or person.identifier
  22. if person.birth_date is not None:
  23. label += r"\n*{}".format(person.birth_date.isoformat())
  24. graph.node(person.identifier, label=label, shape="box")
  25. def digraph(collection: PersonCollection) -> graphviz.dot.Digraph:
  26. """
  27. >>> bob = Person('bob')
  28. >>> bob.father = Person('frank')
  29. >>> carol = Person('carol')
  30. >>> carol.mother = Person('grace')
  31. >>> alice = Person('alice')
  32. >>> alice.mother = carol
  33. >>> alice.father = bob
  34. >>> david = Person('david')
  35. >>> david.mother = carol
  36. >>> david.father = bob
  37. >>> collection = PersonCollection()
  38. >>> collection.add_person(alice)
  39. Person(alice)
  40. >>> collection.add_person(david)
  41. Person(david)
  42. >>> graph = digraph(collection)
  43. >>> print(graph.source)
  44. digraph yamily {
  45. {
  46. rank=same
  47. grace [label=grace shape=box]
  48. }
  49. {
  50. rank=same
  51. carol [label=carol shape=box]
  52. bob [label=bob shape=box]
  53. "relation-bob-carol" [shape=point width=0]
  54. carol -> "relation-bob-carol" [arrowhead=none constraint=False]
  55. bob -> "relation-bob-carol" [arrowhead=none constraint=False]
  56. }
  57. {
  58. rank=same
  59. frank [label=frank shape=box]
  60. }
  61. {
  62. rank=same
  63. alice [label=alice shape=box]
  64. }
  65. {
  66. rank=same
  67. david [label=david shape=box]
  68. }
  69. grace -> carol
  70. frank -> bob
  71. "relation-bob-carol" -> alice
  72. "relation-bob-carol" -> david
  73. }
  74. >>> graph.render('/tmp/yamily.gv')
  75. '/tmp/yamily.gv.pdf'
  76. """
  77. graph = graphviz.Digraph("yamily")
  78. nodes: typing.Set[Person] = set()
  79. parent_node_names: typing.Dict[typing.Tuple[Person, Person], str] = dict()
  80. for person in collection:
  81. if person in nodes:
  82. continue
  83. with graph.subgraph() as subgraph:
  84. subgraph.attr(rank="same")
  85. _add_person_node(subgraph, person)
  86. nodes.add(person)
  87. for coparent in collection.get_coparents(person):
  88. _add_person_node(subgraph, coparent)
  89. nodes.add(coparent)
  90. parents = tuple(sorted((person, coparent), key=lambda p: p.identifier))
  91. parent_node_name = "relation-{}-{}".format(
  92. parents[0].identifier, parents[1].identifier
  93. )
  94. subgraph.node(parent_node_name, shape="point", width="0")
  95. subgraph.edge(
  96. person.identifier,
  97. parent_node_name,
  98. constraint="False",
  99. arrowhead="none",
  100. )
  101. subgraph.edge(
  102. coparent.identifier,
  103. parent_node_name,
  104. constraint="False",
  105. arrowhead="none",
  106. )
  107. parent_node_names[parents] = parent_node_name
  108. parent_node_names[tuple(parents[::-1])] = parent_node_name
  109. for child in collection:
  110. if child.mother is not None and child.father is not None:
  111. parents = sorted(child.parents, key=lambda p: p.identifier)
  112. parent_node_name = parent_node_names[tuple(parents)]
  113. graph.edge(parent_node_name, child.identifier)
  114. elif child.mother is not None:
  115. graph.edge(child.mother.identifier, child.identifier)
  116. elif child.father is not None:
  117. graph.edge(child.father.identifier, child.identifier)
  118. return graph