Chain-Load: draft - generate DOT diagram from calculation topology

With all the preceding DSL work, this turns out to be surprisingly easy;
the only minor twist is the grouping of nodes into (time)levels,
which can be achieved with a "lagging" update from the loop body

Note: next step will be to extract the DSL helpers into a Library header
This commit is contained in:
Fischlurch 2023-11-16 01:46:55 +01:00
parent 65fa16b626
commit 1c4b1a2973
4 changed files with 529 additions and 40 deletions

438
src/lib/dot-gen.hpp Normal file
View file

@ -0,0 +1,438 @@
/*
DOT-GEN.hpp - DSL to generate Graphviz-DOT code
Copyright (C) Lumiera.org
2023, Hermann Vosseler <Ichthyostega@web.de>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/** @file dot-gen.hpp
** Support for generation of Graphviz-DOT code for structure visualisation.
**
** @see TestChainLoad_test
** @see SchedulerStress_test
*/
#ifndef LIB_DOT_GEN_H
#define LIB_DOT_GEN_H
#include "vault/common.hpp"
#include "lib/test/test-helper.hpp"
//#include "vault/gear/job.h"
//#include "vault/gear/activity.hpp"
//#include "vault/gear/nop-job-functor.hpp"
//#include "lib/time/timevalue.hpp"
//#include "lib/meta/variadic-helper.hpp"
//#include "lib/meta/function.hpp"
//#include "lib/wrapper.hpp"
#include "lib/format-util.hpp" /////////////////TODO used only for dot generation
#include "lib/util.hpp" /////////////////TODO used only for dot generation
#include <boost/functional/hash.hpp>
#include <functional>
#include <utility>
//#include <string>
//#include <deque>
#include <memory>
#include <sstream> /////////////////TODO used only for dot generation
#include <string>
#include <vector> /////////////////TODO used only for dot generation
#include <array>
namespace lib {
// using std::string;
// using std::function;
// using lib::time::TimeValue;
// using lib::time::Time;
// using lib::time::FSecs;
// using lib::time::Offset;
// using lib::meta::RebindVariadic;
using util::toString; /////////////////TODO used only for dot generation
using util::isnil; /////////////////TODO used only for dot generation
using util::max;
using util::unConst;
// using std::forward;
using std::string;
using std::swap;
using std::move;
using boost::hash_combine;
namespace {// Diagnostic markers
// const string MARK_INC{"IncSeq"};
// const string MARK_SEQ{"Seq"};
const size_t DEFAULT_FAN = 16;
const size_t DEFAULT_SIZ = 256;
// using SIG_JobDiagnostic = void(Time, int32_t);
}
namespace dot {
struct Code : string
{
using string::string;
Code(string const& c) : string{c} { }
Code(string && c) : string{move(c)}{ }
};
struct Section
{
std::vector<string> lines;
Section (string name)
: lines{"// "+name}
{ }
Section&&
operator+= (Code const& code)
{
lines.emplace_back(code);
return move(*this);
}
};
/**
* Helper to generate DOT-Graphviz rendering of topology
*/
class DotOut
{
std::ostringstream buff_;
static uint const IDENT_STEP = 2;
public:
void
putLine (string line, uint indent=0)
{
if (indent)
buff_ << string(indent,' ');
buff_ << line
<< '\n';
}
void
put (Code const& code)
{
buff_ << code;
}
void
put (Section const& sect)
{
for (string const& line : sect.lines)
putLine (line, IDENT_STEP);
}
template<class P, class...PS>
void
put (P const& part, PS const& ...parts)
{
put (part);
putLine ("");
put (parts...);
}
/** retrieve complete code generated thus far */
operator string() const
{
return buff_.str();
}
};
struct Node : Code
{
Node (size_t id)
: Code{"N"+toString(id)}
{ }
Node&&
addAttrib (string def)
{
if (back() != ']')
append ("[");
else
{
resize (length()-2);
append (", ");
}
append (def+" ]");
return move(*this);
}
Node&&
label (size_t i)
{
return addAttrib ("label="+toString(i));
}
Node&&
style (Code const& code)
{
if (not isnil(code))
addAttrib (code);
return move(*this);
}
};
struct Scope : Code
{
Scope (size_t id)
: Code{"{ /*"+toString(id)+"*/ }"}
{ }
Scope&&
add (Code const& code)
{
resize(length()-1);
append (code+" }");
return move(*this);
}
Scope&&
rank (string rankSetting)
{
return add(Code{"rank="+rankSetting});
}
};
inline Code
connect (size_t src, size_t dest)
{
return Code{Node(src) +" -> "+ Node(dest)};
}
template<class...COD>
inline DotOut
digraph (COD ...parts)
{
DotOut script;
script.putLine (Code{"digraph {"});
script.put (parts...);
script.putLine (Code{"}"});
return script;
}
}
/**
* A Generator for synthetic Render Jobs for Scheduler load testing.
* @tparam maxFan maximal fan-in/out from a node, also limits maximal parallel strands.
* @see TestChainLoad_test
*/
template<size_t numNodes =DEFAULT_SIZ, size_t maxFan =DEFAULT_FAN>
class TestChainLoad
: util::MoveOnly
{
public:
struct Node
: util::MoveOnly
{
using _Arr = std::array<Node*, maxFan>;
using Iter = typename _Arr::iterator;
/** Table with connections to other Node records */
struct Tab : _Arr
{
Iter after = _Arr::begin();
Iter end() { return after; }
friend Iter end(Tab& tab) { return tab.end(); }
void clear() { after = _Arr::begin(); } ///< @warning pointer data in array not cleared
size_t size() const { return unConst(this)->end()-_Arr::begin(); }
bool empty() const { return 0 == size(); }
Iter
add(Node* n)
{
if (after != _Arr::end())
{
*after = n;
return after++;
}
NOTREACHED ("excess node linkage");
}
};
size_t hash;
size_t level{0}, repeat{0};
Tab pred{0}, succ{0};
Node(size_t seed =0)
: hash{seed}
{ }
void
clear()
{
hash = 0;
level = repeat = 0;
pred.clear();
succ.clear();
}
Node&
addPred (Node* other)
{
REQUIRE (other);
pred.add (other);
other->succ.add (this);
return *this;
}
Node&
addSucc (Node* other)
{
REQUIRE (other);
succ.add (other);
other->pred.add (this);
return *this;
}
Node& addPred(Node& other) { return addPred(&other); }
Node& addSucc(Node& other) { return addSucc(&other); }
size_t
calculate()
{
for (Node*& entry: pred)
if (entry)
hash_combine (hash, entry->hash);
return hash;
}
};
private:
using NodeTab = typename Node::Tab;
using NodeStorage = std::array<Node, numNodes>;
using CtrlRule = std::function<size_t(size_t, double)>;
std::unique_ptr<NodeStorage> nodes_;
CtrlRule seedingRule_ {[](size_t, double){ return 0; }};
CtrlRule expansionRule_{[](size_t, double){ return 0; }};
CtrlRule reductionRule_{[](size_t, double){ return 0; }};
public:
TestChainLoad()
: nodes_{new NodeStorage}
{ }
size_t size() const { return nodes_->size(); }
size_t topLevel() const { return nodes_->back().level; }
size_t getSeed() const { return nodes_->front().hash; }
size_t getHash() const { return nodes_->back().hash; }
/* ===== topology control ===== */
/**
* Use current configuration and seed to (re)build Node connectivity.
*/
TestChainLoad
buildToplolgy()
{
NodeTab a,b, // working data for generation
*curr{&a}, // the current set of nodes to carry on
*next{&b}; // the next set of nodes connected to current
Node* node = &nodes_->front();
size_t level{0};
size_t expectedLevel = max (1u, numNodes/maxFan);
// prepare building blocks for the topology generation...
auto height = [&](double level)
{
return level/expectedLevel;
};
auto spaceLeft = [&]{ return next->size() < maxFan; };
auto addNode = [&]{
Node* n = *next->add (node++);
n->clear();
n->level = level;
return n;
};
auto apply = [&](CtrlRule& rule, Node* n)
{
return rule (n->hash, height(level));
};
addNode(); // prime next with root node
// visit all further nodes and establish links
while (node < &nodes_->back())
{
++level;
curr->clear();
swap (next, curr);
size_t toReduce{0};
Node* r;
REQUIRE (spaceLeft());
for (Node* o : *curr)
{ // follow-up on all Nodes in current level...
o->calculate();
size_t toSeed = apply (seedingRule_, o);
size_t toExpand = apply (expansionRule_,o);
while (0 < toSeed and spaceLeft())
{ // start a new chain from seed
Node* n = addNode();
n->hash = this->getSeed();
--toSeed;
}
while (0 < toExpand and spaceLeft())
{ // fork out secondary chain from o
Node* n = addNode();
o->addSucc(n);
--toExpand;
}
if (not toReduce and spaceLeft())
{ // carry-on the chain from o
r = addNode();
toReduce = apply (reductionRule_, o);
}
else
--toReduce;
ENSURE (r);
r->addPred(o);
}
}
ENSURE (node == &nodes_->back());
// connect ends of all remaining chains to top-Node
node->clear();
node->level = ++level;
for (Node* o : *next)
node->addPred(o);
//
return move(*this);
}
private:
};
} // namespace lib
#endif /*LIB_DOT_GEN_H*/

