Skip to content

Commit

Permalink
First pass at graphing event ancestry.
Browse files Browse the repository at this point in the history
  • Loading branch information
bicobus committed Feb 5, 2024
1 parent 573909f commit 72a6d05
Show file tree
Hide file tree
Showing 7 changed files with 751 additions and 350 deletions.
824 changes: 589 additions & 235 deletions poetry.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ graphviz = "^0.20.1"
xmlschema = "^2.2"
attrs = "^23.1"
xsdata-attrs = {extras = ["cli"], version = "^23.8"}
lxml = "^4.9"
pygments = "^2.15"
wxpython = "^4.2"
pillow = "^10"
platformdirs = "^3.10.0"
matplotlib = "^3.8.2"
netgraph = "^4.13.2"


[tool.poetry.group.dev.dependencies]
Expand Down
71 changes: 71 additions & 0 deletions src/pycestorieseditor/ancestrygraph.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# -*- coding: utf-8 -*-
# Licensed under the EUPL v1.2
# © 2024 bicobus <[email protected]>
from itertools import chain
from textwrap import wrap

import matplotlib.pyplot as plt
import wx
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.backends.backend_wxagg import NavigationToolbar2WxAgg as NavigationToolbar2Wx
from netgraph import Graph

from pycestorieseditor.ceevents import ancestry_instance
from pycestorieseditor.ceevents_template import Ceevent


def get_color(node, core):
if node in core.parents_names():
return "tab:red"
if node in core.children_names():
return "tab:blue"
return "tab:green"


def nwrap(txt):
if len(txt) > 20:
return "\n".join(wrap(txt, 16))
return txt


def graph_event(panel, ceevent: Ceevent):
figure = plt.figure()
canvas = FigureCanvas(panel, -1, figure)
toolbar = NavigationToolbar2Wx(canvas)
toolbar.Realize()

core_node = ancestry_instance.get(ceevent.name)
nodes = [core_node.name] + list(set(core_node.parents_names() + core_node.children_names()))
colors = {i: get_color(node, core_node) for i, node in enumerate(nodes)}

edges = [
n
for n in chain(
[(nodes.index(core_node.name), nodes.index(x)) for x in core_node.children_names()],
[(nodes.index(x), nodes.index(core_node.name)) for x in core_node.parents_names()],
)
]
labels = {i: nwrap(lbl) for i, lbl in enumerate(nodes)}

G = Graph(
edges,
nodes=range(len(nodes)),
node_layout="radial",
node_labels=labels,
node_label_offset=0.1,
node_label_fontdict={'size': 10},
node_color=colors,
arrows=True,
edge_layout="curved",
)

# G = nx.DiGraph()
# G.add_nodes_from(range(len(nodes)))
# G.add_edges_from(edges)
# pos = nx.nx_pydot.pydot_layout(G, prog="dot")
# nx.draw(G, pos=pos, node_color=colors, labels=labels, node_size=800, node_shape="D")

sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(canvas, 1, wx.LEFT | wx.TOP | wx.GROW)
sizer.Add(toolbar, 0, wx.EXPAND)
panel.SetSizer(sizer)
59 changes: 58 additions & 1 deletion src/pycestorieseditor/ceevents.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,63 @@ def len(self):
return self._len


class AncestryNode:
def __init__(self, ceeventref):
self.name = ceeventref.name
self._ceevent = ceeventref
self._parents = []
self._children = []

def set_parent(self, node: AncestryNode):
self._parents.append(node)

def set_child(self, node: AncestryNode):
if node in self._children:
return
self._children.append(node)
node.set_parent(self)

def is_graphable(self):
return len(self._children) + len(self._parents) > 0

@property
def children(self):
return self._children

@property
def parents(self):
return self._parents

@lru_cache
def children_names(self):
return [n.name for n in self._children]

@lru_cache
def parents_names(self):
return [n.name for n in self._parents]


class Ancestry:
def __init__(self):
self._nodes: dict[str, AncestryNode] = {}

def register(self, node: AncestryNode):
"""Register a Ceevent object, does nothing if it already exists"""
if node.name not in self._nodes:
self._nodes[node.name] = node

def get(self, name: str):
return self._nodes[name]

def set_child_node(self, name: str, node: AncestryNode):
self._nodes[name].set_child(node)

def set_child_node_from_name(self, name_parent: str, name_child: str):
self._nodes[name_parent].set_child(self._nodes[name_child])


event_ancestry_errors = EventAncestryErrors()
ancestry_instance = Ancestry()


def find_by_name(name: str):
Expand Down Expand Up @@ -161,7 +217,7 @@ def populate_children():
bucket = get_ebucket()
for cevent in bucket.values():
for child in _outboundevents(cevent, errors):
cevent.set_child_node(child)
ancestry_instance.set_child_node_from_name(cevent.name, child.name)
return next(errors)


