Compare commits
1 Commits
main
...
dev/blocki
| Author | SHA1 | Date | |
|---|---|---|---|
| 83283c0657 |
@ -1,14 +1,10 @@
|
||||
FROM alpine
|
||||
RUN apk add --no-cache \
|
||||
yq \
|
||||
python3 \
|
||||
py3-pip \
|
||||
py3-yaml \
|
||||
graphviz
|
||||
|
||||
COPY run.sh /run.sh
|
||||
RUN chmod +x /run.sh
|
||||
|
||||
RUN pip3 install graphviz --break-system-packages # Fix?
|
||||
|
||||
CMD ["/run.sh"]
|
||||
CMD ["./run.sh"]
|
||||
|
||||
31
README.md
31
README.md
@ -1,31 +0,0 @@
|
||||
|
||||
# Puudot
|
||||
|
||||
## Development
|
||||
|
||||
Start implementing graph.py with Graphviz Python library.
|
||||
First created in new_graph.py, but will replace graph.py.
|
||||
|
||||
### Graph
|
||||
|
||||
Based on current cluster architecture.
|
||||
|
||||
### RecordGraph
|
||||
|
||||
New architecture for creating dot. No clusters; one node (record) contains one row table with each column for a person.
|
||||
~~~
|
||||
dot.node('<this node id>', fr'{(<node id>) <text>}|...')
|
||||
dot.edge('<this node id>:<other node id>', '<other node id>')
|
||||
~~~
|
||||
|
||||
algorithm:
|
||||
~~~
|
||||
for block in blocks
|
||||
create node
|
||||
if block has link, set link as id
|
||||
else create id
|
||||
add texts to node
|
||||
for text in block save links
|
||||
|
||||
create edges from links
|
||||
~~~
|
||||
@ -8,6 +8,8 @@ import uuid
|
||||
# - also see: https://stackoverflow.com/questions/53862417/how-to-set-head-and-tail-position-in-nodes-graphviz
|
||||
# Solve layering of clusters
|
||||
# - https://observablehq.com/@gordonsmith/church
|
||||
# https://candide-guevara.github.io/cs_related/2019/09/10/graphviz-examples.html
|
||||
# https://forum.graphviz.org/t/set-nodes-from-left-to-right-and-other-from-top-to-bottom-on-the-same-rank/1860
|
||||
|
||||
|
||||
|
||||
@ -40,9 +42,21 @@ def make_link_line(link, config):
|
||||
return line + "\n"
|
||||
|
||||
def make_block_line(block, config):
|
||||
line = f"subgraph cluster_{block['id']} {{\n{config["subgraph"]}\n"
|
||||
for node in block["texts"]:
|
||||
configs = []
|
||||
if block['hidden']:
|
||||
configs.append(config["subgraph"]["hidden"])
|
||||
else:
|
||||
configs.append(config["subgraph"]["block"])
|
||||
|
||||
config_line = "\n".join(configs)
|
||||
|
||||
line = f"subgraph cluster_{block['id']} {{\n{config_line}\nlabel=\"{block['label']}\"\n"
|
||||
for node in block["nodes"]:
|
||||
line += f"{node}\n"
|
||||
#if block['hidden']:
|
||||
#line += "{rank=same\nedge [constraint=false]\n"
|
||||
#line += f"{block['nodes'][0]} -- {block['nodes'][1]}\n"
|
||||
#line += "}"
|
||||
return line + "}\n\n"
|
||||
|
||||
|
||||
@ -76,7 +90,6 @@ class Graph:
|
||||
def __init__(self):
|
||||
self.config = {}
|
||||
|
||||
self.layers = {}
|
||||
self.blocks = []
|
||||
self.nodes = []
|
||||
self.links = []
|
||||
@ -89,12 +102,15 @@ class Graph:
|
||||
dot = config.get("dot", {})
|
||||
if dot != {}:
|
||||
dot["graph"] = dot.get("graph", "")
|
||||
dot["subgraph"] = dot.get("subgraph", "")
|
||||
dot["subgraph"] = dot.get("subgraph", {})
|
||||
dot["subgraph"]["block"] = dot["subgraph"].get("block", "")
|
||||
dot["subgraph"]["hidden"] = dot["subgraph"].get("hidden", "")
|
||||
dot["node"] = dot.get("node", {})
|
||||
dot["node"]["text"] = dot["node"].get("text", "")
|
||||
dot["node"]["hidden"] = dot["node"].get("hidden", "")
|
||||
dot["edge"] = dot.get("edge", {})
|
||||
dot["edge"]["default"] = dot["edge"].get("default", "")
|
||||
dot["edge"]["middle"] = dot["edge"].get("middle", "")
|
||||
dot["edge"]["hidden"] = dot["edge"].get("hidden", "")
|
||||
self.config = dot
|
||||
|
||||
@ -108,10 +124,10 @@ class Graph:
|
||||
new_block = {
|
||||
"id": block_id,
|
||||
"label": block.get("label", ""),
|
||||
"texts": []
|
||||
"nodes": [],
|
||||
"hidden": False
|
||||
}
|
||||
|
||||
#prev_node_id = ""
|
||||
texts_in_block = block.get("texts", [])
|
||||
for i, text in enumerate(texts_in_block):
|
||||
node_id = get_id()
|
||||
@ -120,22 +136,28 @@ class Graph:
|
||||
if i == math.ceil(len(texts_in_block) / 2) - 1:
|
||||
for link in block.get("links", []):
|
||||
if link in linker:
|
||||
"""link_node1 = get_id()
|
||||
link_node2 = get_id()
|
||||
|
||||
self.nodes.append({"id": link_node1, "text": "", "hidden": True})
|
||||
self.nodes.append({"id": link_node2, "text": "", "hidden": True})
|
||||
|
||||
link_block_id = get_id()
|
||||
self.blocks.append({"id": link_block_id, "label": "", "nodes": [link_node1, link_node2], "hidden": True})
|
||||
|
||||
self.links.append({"from": linker[link], "to": link_node1, "head": "", "hidden": False})
|
||||
#self.links.append({"from": link_node1, "to": link_node2, "head": "", "hidden": False})
|
||||
self.links.append({"from": link_node2, "to": node_id, "head": block_id, "hidden": False})"""
|
||||
self.links.append({"from": linker[link], "to": node_id, "head": block_id, "hidden": False})
|
||||
del linker[link]
|
||||
|
||||
self.nodes.append({"id": node_id, "text": text.get("text", ""), "hidden": False})
|
||||
|
||||
# Chain nodes in block
|
||||
#if prev_node_id:
|
||||
# self.links.append({"from": prev_node_id, "to": node_id, "head": "", "hidden": True})
|
||||
|
||||
new_block["texts"].append(node_id)
|
||||
new_block["nodes"].append(node_id)
|
||||
|
||||
for link in text.get("links", []):
|
||||
linker[link] = node_id
|
||||
|
||||
#prev_node_id = node_id
|
||||
|
||||
self.blocks.append(new_block)
|
||||
|
||||
def build_dot(self):
|
||||
@ -145,12 +167,12 @@ class Graph:
|
||||
self.dot_file += add_links(self.links, self.config)
|
||||
self.dot_file += make_footer()
|
||||
|
||||
def make_dot(self, dot_file, out_file, format):
|
||||
def make_dot(self, format="svg", dot_file="dot.gv", svg_file="graph.svg"):
|
||||
if self.dot_file != "":
|
||||
with open(dot_file, "w") as f:
|
||||
f.write(self.dot_file)
|
||||
if format:
|
||||
os.system(f"dot -T{format} {dot_file} -o {out_file}")
|
||||
if format == "svg":
|
||||
os.system(f"dot -T{format} {dot_file} -o {svg_file}")
|
||||
|
||||
|
||||
|
||||
@ -1,54 +0,0 @@
|
||||
import graphviz
|
||||
|
||||
# One cluster of nodes per block
|
||||
class Graph():
|
||||
def __init__(self):
|
||||
self.dot = None
|
||||
|
||||
|
||||
|
||||
# One node per block
|
||||
class RecordGraph:
|
||||
def __init__(self):
|
||||
self.dot = None
|
||||
self.edges = []
|
||||
self.node_counter = 0
|
||||
|
||||
def create_graph(self, config, data):
|
||||
# TODO: process config, use for graph init
|
||||
self.dot = graphviz.Graph('testgraph', graph_attr={'center': 'true', 'compound': 'true'}, node_attr={'shape': 'record'})
|
||||
self.dot.format = 'svg' # TODO: move to export
|
||||
|
||||
blocks = data.get('blocks', [])
|
||||
for block in blocks:
|
||||
self.node_counter += 1
|
||||
|
||||
# Create node id
|
||||
node_id = f'nr{self.node_counter}'
|
||||
links = block.get('links', [])
|
||||
if len(links) > 0:
|
||||
node_id = f'n{links[0]}'
|
||||
|
||||
# Create text table for node
|
||||
texts = []
|
||||
for text in block.get('texts', []):
|
||||
person = text.get('text', '').replace('\n', '\\n')
|
||||
link = text.get('links', [])
|
||||
if len(link) > 0:
|
||||
person = fr'<l{link[0]}> {person}'
|
||||
self.edges.append((fr'{node_id}:l{link[0]}', fr'n{link[0]}'))
|
||||
texts.append(person)
|
||||
|
||||
# Add node
|
||||
table = r"|".join(texts)
|
||||
self.dot.node(node_id, table)
|
||||
|
||||
# Add edges
|
||||
self.dot.edges(self.edges)
|
||||
|
||||
def export_graph(self, out_dir):
|
||||
self.dot.render(directory=out_dir)
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return self.dot.source
|
||||
@ -1,6 +1,6 @@
|
||||
from db import load_data, load_config
|
||||
from graph import Graph
|
||||
import new_graph as ng
|
||||
|
||||
import os
|
||||
|
||||
DATA_DIR="/data"
|
||||
@ -10,7 +10,6 @@ def main():
|
||||
# Get all YAML files in the data directory
|
||||
yaml_files = [f for f in os.listdir(DATA_DIR) if f.endswith(('.yaml', '.yml'))]
|
||||
config = load_config(CONFIG_FILE)
|
||||
puudot_config = config.get('puudot', {})
|
||||
|
||||
for yaml_file in yaml_files:
|
||||
print(f"Processing {yaml_file}...")
|
||||
@ -20,23 +19,11 @@ def main():
|
||||
graph.set_config(config)
|
||||
graph.process_blocks(data)
|
||||
graph.build_dot()
|
||||
|
||||
# Test new dot generation
|
||||
new_graph = ng.RecordGraph()
|
||||
new_graph.create_graph(config, data)
|
||||
#print(new_graph)
|
||||
new_graph.export_graph(DATA_DIR)
|
||||
|
||||
# Use the base name of the YAML file (without extension) as the output name
|
||||
base_name = os.path.splitext(yaml_file)[0]
|
||||
dot_file = os.path.join(DATA_DIR, f"{base_name}.gv")
|
||||
|
||||
if puudot_config.get('verbose') == True:
|
||||
print(f"Output formats: {puudot_config.get('output')}")
|
||||
|
||||
for format in puudot_config.get('output'):
|
||||
out_file = os.path.join(DATA_DIR, f"{base_name}.{format}")
|
||||
graph.make_dot(dot_file, out_file, format)
|
||||
svg_file = os.path.join(DATA_DIR, f"{base_name}.svg")
|
||||
graph.make_dot("svg", dot_file, svg_file)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
24
config.yaml
24
config.yaml
@ -1,25 +1,17 @@
|
||||
puudot:
|
||||
verbose: false
|
||||
output:
|
||||
- pdf
|
||||
- svg
|
||||
dot:
|
||||
graph: |
|
||||
graph [splines=true, nodesep=0.25, ranksep="1 equally"]
|
||||
//graph [splines=ortho, nodesep=0.2, ranksep="0.5 equally"]
|
||||
graph [splines=polyline, nodesep=0.2, ranksep="0.5 equally"]
|
||||
//graph [splines=curved, nodesep=0.2, ranksep="0.5 equally"]
|
||||
//graph [splines=true, nodesep=0.2, ranksep="0.5 equally"]
|
||||
//graph [splines=line, nodesep=0.2, ranksep="0.5 equally"]
|
||||
//node [color=white]
|
||||
//edge [headport=n, tailport=s]
|
||||
compound=true
|
||||
center=true
|
||||
fontname="Helvetica"
|
||||
charset="UTF-8"
|
||||
bgcolor="white"
|
||||
size="100,11.693!"
|
||||
subgraph: |
|
||||
subgraph:
|
||||
block: |
|
||||
labeljust=l
|
||||
hidden: |
|
||||
//rank=same
|
||||
//style=invis
|
||||
node:
|
||||
text: |
|
||||
shape=plaintext
|
||||
@ -30,7 +22,7 @@ dot:
|
||||
height=0
|
||||
edge:
|
||||
default: |
|
||||
headport=n
|
||||
tailport=s
|
||||
//headport=n
|
||||
//tailport=s
|
||||
hidden: |
|
||||
style=invis
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
Before Width: | Height: | Size: 272 KiB After Width: | Height: | Size: 139 KiB |
File diff suppressed because it is too large
Load Diff
@ -9,4 +9,3 @@ services:
|
||||
- ./code:/code
|
||||
- ./data:/data
|
||||
- ./config.yaml:/config.yaml
|
||||
network_mode: none
|
||||
|
||||
12
run.sh
Normal file → Executable file
12
run.sh
Normal file → Executable file
@ -6,17 +6,5 @@ if [ ! -f "/code/puudot.py" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if config.yaml exists
|
||||
if [ ! -f "/config.yaml" ]; then
|
||||
echo "Error: config.yaml not found in /config directory!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
verbose=$(yq eval '.puudot.verbose' /config.yaml)
|
||||
|
||||
if [ "$verbose" = true ]; then
|
||||
echo $(dot -v)
|
||||
fi
|
||||
|
||||
# Run the script
|
||||
python3 /code/puudot.py
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user