From 1b9d4a4a93a65ad886d28319966ac64b92f34efb Mon Sep 17 00:00:00 2001 From: Ricardo Guiraldelli Date: Wed, 3 Apr 2024 21:39:48 +0300 Subject: [PATCH] Add script to generate concept maps using Graphviz --- concept-map.sh | 177 ++++++++++++++++++++++++++++++++++++++++++++++ concept-style.dot | 32 +++++++++ 2 files changed, 209 insertions(+) create mode 100644 concept-map.sh create mode 100644 concept-style.dot diff --git a/concept-map.sh b/concept-map.sh new file mode 100644 index 0000000..f0e68cb --- /dev/null +++ b/concept-map.sh @@ -0,0 +1,177 @@ +SIMPLE_DOT=$(cat <<'EOF' +BEGIN { + edge = "->" + properties_separator = ";" + open_blocks = "{" + close_blocks = "}" + hyperedge_property = "_he" + uuid_command = "uuidgen" + + blocks = 0 + processed_line = 0 + + # Print the graph header. + #print "digraph {" +} + +# Firstly, we process blocks. +NF == 1 && $1 == open_blocks { + # open block + blocks++ +} + +NF == 1 && $1 == "}" { + # close block + blocks-- +} + +# Secondly, we process "special rules". +## Nodes. +NF > 1 && $2 != edge { + printf ($1 "\t") + label = get_label($0) + get_properties($0, properties) + properties_str = generate_properties(label, properties) + if (length(properties_str)) { + print properties_str + } + + processed_line = 1 +} + +## Edges. +NF > 3 && $2 == edge { + printf ($1 FS $2 FS $3) + label = get_label($0) + get_properties($0, properties) + properties_str = generate_properties(label, properties) + if (length(properties_str)) { + printf "\t" + print properties_str + } + + processed_line = 1 +} + +# Thirdly, we do general treatment for every line. +{ + if (!processed_line) { + print $0 + } + processed_line = 0 +} + +END { + # Print the closing curly brace. + print "}" +} + +function get_label(_line, _field, _out, _split, _len) { + _field = 2 + if ($2 == edge) { + _field = 4 + } + + # Special case: there isn't label, but there are properties. + _len = split($(_field), _split, properties_separator) + if ((_len > 1) && !_split[1]) { + # There is a properties_separator in the field, and no label. + return + } + + for (_field; _field <= NF; _field++) { + _len = split($(_field), _split, properties_separator) + if (_len > 1) { + _out = !(_out) ? _split[1] : (_out FS _split[1]) + return _out + } else { + _out = !(_out) ? $(_field) : (_out FS $(_field)) + } + } + return _out +} + +function get_properties(_line, _split, _field, _len, _uuid) { + delete _split + _len = split(_line, _split, properties_separator) + if (!_len) { + return + } + + for (_field = 2; _field <= _len; _field++) { + if (_split[_field] == hyperedge_property) { + uuid_command | getline _uuid + _split[_field] = hyperedge_property "=\"" _uuid "\"" + close(uuid_command) + } + _split[_field - 1] = _split[_field] + } + delete _split[_len] + return +} + +function generate_properties(_label, _properties, _out, _len, _type_label) { + _type_label = "label" + for (p in _properties) { + if (match(hyperedge_property, _properties[p])) { + _type_label = "xlabel" + } + } + if (_label) { + _out = (_type_label "=\"" _label "\"") + } + + _len = length(_properties) + for (i = 1; i <= _len; i++) { + _out = !(_out) ? _properties[1] : (_out "," _properties[i]) + } + + return (!(_out) ? "" : ("[" _out "]")) +} +EOF +) + +HYPERGRAPH_PROCESSOR=$(cat <<'EOF' +// An attempt to reproduce what was reported in https://gitlab.com/graphviz/graphviz/-/issues/1911#note_473279838 + +E [ _he ] { + // Creates the node representing the hyper-edge, if it already doesn't exist. + node_t hyperedge = isNode($G, $._he); + if (!hyperedge) { + hyperedge = node($G, $._he); + hyperedge.shape = "point"; + hyperedge.fillcolor = "red"; + if ($.label) { + /* + hyperedge.label = $.label; + hyperedge.shape = "none"; + */ + hyperedge.xlabel = $.label; + } + } + + // Creates the edges to and from hyperedge, if it already doesn't exist. + if (!isEdge_sg($G, $.tail, hyperedge, "")) { + edge_t new_out = edge_sg($G, $.tail, hyperedge, ""); + new_out.arrowhead = "none"; + } + if(!isEdge_sg($G, hyperedge, $.head, "")) { + edge_t new_in = edge_sg($G, hyperedge, $.head, ""); + } + + // Removes the current edge (with attribute "_he") because edges to "hyperedge" are added. + delete($G, $); +} +EOF +) + +# Process the input files and produce a SVG. +cat <(echo "digraph {") ~/src/scripts/concept-style.dot <(awk -f <(echo "${SIMPLE_DOT}") $*) | +gvpr -c -f <(echo "${HYPERGRAPH_PROCESSOR}") | +dot -Tsvg + +## DEAD-CODE +## awk -f ~/tmp/graphviz/concept.awk $* | +## gvpr -c -f ~/tmp/graphviz/hypergraph.gvpr | +## xsel -ib + diff --git a/concept-style.dot b/concept-style.dot new file mode 100644 index 0000000..f81d1b6 --- /dev/null +++ b/concept-style.dot @@ -0,0 +1,32 @@ +// Style taken, and modified, from https://mostlymaths.net/2023/07/concept-maps-helper.html/ +layout=dot +margin=1 +bgcolor="#ffffff00" +rankdir=TB +fontname="Sans-Serif" +nodesep=0.5 +overlap=scale +compound=true +node [ + fontname = "Sans-Serif" + style="rounded,filled" + labelloc=c + margin="0.3,0.15" + splines=true + shape=rect + fontsize=15 +]; +edge [ + minlen=3 + penwidth=2 + color="#33333388" + fontname="Sans-Serif" + fontsize=14 + arrowhead=normal // Latest papers about cmaps have recovered heads +]; +graph [ + //margin=8 + style="rounded,dotted" +]; +fontsize=24 +labelloc="t"