Expand Down Expand Up @@ -350,6 +406,7 @@ def process_module(xmlfiles: list, cb=None):
ceevent.xmlfile,
)
ebucket[ceevent.name] = ceevent
ancestry_instance.register(AncestryNode(ceevent))
if cb:
cb("Munching skills...")
for skill, eventname in skills:
Expand Down
12 changes: 0 additions & 12 deletions src/pycestorieseditor/ceevents_template/ceevents_modal.py
Original file line number Diff line number Diff line change
Expand Up @@ -4083,8 +4083,6 @@ class Meta:
validator=validators.optional(validators.instance_of(str)),
metadata={"type": XmlType.IGNORE},
)
_parents: list = field(default=[], alias="__parents", metadata={"type": XmlType.IGNORE})
_children: list = field(default=[], alias="__children", metadata={"type": XmlType.IGNORE})

@property
def outboundevents(self) -> list[str]:
Expand Down Expand Up @@ -4119,16 +4117,6 @@ def get_color(self):
except KeyError:
continue

def set_parent_node(self, parent: Ceevent):
self._parents.append(parent)

def set_child_node(self, child: Ceevent):
if child in self._children:
# logger.info("Event '%s' already set as child for %s", child.name.value, self.name.value)
return
self._children.append(child)
child.set_parent_node(self)


@define
class Ceevents:
Expand Down
96 changes: 0 additions & 96 deletions src/pycestorieseditor/graph.py

This file was deleted.

36 changes: 31 additions & 5 deletions src/pycestorieseditor/wxui.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
populate_children,
event_ancestry_errors,
find_by_name,
ancestry_instance,
)
from pycestorieseditor.ceevents_template import (
RestrictedListOfFlagsType,
Expand All @@ -54,6 +55,7 @@
MenuOption,
MenuOptions,
)
from pycestorieseditor.ancestrygraph import graph_event
from pycestorieseditor.pil2wx import (
hex2rgb,
wxicon,
Expand Down Expand Up @@ -1180,9 +1182,15 @@ def __init__(self, parent, options: MenuOptions):
self.SetSizerAndFit(sizer)


class DwTabAncestry(wx.Panel):
def __init__(self, parent, ceevent: Ceevent):
super().__init__(parent)
graph_event(self, ceevent)


class DetailWindow(wx.Frame):
def __init__(self, parent, ceevent: Ceevent, *args, **kwargs):
title = ceevent.name
title = f"Event details: {ceevent.name}"
kwargs['style'] = kwargs.get("style", 0) | wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
super().__init__(parent, title=title, *args, **kwargs)
self.SetMinSize((800, 600))
Expand All @@ -1204,6 +1212,12 @@ def __init__(self, parent, ceevent: Ceevent, *args, **kwargs):
nb.AddPage(taboptions, "Options")
if tabmoptions:
nb.AddPage(tabmoptions, "Menu Options")
ancestry = ancestry_instance.get(ceevent.name)
if ancestry.is_graphable():
tabancestry = DwTabAncestry(nb, ceevent)
nb.AddPage(tabancestry, "Ancestry Graph")
else:
print(len(ancestry.children), len(ancestry.parents))

sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(nb, 1, wx.EXPAND | wx.ALL)
Expand Down Expand Up @@ -1304,14 +1318,20 @@ def build_widgets(self, ceevent):
for opt in ceevent.options.option:
if not opt.trigger_event_name and not opt.trigger_events:
btn = wx.Button(
self, label=filter_text(opt.option_text, True), style=wx.BORDER_STATIC, size=buttonsize
self,
label=filter_text(opt.option_text, True),
style=wx.BORDER_STATIC,
size=buttonsize,
)
btn.SetBackgroundColour("#FF6666")
btn.Bind(wx.EVT_BUTTON, lambda evt: self.GetParent().Close(), btn)
sizer.Add(btn, 0, wx.ALL, 5)
elif not opt.trigger_events:
btn = wx.Button(
self, label=filter_text(opt.option_text, False), style=wx.BORDER_STATIC, size=buttonsize
self,
label=filter_text(opt.option_text, False),
style=wx.BORDER_STATIC,
size=buttonsize,
)
btn.event_name = opt.trigger_event_name
self.Bind(wx.EVT_BUTTON, self.on_button_clicked, btn)
Expand All @@ -1321,7 +1341,10 @@ def build_widgets(self, ceevent):
ssizer = wx.BoxSizer(wx.VERTICAL)

btn = wx.Button(
self, label=filter_text(opt.option_text, False), style=wx.BORDER_STATIC, size=buttonsize
self,
label=filter_text(opt.option_text, False),
style=wx.BORDER_STATIC,
size=buttonsize,
)
btn.event_name = event.event_name
btn.Bind(wx.EVT_BUTTON, self.on_button_clicked, btn)
Expand All @@ -1343,7 +1366,10 @@ def on_button_clicked(self, evt):
ceevent = find_by_name(wdg.event_name)
if not ceevent:
dialog = wx.MessageDialog(
self, f"Event {wdg.event_name} does not exists.", "Warning: event missing", style=wx.OK | wx.CENTER | wx.ICON_WARNING
self,
f"Event {wdg.event_name} does not exists.",
"Warning: event missing",
style=wx.OK | wx.CENTER | wx.ICON_WARNING,
)
dialog.ShowModal()
return False
Expand Down

0 comments on commit 72a6d05

Please sign in to comment.