-
Notifications
You must be signed in to change notification settings - Fork 172
Expand file tree
/
Copy pathshiftLabel.gvpr
More file actions
319 lines (295 loc) · 14.1 KB
/
Copy pathshiftLabel.gvpr
File metadata and controls
319 lines (295 loc) · 14.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
BEGIN{
double bbMinX, bbMinY, bbMaxX, bbMaxY, lpX, lpY, lpMinX, lpMaxY, newX, newY, centerX;
double fudgeX, fudgeY;
double cloudMinX, cloudMinY, cloudMaxX, cloudMaxY, cloudCenterX;
double clusterMinX, clusterMinY, clusterMaxX, clusterMaxY, clusterCenterX;
double labelX, labelY, labelHalfW, labelPad;
double kidMinX, kidMinY, kidMaxX, kidMaxY;
// gap kept between a child cluster border and its parent border
double childGap = 30.0;
graph_t kidG, otherG;
double othMinX, othMinY, othMaxX, othMaxY;
double topMostY, bottomMostY, topEdgeY, nodeTop;
int hasLegend;
string fld[int];
node_t n;
// $G is not addressable inside BEGIN, so BEG_G hands the root graph over
graph_t rootG;
hasLegend = 0;
// recursive routine, processing from the top (root) down
graph_t labelShift (graph_t Gr) {
graph_t thisG;
// First recurse into subgraphs
for (thisG = fstsubg(Gr); thisG; thisG=nxtsubg(thisG)) {
thisG = labelShift(thisG);
}
if (match(Gr.name,"cluster")==0 ){
// Check for main cloud provider group using _cloudgroup flag
if (hasAttr(Gr, "_cloudgroup") && Gr._cloudgroup=="1"){
sscanf (Gr.bb, "%lf,%lf,%lf,%lf", &cloudMinX, &cloudMinY, &cloudMaxX, &cloudMaxY);
cloudCenterX = (cloudMinX + cloudMaxX) / 2.0;
}
// Position cluster label nodes within this cluster
sscanf (Gr.bb, "%lf,%lf,%lf,%lf", &clusterMinX, &clusterMinY, &clusterMaxX, &clusterMaxY);
// Children are processed first (see the recursion above) and may have
// grown to fit their own labels. neato sized this box before any of that
// happened, so grow it now to contain them - otherwise a widened subnet
// sticks out through its VNET's border.
for (kidG = fstsubg(Gr); kidG; kidG = nxtsubg(kidG)) {
if (match(kidG.name, "cluster") == 0) {
sscanf (kidG.bb, "%lf,%lf,%lf,%lf", &kidMinX, &kidMinY, &kidMaxX, &kidMaxY);
if (kidMinX - childGap < clusterMinX) clusterMinX = kidMinX - childGap;
if (kidMinY - childGap < clusterMinY) clusterMinY = kidMinY - childGap;
if (kidMaxX + childGap > clusterMaxX) clusterMaxX = kidMaxX + childGap;
if (kidMaxY + childGap > clusterMaxY) clusterMaxY = kidMaxY + childGap;
}
}
Gr.bb = sprintf("%.1f,%.1f,%.1f,%.1f",
clusterMinX, clusterMinY, clusterMaxX, clusterMaxY);
// Children widen themselves to fit their labels without being able to
// see their siblings, so two can end up overlapping. Pull the left one
// back to the gap - a label that slightly exceeds its box is far less
// confusing than two boxes drawn on top of each other.
for (kidG = fstsubg(Gr); kidG; kidG = nxtsubg(kidG)) {
if (match(kidG.name, "cluster") != 0) continue;
sscanf (kidG.bb, "%lf,%lf,%lf,%lf", &kidMinX, &kidMinY, &kidMaxX, &kidMaxY);
for (otherG = fstsubg(Gr); otherG; otherG = nxtsubg(otherG)) {
if (match(otherG.name, "cluster") != 0) continue;
if (otherG.name == kidG.name) continue;
sscanf (otherG.bb, "%lf,%lf,%lf,%lf", &othMinX, &othMinY, &othMaxX, &othMaxY);
// only siblings that share vertical space can collide
if (kidMinY < othMaxY && othMinY < kidMaxY) {
if (othMinX > kidMinX && othMinX < kidMaxX + childGap) {
kidMaxX = othMinX - childGap;
kidG.bb = sprintf("%.1f,%.1f,%.1f,%.1f",
kidMinX, kidMinY, kidMaxX, kidMaxY);
}
}
}
}
clusterCenterX = (clusterMinX + clusterMaxX) / 2.0;
// Label nodes are declared on the root graph, not inside the cluster, so
// that an over-wide label cannot stretch the cluster it labels. Scan all
// nodes and let the _clusterid check below pick out this cluster's own.
for (n = fstnode(rootG); n; n = nxtnode(n)) {
if (hasAttr(n, "_clusterlabel") && n._clusterlabel == "1") {
// Only position label nodes that belong to THIS cluster (not nested subgraphs)
if (hasAttr(n, "_clusterid") && hasAttr(n, "_labelposition")) {
if (n._clusterid == Gr.name) {
// Calculate position based on label_position attribute
// Position inside cluster at bottom with X offset to prevent neato from moving them
// pos pins the node CENTRE, so half the label's own width has to
// be added to keep its left edge a fixed inset from the cluster
// border. A constant offset only lines up for one label length,
// and drifts left as soon as labels get longer (e.g. real
// resource names instead of generic ones).
// create_cluster_label_node() sets fixedsize=false and width=0 on
// these nodes so graphviz measures them from their own label -
// otherwise they inherit the 2.8in default and this is meaningless.
labelHalfW = 0.0;
if (hasAttr(n, "width")) {
sscanf (n.width, "%lf", &labelHalfW);
labelHalfW = (labelHalfW * 72.0) / 2.0;
}
// neato ignores a cluster's margin attribute completely (proven:
// identical bb at margin 50/100/200/300) and does not support
// native cluster labels, so there is no way to ask for room at
// the bottom up front. gvpr runs after layout though, so grow the
// box downwards here and drop the label into the space created.
// Every labelled cluster gets the same padding, parents included,
// so a padded child cannot push through its parent's border.
labelPad = 95.0;
clusterMinY = clusterMinY - labelPad;
// A box holding one icon is narrower than its own label, so the
// text would spill over its neighbour. labelHalfW comes from the
// laid-out label node, so this is the real width, not an estimate.
if (clusterMaxX - clusterMinX < (labelHalfW * 2.0) + 24.0) {
clusterMaxX = clusterMinX + (labelHalfW * 2.0) + 24.0;
}
Gr.bb = sprintf("%.1f,%.1f,%.1f,%.1f",
clusterMinX, clusterMinY, clusterMaxX, clusterMaxY);
if (n._labelposition == "bottom-left") {
labelY = clusterMinY + (labelPad / 2.0);
if (hasAttr(n, "_clustertype") && n._clustertype == "AZUREGroup") {
labelX = clusterMinX + labelHalfW + 20.0;
} else if (hasAttr(n, "_clustertype") && n._clustertype == "SubnetGroup") {
labelX = clusterMinX + labelHalfW + 12.0;
} else {
labelX = clusterMinX + labelHalfW + 20.0;
}
} else if (n._labelposition == "bottom-right") {
labelX = clusterMaxX - labelHalfW - 20.0;
labelY = clusterMinY + (labelPad / 2.0);
} else if (n._labelposition == "bottom-center") {
labelX = clusterCenterX;
labelY = clusterMinY + (labelPad / 2.0);
}
// Pin the node position
n.pos = sprintf("%.1f,%.1f!", labelX, labelY);
}
}
}
}
// Handle top positioning for AWS clusters (original logic)
if (hasAttr(Gr, "_shift") && Gr._shift!="" && Gr._shift!="0"){
fudgeX=2.; // aim for just inside periphery - increase to shift left
fudgeY=1.; // aim for just inside periphery - increase to shift up
if (index(Gr._shift,",")>0){
split(Gr._shift,fld,",");
fudgeX=(double)fld[0];
fudgeY=(double)fld[1];
}
sscanf (Gr.bb, "%lf,%lf,%lf,%lf", &bbMinX, &bbMinY, &bbMaxX, &bbMaxY);
lpX=xOf(Gr.lp);
lpY=yOf(Gr.lp);
lpMinX=lpX-((Gr.lwidth*72.)/2.);
lpMaxY=lpY+((Gr.lheight*72.)/2.);
//print(" // bb: ", Gr.bb, " lpMinX: ", lpMinX, " lpMaxY: ", lpMaxY);
Gr._oldlp=Gr.lp;
newX=lpX-(lpMinX-bbMinX)-fudgeX;
newY=lpY+(bbMaxY-lpMaxY)+fudgeY;
Gr.lp=sprintf("%.1f,%.1f", newX, newY);
}
}
return Gr;
} // end of labelShift
}
BEG_G{
double nodeY;
topMostY = 0;
topEdgeY = 0;
bottomMostY = 999999.0;
rootG = $G;
labelShift($G);
// Center edge nodes (CDN, API GW, etc.) among their targets so ortho edges
// fan out in multiple directions instead of all routing in parallel.
// Only nodes tagged with _edgenode="1" by drawing.py are candidates.
edge_t e;
node_t src, tgt;
int fanoutCount;
double sumX, avgX, srcX, srcY;
int centeredCount;
centeredCount = 0;
for (src = fstnode($G); src; src = nxtnode(src)) {
if (!hasAttr(src, "_edgenode") || src._edgenode != "1") continue;
fanoutCount = 0;
sumX = 0.0;
for (e = fstout(src); e; e = nxtout(e)) {
tgt = e.head;
if (hasAttr(tgt, "_titlenode") && tgt._titlenode == "1") continue;
if (hasAttr(tgt, "_footernode") && tgt._footernode == "1") continue;
if (hasAttr(tgt, "_legendnode") && tgt._legendnode == "1") continue;
if (hasAttr(tgt, "_clusterlabel") && tgt._clusterlabel == "1") continue;
sumX = sumX + xOf(tgt.pos);
fanoutCount = fanoutCount + 1;
}
if (fanoutCount > 3) {
avgX = sumX / (double)fanoutCount;
srcX = xOf(src.pos);
srcY = yOf(src.pos);
// Only center if significantly off-center from targets (>1000pt ~14in)
double xDist = srcX - avgX;
if (xDist < 0) xDist = -xDist;
if (xDist > 1000.0) {
// Clamp avgX within cloud group bounds
if (cloudMaxX > 0) {
if (avgX < cloudMinX + 50.0) avgX = cloudMinX + 50.0;
if (avgX > cloudMaxX - 50.0) avgX = cloudMaxX - 50.0;
}
// Position just inside cloud group top — well above VPC/sub-clusters
src.pos = sprintf("%.1f,%.1f!", avgX, cloudMaxY - 60.0);
// Clear edge positions so neato -n2 re-routes them from the new location
edge_t oe;
for (oe = fstout(src); oe; oe = nxtout(oe)) {
oe.pos = "";
}
for (oe = fstin(src); oe; oe = nxtin(oe)) {
oe.pos = "";
}
centeredCount = centeredCount + 1;
}
}
}
// Pin NSG shield badges to the top-right corner of their node card.
// Graphviz places xlabels wherever they happen to fit, which leaves them
// floating off the icon; xlp is the xlabel's position and can be moved.
// Unlike the cluster label nodes, `width`/`height` are meaningful here:
// these nodes are fixedsize, so the inherited default IS the real size.
double badgeW, badgeH, shiftX;
for (n = fstnode($G); n; n = nxtnode(n)) {
if (!hasAttr(n, "_badgenode") || n._badgenode != "1") continue;
if (!hasAttr(n, "xlp")) continue;
badgeW = 2.8; badgeH = 3.8;
if (hasAttr(n, "width")) sscanf (n.width, "%lf", &badgeW);
if (hasAttr(n, "height")) sscanf (n.height, "%lf", &badgeH);
// xlp pins the badge's CENTRE. The badge is [shield][name], so shift right
// by half its width minus half the shield to sit the shield itself on the
// corner and let the name run outwards, instead of the whole badge
// straddling the corner and covering the icon.
shiftX = 0.0;
if (hasAttr(n, "_badgewidth")) {
sscanf (n._badgewidth, "%lf", &shiftX);
shiftX = (shiftX / 2.0) - 32.0;
}
n.xlp = sprintf("%.1f,%.1f",
xOf(n.pos) + (badgeW * 72.0) / 2.0 + shiftX,
yOf(n.pos) + (badgeH * 72.0) / 2.0 - 30.0);
}
// Find topmost and bottommost node positions (including outer nodes).
// Also note whether a legend node exists so the footer can be centred
// when it has to stand alone.
for (n = fstnode($G); n; n = nxtnode(n)) {
if (hasAttr(n, "_titlenode") && n._titlenode == "1") continue;
if (hasAttr(n, "_footernode") && n._footernode == "1") continue;
if (hasAttr(n, "_legendnode") && n._legendnode == "1") { hasLegend = 1; continue; }
nodeY = yOf(n.pos);
if (nodeY > topMostY) topMostY = nodeY;
if (nodeY < bottomMostY) bottomMostY = nodeY;
// Track the topmost node EDGE too: node.pos is the centre, and a tall
// card (Azure nodes are ~5.2in) extends ~190pt above it, which is what
// the title must clear.
nodeTop = nodeY + (atof(n.height) * 72.0) / 2.0;
if (nodeTop > topEdgeY) topEdgeY = nodeTop;
}
// Use higher of cloudMaxY or topMostY
if (cloudMaxY > topMostY) topMostY = cloudMaxY;
if (cloudMaxY > topEdgeY) topEdgeY = cloudMaxY;
// Use lower of cloudMinY or bottomMostY
if (cloudMinY < bottomMostY) bottomMostY = cloudMinY;
}
END_G{
double gbbMinX, gbbMinY, gbbMaxX, gbbMaxY, gcenterX, glpY;
// Center root graph label at the END after all processing
sscanf ($G.bb, "%lf,%lf,%lf,%lf", &gbbMinX, &gbbMinY, &gbbMaxX, &gbbMaxY);
gcenterX = (gbbMinX + gbbMaxX) / 2.0;
glpY = yOf($G.lp);
$G.lp = sprintf("%.1f,%.1f", gcenterX, glpY);
}
N[_titlenode=="1"]{
// topEdgeY is the top EDGE of the tallest-placed node (not its centre),
// so a fixed clearance works for every provider's card height. The title
// node's own text centres on pos, so allow half its height (~40pt at
// fontsize 56) plus breathing room.
pos=sprintf("%.0f,%.0f!", cloudCenterX, topEdgeY + 120);
}
// Footer + optional legend sit on the same Y below the cloud.
// If a legend exists, footer shifts left and legend goes right so the
// two occupy a single row. If no legend, footer stays centred.
// Vertical offset of 400 matches the old legend-below-footer position
// so the outer cluster border keeps its usual breathing room.
// Footer width=18in → half-width ~648 pts; ±750 keeps the pair clear
// of each other either side of cloudCenterX.
N[_footernode=="1"]{
// -400 in both branches: a provider's corner branding (the Azure logo is
// a ~560pt image hung below the cloud border) reaches ~280pt under
// bottomMostY, and the footer's own half-height is ~90pt, so anything
// shallower overlaps the logo on narrow diagrams.
if (hasLegend) {
pos=sprintf("%.0f,%.0f!", cloudCenterX - 750, bottomMostY - 400);
} else {
pos=sprintf("%.0f,%.0f!", cloudCenterX, bottomMostY - 400);
}
}
N[_legendnode=="1"]{
pos=sprintf("%.0f,%.0f!", cloudCenterX + 750, bottomMostY - 400);
}