View file

@ -177,18 +177,7 @@ namespace test {
CHECK (0 == graph.getHash());
///////////////////////////////////////////////////////////////////////TODO : what follows is WIP to test the DOT graph generator....
using N = TestChainLoad<>::Node;
std::array<N,7> n;
n[1].addPred(n[0]);
n[2].addPred(n[0]);
n[3].addPred(n[1]);
n[3].addPred(n[2]);
n[5].addPred(n[1]);
n[6].addPred(n[3]);
n[6].addPred(n[4]);
n[6].addPred(n[5]);
n[0].hash = 55;
n[4].hash = n[0].hash;
using N = const TestChainLoad<32>::Node;
using namespace dot;
Section nodes("Nodes");
@ -200,8 +189,12 @@ namespace test {
Code TOP {"shape=box, style=rounded"};
Code DEFAULT{};
auto nNr = [&](N& nn){ return size_t(&nn - &n[0]); };
for (N& nn : n)
N& n0n = *graph.allNodes();
auto nNr = [&](N& nn){ return size_t(&nn - &n0n); };
size_t level(0);
Scope timeLevel{level};
layers += timeLevel.rank("min ");
for (N& nn : graph.allNodes())
{
size_t i = nNr(nn);
nodes += Node(i).label(i+1).style(i==0 ? BOTTOM
@ -210,12 +203,16 @@ namespace test {
: DEFAULT);
for (N* suc : nn.succ)
topology += connect(i, nNr(*suc));
if (level != nn.level)
{
++level;
ENSURE (level == nn.level);
timeLevel = Scope(level).rank("same");
layers += timeLevel;
}
timeLevel.add(Node(i));
}
layers += Scope(0).rank("min ").add(Node(0));
layers += Scope(1).rank("same").add(Node(1)).add(Node(2));
layers += Scope(2).rank("same").add(Node(3)).add(Node(4)).add(Node(5));
layers += Scope(3).rank("same").add(Node(6));
cout << digraph(nodes,layers,topology) <<endl;
}

View file

@ -82,6 +82,7 @@
//#include "lib/meta/variadic-helper.hpp"
//#include "lib/meta/function.hpp"
//#include "lib/wrapper.hpp"
#include "lib/iter-explorer.hpp"
#include "lib/format-util.hpp" /////////////////TODO used only for dot generation
#include "lib/util.hpp" /////////////////TODO used only for dot generation
@ -147,9 +148,9 @@ namespace test {
{ }
Section&&
operator+= (Code&& code)
operator+= (Code const& code)
{
lines.emplace_back(move (code));
lines.emplace_back(code);
return move(*this);
}
};
@ -295,13 +296,17 @@ namespace test {
{
using _Arr = std::array<Node*, maxFan>;
using Iter = typename _Arr::iterator;
using CIter = typename _Arr::const_iterator;
/** Table with connections to other Node records */
struct Tab : _Arr
{
Iter after = _Arr::begin();
Iter end() { return after; }
friend Iter end(Tab& tab) { return tab.end(); }
Iter end() { return after; }
CIter end() const{ return after; }
friend Iter end (Tab & tab){ return tab.end(); }
friend CIter end (Tab const& tab){ return tab.end(); }
void clear() { after = _Arr::begin(); } ///< @warning pointer data in array not cleared
@ -391,6 +396,15 @@ namespace test {
size_t getHash() const { return nodes_->back().hash; }
using NodeIter = decltype(lib::explore (std::declval<NodeStorage & >()));
NodeIter
allNodes()
{
return lib::explore (*nodes_);
}
/* ===== topology control ===== */
/**

View file

@ -95854,13 +95854,13 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1700100290904" ID="ID_1991398605" MODIFIED="1700100351351" TEXT="brauche Hilfsmittel zur Visualisierung">
<arrowlink COLOR="#62819c" DESTINATION="ID_1464042796" ENDARROW="Default" ENDINCLINATION="21;-256;" ID="Arrow_ID_596151514" STARTARROW="None" STARTINCLINATION="787;55;"/>
<icon BUILTIN="pencil"/>
<node COLOR="#338800" CREATED="1700100356900" HGAP="45" ID="ID_216360062" LINK="#ID_764096140" MODIFIED="1700100481583" TEXT="erst mal einfaches Referenz-Skript (re)-generieren" VSHIFT="-5">
<node COLOR="#338800" CREATED="1700100356900" FOLDED="true" HGAP="45" ID="ID_216360062" LINK="#ID_764096140" MODIFIED="1700105412090" TEXT="erst mal einfaches Referenz-Skript (re)-generieren" VSHIFT="-5">
<icon BUILTIN="button_ok"/>
<node CREATED="1700100394556" ID="ID_587545230" MODIFIED="1700100416123" TEXT="die entsprechende Node-Struktur fest verdrahtet aufbauen"/>
<node CREATED="1700100416777" ID="ID_1280977723" MODIFIED="1700100426249" TEXT="Code-Schema zur Generierung daran entwickeln"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1700100453954" HGAP="138" ID="ID_645441127" MODIFIED="1700100479215" TEXT="auf komplexere Topologien ausweiten" VSHIFT="-12">
<icon BUILTIN="flag-yellow"/>
<node COLOR="#338800" CREATED="1700100453954" HGAP="138" ID="ID_645441127" MODIFIED="1700105287506" TEXT="auf komplexere Topologien ausweiten" VSHIFT="-12">
<icon BUILTIN="button_ok"/>
</node>
</node>
</node>
@ -96016,7 +96016,7 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1699972014017" ID="ID_1464042796" MODIFIED="1700100343215" TEXT="Topologie sichtbar machen">
<linktarget COLOR="#62819c" DESTINATION="ID_1464042796" ENDARROW="Default" ENDINCLINATION="21;-256;" ID="Arrow_ID_596151514" SOURCE="ID_1991398605" STARTARROW="None" STARTINCLINATION="787;55;"/>
<icon BUILTIN="flag-yellow"/>
<node CREATED="1699972026007" ID="ID_551004748" MODIFIED="1699984490886" TEXT="Idee: DOT generieren (Graphviz)">
<node COLOR="#435e98" CREATED="1699972026007" ID="ID_551004748" MODIFIED="1700105381375" TEXT="Idee: DOT generieren (Graphviz)">
<arrowlink COLOR="#435fb4" DESTINATION="ID_1402372766" ENDARROW="Default" ENDINCLINATION="-1358;124;" ID="Arrow_ID_1189460163" STARTARROW="None" STARTINCLINATION="-1697;146;"/>
<icon BUILTIN="idea"/>
<node CREATED="1699982887957" ID="ID_1019263938" MODIFIED="1699983239443" TEXT="sollte mit einfachsten DOT-Features m&#xf6;glich sein">
@ -96043,8 +96043,8 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
</body>
</html></richcontent>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1699983274744" ID="ID_1122794529" MODIFIED="1699983281544" TEXT="Darstellungsfeatures bereitstellen">
<icon BUILTIN="flag-yellow"/>
<node COLOR="#338800" CREATED="1699983274744" FOLDED="true" ID="ID_1122794529" MODIFIED="1700105386483" TEXT="Darstellungsfeatures bereitstellen">
<icon BUILTIN="button_ok"/>
<node CREATED="1699984555015" ID="ID_1692130388" MODIFIED="1699984558895" TEXT="von unten nach oben"/>
<node CREATED="1699984543111" ID="ID_219083359" MODIFIED="1699984554042" TEXT="Zeitebene : horizontal auf einem Level"/>
<node CREATED="1699984727751" ID="ID_1646991690" MODIFIED="1699984735954" TEXT="Seed-Nodes: circle"/>
@ -96135,7 +96135,7 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
</p>
</body>
</html></richcontent>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1699989949319" ID="ID_1160779868" MODIFIED="1699989973277" STYLE="fork" TEXT="wichtig">
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1699989949319" FOLDED="true" ID="ID_1160779868" MODIFIED="1700105389973" STYLE="fork" TEXT="wichtig">
<icon BUILTIN="messagebox_warning"/>
<node CREATED="1699989975909" ID="ID_1742749035" MODIFIED="1699990064728">
<richcontent TYPE="NODE"><html>
@ -96162,11 +96162,14 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1699983287679" ID="ID_1236079516" MODIFIED="1699983381771" TEXT="Ausgabestruktur erzeugen">
<icon BUILTIN="flag-yellow"/>
<node BACKGROUND_COLOR="#f8f1cb" COLOR="#a50125" CREATED="1699983387905" ID="ID_1906181979" MODIFIED="1699983399913" TEXT="mu&#xdf; Klammern und Gruppen erzeugen">
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1699983287679" ID="ID_1236079516" MODIFIED="1700105397578" TEXT="Ausgabestruktur erzeugen">
<icon BUILTIN="pencil"/>
<node COLOR="#435e98" CREATED="1699983387905" FOLDED="true" ID="ID_1906181979" MODIFIED="1700105376397" TEXT="mu&#xdf; Klammern und Gruppen erzeugen">
<icon BUILTIN="messagebox_warning"/>
<node CREATED="1699995923504" ID="ID_685647464" MODIFIED="1699995941946" TEXT="brauche eine DSL mit builder-Notation"/>
<icon BUILTIN="hourglass"/>
<node COLOR="#435e98" CREATED="1699995923504" ID="ID_685647464" MODIFIED="1700105364799" TEXT="brauche eine DSL mit builder-Notation">
<icon BUILTIN="yes"/>
</node>
<node CREATED="1699998536090" ID="ID_1929608086" MODIFIED="1699998999619" TEXT="mu&#xdf; Subexpressions in der DSL abgrenzen">
<edge COLOR="#82603d" STYLE="linear"/>
<node CREATED="1699998574428" ID="ID_1088049425" MODIFIED="1699998608692" TEXT="M&#xf6;glichkeit-1 : end-Function">
@ -96368,8 +96371,8 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<icon BUILTIN="idea"/>
</node>
</node>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1699983576099" ID="ID_1099057461" MODIFIED="1700100170389" TEXT="einfachen Basis-Renderer f&#xfc;r DOT anlegen (ausbauf&#xe4;hig)">
<icon BUILTIN="pencil"/>
<node COLOR="#338800" CREATED="1699983576099" ID="ID_1099057461" MODIFIED="1700105296039" TEXT="einfachen Basis-Renderer f&#xfc;r DOT anlegen (ausbauf&#xe4;hig)">
<icon BUILTIN="button_ok"/>
<node COLOR="#338800" CREATED="1699984818923" ID="ID_1140148647" MODIFIED="1700099172943" TEXT="Generator-Funktionen f&#xfc;r Standard-Codebausteine">
<icon BUILTIN="button_ok"/>
</node>
@ -96387,8 +96390,7 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
...aber sage erst mal <b>YAGNI</b>
</p>
</body>
</html>
</richcontent>
</html></richcontent>
</node>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1700099220184" ID="ID_98826849" MODIFIED="1700099995491" TEXT="das w&#xe4;re n&#xe4;mlich nicht trivial in C++">
<icon BUILTIN="messagebox_warning"/>
@ -96413,10 +96415,35 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<icon BUILTIN="yes"/>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1699983654942" ID="ID_1968810638" MODIFIED="1699983679568" TEXT="lokale Spezialisierung hiervon im Arbeits-Scope">
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1699983654942" ID="ID_1968810638" MODIFIED="1700105304336" TEXT="lokale Spezialisierung hiervon im Arbeits-Scope">
<icon BUILTIN="pencil"/>
<node COLOR="#338800" CREATED="1700105305528" ID="ID_1899482943" MODIFIED="1700105320553" TEXT="testweise direkt im verify_Topology() aufgebaut">
<icon BUILTIN="button_ok"/>
</node>
<node COLOR="#435e98" CREATED="1700105322133" ID="ID_1080416201" MODIFIED="1700105347114" TEXT="einfache For-Schleife mit &#xbb;schleppendem&#xab; time-Level">
<icon BUILTIN="idea"/>
</node>
</node>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1700105473288" ID="ID_363744899" MODIFIED="1700105544295" TEXT="Operatoren">
<icon BUILTIN="pencil"/>
<node COLOR="#338800" CREATED="1700105477363" ID="ID_1347431300" MODIFIED="1700105485349" TEXT="Iteration &#xfc;ber alle Nodes">
<icon BUILTIN="button_ok"/>
<node COLOR="#435e98" CREATED="1700105486643" ID="ID_305524836" MODIFIED="1700105501783" TEXT="exponiere direkt lib::IterExplorer()">
<icon BUILTIN="idea"/>
</node>
<node COLOR="#435e98" CREATED="1700105502189" ID="ID_1284175277" MODIFIED="1700105541879" TEXT="alle weiteren Operatoren lassen sich dann leicht darauf aufbauen"/>
<node COLOR="#338800" CREATED="1700105521395" ID="ID_5876464" MODIFIED="1700105535812" TEXT="Umgang mit const Nodes erm&#xf6;glichen">
<icon BUILTIN="button_ok"/>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1700105549294" ID="ID_1778641158" MODIFIED="1700105554766" TEXT="Neuberechnung">
<icon BUILTIN="flag-yellow"/>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1700105555476" ID="ID_587761135" MODIFIED="1700105558876" TEXT="Scheduling">
<icon BUILTIN="flag-yellow"/>
</node>
</node>
</node>
@ -103828,6 +103855,19 @@ class Something
<node CREATED="1699978461307" ID="ID_1957589954" MODIFIED="1699978480653" TEXT="color : outline / edges"/>
<node CREATED="1699978481466" ID="ID_1236422711" MODIFIED="1699978503602" TEXT="fillcolor &#x2014; setzt style=filled vorraus"/>
</node>
<node CREATED="1700105113377" ID="ID_1656490787" LINK="https://graphviz.org/faq/#FaqLarger" MODIFIED="1700105197251" TEXT="Gr&#xf6;&#xdf;en">
<node CREATED="1700105117995" ID="ID_1615340120" LINK="https://graphviz.org/docs/attrs/size/" MODIFIED="1700105130514" TEXT="size">
<node CREATED="1700105132262" ID="ID_343226797" MODIFIED="1700105138969" TEXT="maximalgr&#xe4;&#xdf;e in Inches"/>
<node CREATED="1700105139702" ID="ID_1994987569" MODIFIED="1700105152857" TEXT="size! : dann verbindliche Gr&#xf6;&#xdf;e"/>
<node CREATED="1700105153462" ID="ID_1167641306" MODIFIED="1700105161830" TEXT="Wechselwirkung mit ratio"/>
</node>
<node CREATED="1700105163468" ID="ID_514140217" LINK="https://graphviz.org/docs/attrs/ratio/" MODIFIED="1700105172944" TEXT="ratio">
<node CREATED="1700105202533" ID="ID_133109322" MODIFIED="1700105215303" TEXT="entweder numerisches Seitenverh&#xe4;ltnis"/>
<node CREATED="1700105215851" ID="ID_948086315" MODIFIED="1700105219623" TEXT="oder Strategie">
<node CREATED="1700105253878" ID="ID_1875666186" MODIFIED="1700105266504" TEXT="fill / compress / expand / auto"/>
</node>
</node>
</node>
</node>
</node>
</node>