Compare commits
1 Commits
main
...
dev/blocki
| Author | SHA1 | Date | |
|---|---|---|---|
| 83283c0657 |
@ -1,14 +1,10 @@
|
|||||||
FROM alpine
|
FROM alpine
|
||||||
RUN apk add --no-cache \
|
RUN apk add --no-cache \
|
||||||
yq \
|
|
||||||
python3 \
|
python3 \
|
||||||
py3-pip \
|
|
||||||
py3-yaml \
|
py3-yaml \
|
||||||
graphviz
|
graphviz
|
||||||
|
|
||||||
COPY run.sh /run.sh
|
COPY run.sh /run.sh
|
||||||
RUN chmod +x /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
|
# - also see: https://stackoverflow.com/questions/53862417/how-to-set-head-and-tail-position-in-nodes-graphviz
|
||||||
# Solve layering of clusters
|
# Solve layering of clusters
|
||||||
# - https://observablehq.com/@gordonsmith/church
|
# - 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"
|
return line + "\n"
|
||||||
|
|
||||||
def make_block_line(block, config):
|
def make_block_line(block, config):
|
||||||
line = f"subgraph cluster_{block['id']} {{\n{config["subgraph"]}\n"
|
configs = []
|
||||||
for node in block["texts"]:
|
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"
|
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"
|
return line + "}\n\n"
|
||||||
|
|
||||||
|
|
||||||
@ -76,7 +90,6 @@ class Graph:
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.config = {}
|
self.config = {}
|
||||||
|
|
||||||
self.layers = {}
|
|
||||||
self.blocks = []
|
self.blocks = []
|
||||||
self.nodes = []
|
self.nodes = []
|
||||||
self.links = []
|
self.links = []
|
||||||
@ -89,12 +102,15 @@ class Graph:
|
|||||||
dot = config.get("dot", {})
|
dot = config.get("dot", {})
|
||||||
if dot != {}:
|
if dot != {}:
|
||||||
dot["graph"] = dot.get("graph", "")
|
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"] = dot.get("node", {})
|
||||||
dot["node"]["text"] = dot["node"].get("text", "")
|
dot["node"]["text"] = dot["node"].get("text", "")
|
||||||
dot["node"]["hidden"] = dot["node"].get("hidden", "")
|
dot["node"]["hidden"] = dot["node"].get("hidden", "")
|
||||||
dot["edge"] = dot.get("edge", {})
|
dot["edge"] = dot.get("edge", {})
|
||||||
dot["edge"]["default"] = dot["edge"].get("default", "")
|
dot["edge"]["default"] = dot["edge"].get("default", "")
|
||||||
|
dot["edge"]["middle"] = dot["edge"].get("middle", "")
|
||||||
dot["edge"]["hidden"] = dot["edge"].get("hidden", "")
|
dot["edge"]["hidden"] = dot["edge"].get("hidden", "")
|
||||||
self.config = dot
|
self.config = dot
|
||||||
|
|
||||||
@ -108,10 +124,10 @@ class Graph:
|
|||||||
new_block = {
|
new_block = {
|
||||||
"id": block_id,
|
"id": block_id,
|
||||||
"label": block.get("label", ""),
|
"label": block.get("label", ""),
|
||||||
"texts": []
|
"nodes": [],
|
||||||
|
"hidden": False
|
||||||
}
|
}
|
||||||
|
|
||||||
#prev_node_id = ""
|
|
||||||
texts_in_block = block.get("texts", [])
|
texts_in_block = block.get("texts", [])
|
||||||
for i, text in enumerate(texts_in_block):
|
for i, text in enumerate(texts_in_block):
|
||||||
node_id = get_id()
|
node_id = get_id()
|
||||||
@ -120,21 +136,27 @@ class Graph:
|
|||||||
if i == math.ceil(len(texts_in_block) / 2) - 1:
|
if i == math.ceil(len(texts_in_block) / 2) - 1:
|
||||||
for link in block.get("links", []):
|
for link in block.get("links", []):
|
||||||
if link in linker:
|
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})
|
self.links.append({"from": linker[link], "to": node_id, "head": block_id, "hidden": False})
|
||||||
del linker[link]
|
del linker[link]
|
||||||
|
|
||||||
self.nodes.append({"id": node_id, "text": text.get("text", ""), "hidden": False})
|
self.nodes.append({"id": node_id, "text": text.get("text", ""), "hidden": False})
|
||||||
|
|
||||||
# Chain nodes in block
|
new_block["nodes"].append(node_id)
|
||||||
#if prev_node_id:
|
|
||||||
# self.links.append({"from": prev_node_id, "to": node_id, "head": "", "hidden": True})
|
|
||||||
|
|
||||||
new_block["texts"].append(node_id)
|
|
||||||
|
|
||||||
for link in text.get("links", []):
|
for link in text.get("links", []):
|
||||||
linker[link] = node_id
|
linker[link] = node_id
|
||||||
|
|
||||||
#prev_node_id = node_id
|
|
||||||
|
|
||||||
self.blocks.append(new_block)
|
self.blocks.append(new_block)
|
||||||
|
|
||||||
@ -145,12 +167,12 @@ class Graph:
|
|||||||
self.dot_file += add_links(self.links, self.config)
|
self.dot_file += add_links(self.links, self.config)
|
||||||
self.dot_file += make_footer()
|
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 != "":
|
if self.dot_file != "":
|
||||||
with open(dot_file, "w") as f:
|
with open(dot_file, "w") as f:
|
||||||
f.write(self.dot_file)
|
f.write(self.dot_file)
|
||||||
if format:
|
if format == "svg":
|
||||||
os.system(f"dot -T{format} {dot_file} -o {out_file}")
|
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 db import load_data, load_config
|
||||||
from graph import Graph
|
from graph import Graph
|
||||||
import new_graph as ng
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
DATA_DIR="/data"
|
DATA_DIR="/data"
|
||||||
@ -10,7 +10,6 @@ def main():
|
|||||||
# Get all YAML files in the data directory
|
# Get all YAML files in the data directory
|
||||||
yaml_files = [f for f in os.listdir(DATA_DIR) if f.endswith(('.yaml', '.yml'))]
|
yaml_files = [f for f in os.listdir(DATA_DIR) if f.endswith(('.yaml', '.yml'))]
|
||||||
config = load_config(CONFIG_FILE)
|
config = load_config(CONFIG_FILE)
|
||||||
puudot_config = config.get('puudot', {})
|
|
||||||
|
|
||||||
for yaml_file in yaml_files:
|
for yaml_file in yaml_files:
|
||||||
print(f"Processing {yaml_file}...")
|
print(f"Processing {yaml_file}...")
|
||||||
@ -20,23 +19,11 @@ def main():
|
|||||||
graph.set_config(config)
|
graph.set_config(config)
|
||||||
graph.process_blocks(data)
|
graph.process_blocks(data)
|
||||||
graph.build_dot()
|
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
|
# Use the base name of the YAML file (without extension) as the output name
|
||||||
base_name = os.path.splitext(yaml_file)[0]
|
base_name = os.path.splitext(yaml_file)[0]
|
||||||
dot_file = os.path.join(DATA_DIR, f"{base_name}.gv")
|
dot_file = os.path.join(DATA_DIR, f"{base_name}.gv")
|
||||||
|
svg_file = os.path.join(DATA_DIR, f"{base_name}.svg")
|
||||||
if puudot_config.get('verbose') == True:
|
graph.make_dot("svg", dot_file, svg_file)
|
||||||
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)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
26
config.yaml
26
config.yaml
@ -1,25 +1,17 @@
|
|||||||
puudot:
|
|
||||||
verbose: false
|
|
||||||
output:
|
|
||||||
- pdf
|
|
||||||
- svg
|
|
||||||
dot:
|
dot:
|
||||||
graph: |
|
graph: |
|
||||||
|
graph [splines=true, nodesep=0.25, ranksep="1 equally"]
|
||||||
//graph [splines=ortho, nodesep=0.2, ranksep="0.5 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]
|
//node [color=white]
|
||||||
//edge [headport=n, tailport=s]
|
//edge [headport=n, tailport=s]
|
||||||
compound=true
|
compound=true
|
||||||
center=true
|
center=true
|
||||||
fontname="Helvetica"
|
subgraph:
|
||||||
charset="UTF-8"
|
block: |
|
||||||
bgcolor="white"
|
labeljust=l
|
||||||
size="100,11.693!"
|
hidden: |
|
||||||
subgraph: |
|
//rank=same
|
||||||
labeljust=l
|
//style=invis
|
||||||
node:
|
node:
|
||||||
text: |
|
text: |
|
||||||
shape=plaintext
|
shape=plaintext
|
||||||
@ -30,7 +22,7 @@ dot:
|
|||||||
height=0
|
height=0
|
||||||
edge:
|
edge:
|
||||||
default: |
|
default: |
|
||||||
headport=n
|
//headport=n
|
||||||
tailport=s
|
//tailport=s
|
||||||
hidden: |
|
hidden: |
|
||||||
style=invis
|
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
|
- ./code:/code
|
||||||
- ./data:/data
|
- ./data:/data
|
||||||
- ./config.yaml:/config.yaml
|
- ./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
|
exit 1
|
||||||
fi
|
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
|
# Run the script
|
||||||
python3 /code/puudot.py
|
python3 /code/puudot.py
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user