blob: 0b4d130814fd434fd07aa0db2015a7304f819977 [file] [log] [blame]
Thomas Vachuskab51b8bc2015-07-27 08:37:12 -07001/*
Brian O'Connor5ab426f2016-04-09 01:19:45 -07002 * Copyright 2015-present Open Networking Laboratory
Thomas Vachuskab51b8bc2015-07-27 08:37:12 -07003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package org.onlab.stc;
17
18import com.google.common.collect.Lists;
19import com.google.common.collect.Maps;
20
21import java.util.List;
22import java.util.Map;
23import java.util.Set;
24import java.util.stream.Collectors;
25import java.util.stream.IntStream;
26
27/**
28 * Computes scenario process flow layout for the Monitor GUI.
29 */
30public class MonitorLayout {
31
32 public static final int WIDTH = 210;
33 public static final int HEIGHT = 30;
34 public static final int W_GAP = 40;
35 public static final int H_GAP = 50;
36 public static final int SLOT_WIDTH = WIDTH + H_GAP;
37
38 private final Compiler compiler;
39 private final ProcessFlow flow;
40
41 private Map<Step, Box> boxes = Maps.newHashMap();
42
43 /**
44 * Creates a new shared process flow monitor.
45 *
46 * @param compiler scenario compiler
47 */
48 MonitorLayout(Compiler compiler) {
49 this.compiler = compiler;
50 this.flow = compiler.processFlow();
51
52 // Extract the flow and create initial bounding boxes.
53 boxes.put(null, new Box(null, 0));
54 flow.getVertexes().forEach(this::createBox);
55
56 computeLayout(null, 0, 1);
57 }
58
59 // Computes the graph layout giving preference to group associations.
60 private void computeLayout(Group group, int absoluteTier, int tier) {
61 Box box = boxes.get(group);
62
63 // Find all children of the group, or items with no group if at top.
64 Set<Step> children = group != null ? group.children() :
65 flow.getVertexes().stream().filter(s -> s.group() == null)
66 .collect(Collectors.toSet());
67
68 children.forEach(s -> visit(s, absoluteTier, 1, group));
69
70 // Figure out what the group root vertexes are.
71 Set<Step> roots = findRoots(group);
72
73 // Compute the boxes for each of the roots.
74 roots.forEach(s -> updateBox(s, absoluteTier + 1, 1, group));
75
76 // Update the tier and depth of the group bounding box.
77 computeTiersAndDepth(group, box, absoluteTier, tier, children);
78
79 // Compute the minimum breadth of this group's bounding box.
80 computeBreadth(group, box, children);
81
82 // Compute child placements
83 computeChildPlacements(group, box, children);
84 }
85
86 // Updates the box for the specified step, given the tier number, which
87 // is relative to the parent.
88 private Box updateBox(Step step, int absoluteTier, int tier, Group group) {
89 Box box = boxes.get(step);
90 if (step instanceof Group) {
91 computeLayout((Group) step, absoluteTier, tier);
92 } else {
93 box.setTierAndDepth(absoluteTier, tier, 1, group);
94 }
95
96 // Follow the steps downstream of this one.
97 follow(step, absoluteTier + box.depth(), box.tier() + box.depth());
98 return box;
99 }
100
101 // Backwards follows edges leading towards the specified step to visit
102 // the source vertex and compute layout of those vertices that had
103 // sufficient number of visits to compute their tier.
104 private void follow(Step step, int absoluteTier, int tier) {
105 Group from = step.group();
106 flow.getEdgesTo(step).stream()
107 .filter(d -> visit(d.src(), absoluteTier, tier, from))
108 .forEach(d -> updateBox(d.src(), absoluteTier, tier, from));
109 }
110
111 // Visits each step, records maximum tier and returns true if this
112 // was the last expected visit.
113 private boolean visit(Step step, int absoluteTier, int tier, Group from) {
114 Box box = boxes.get(step);
115 return box.visitAndLatchMaxTier(absoluteTier, tier, from);
116 }
117
118 // Computes the absolute and relative tiers and the depth of the group
119 // bounding box.
120 private void computeTiersAndDepth(Group group, Box box,
121 int absoluteTier, int tier, Set<Step> children) {
122 int depth = children.stream().mapToInt(this::bottomMostTier).max().getAsInt();
123 box.setTierAndDepth(absoluteTier, tier, depth, group);
124 }
125
126 // Returns the bottom-most tier this step occupies relative to its parent.
127 private int bottomMostTier(Step step) {
128 Box box = boxes.get(step);
129 return box.tier() + box.depth();
130 }
131
132 // Computes breadth of the specified group.
133 private void computeBreadth(Group group, Box box, Set<Step> children) {
134 if (box.breadth() == 0) {
135 // Scan through all tiers and determine the maximum breadth of each.
136 IntStream.range(1, box.depth)
137 .forEach(t -> computeTierBreadth(t, box, children));
138 box.latchBreadth(children.stream()
139 .mapToInt(s -> boxes.get(s).breadth())
140 .max().getAsInt());
141 }
142 }
143
144 // Computes tier width.
145 private void computeTierBreadth(int t, Box box, Set<Step> children) {
146 box.latchBreadth(children.stream().map(boxes::get)
147 .filter(b -> isSpanningTier(b, t))
148 .mapToInt(Box::breadth).sum());
149 }
150
151 // Computes the actual child box placements relative to the parent using
152 // the previously established tier, depth and breadth attributes.
153 private void computeChildPlacements(Group group, Box box,
154 Set<Step> children) {
155 // Order the root-nodes in alphanumeric order first.
156 List<Box> tierBoxes = Lists.newArrayList(boxesOnTier(1, children));
157 tierBoxes.sort((a, b) -> a.step().name().compareTo(b.step().name()));
158
159 // Place the boxes centered on the parent box; left to right.
160 int tierBreadth = tierBoxes.stream().mapToInt(Box::breadth).sum();
161 int slot = 1;
162 for (Box b : tierBoxes) {
163 b.updateCenter(1, slot(slot, tierBreadth));
164 slot += b.breadth();
165 }
166 }
167
168 // Returns the horizontal offset off the parent center.
169 private int slot(int slot, int tierBreadth) {
170 boolean even = tierBreadth % 2 == 0;
171 int multiplier = -tierBreadth / 2 + slot - 1;
172 return even ? multiplier * SLOT_WIDTH + SLOT_WIDTH / 2 : multiplier * SLOT_WIDTH;
173 }
174
175 // Returns a list of all child step boxes that start on the specified tier.
176 private List<Box> boxesOnTier(int tier, Set<Step> children) {
177 return boxes.values().stream()
178 .filter(b -> b.tier() == tier && children.contains(b.step()))
179 .collect(Collectors.toList());
180 }
181
182 // Determines whether the specified box spans, or occupies a tier.
183 private boolean isSpanningTier(Box b, int tier) {
184 return (b.depth() == 1 && b.tier() == tier) ||
185 (b.tier() <= tier && tier < b.tier() + b.depth());
186 }
187
188
189 // Determines roots of the specified group or of the entire graph.
190 private Set<Step> findRoots(Group group) {
191 Set<Step> steps = group != null ? group.children() : flow.getVertexes();
192 return steps.stream().filter(s -> isRoot(s, group)).collect(Collectors.toSet());
193 }
194
195 private boolean isRoot(Step step, Group group) {
196 if (step.group() != group) {
197 return false;
198 }
199
200 Set<Dependency> requirements = flow.getEdgesFrom(step);
201 return requirements.stream().filter(r -> r.dst().group() == group)
202 .collect(Collectors.toSet()).isEmpty();
203 }
204
205 /**
206 * Returns the bounding box for the specified step. If null is given, it
207 * returns the overall bounding box.
208 *
209 * @param step step or group; null for the overall bounding box
210 * @return bounding box
211 */
212 public Box get(Step step) {
213 return boxes.get(step);
214 }
215
216 /**
217 * Returns the bounding box for the specified step name. If null is given,
218 * it returns the overall bounding box.
219 *
220 * @param name name of step or group; null for the overall bounding box
221 * @return bounding box
222 */
223 public Box get(String name) {
224 return get(name == null ? null : compiler.getStep(name));
225 }
226
227 // Creates a bounding box for the specified step or group.
228 private void createBox(Step step) {
229 boxes.put(step, new Box(step, flow.getEdgesFrom(step).size()));
230 }
231
232 /**
233 * Bounding box data for a step or group.
234 */
235 final class Box {
236
237 private Step step;
238 private int remainingRequirements;
239
240 private int absoluteTier = 0;
241 private int tier;
242 private int depth = 1;
243 private int breadth;
244 private int center, top;
245
246 private Box(Step step, int remainingRequirements) {
247 this.step = step;
248 this.remainingRequirements = remainingRequirements + 1;
249 breadth = step == null || step instanceof Group ? 0 : 1;
250 }
251
252 private void latchTiers(int absoluteTier, int tier, Group from) {
253 this.absoluteTier = Math.max(this.absoluteTier, absoluteTier);
254 if (step == null || step.group() == from) {
255 this.tier = Math.max(this.tier, tier);
256 }
257 }
258
259 public void latchBreadth(int breadth) {
260 this.breadth = Math.max(this.breadth, breadth);
261 }
262
263 void setTierAndDepth(int absoluteTier, int tier, int depth, Group from) {
264 latchTiers(absoluteTier, tier, from);
265 this.depth = depth;
266 }
267
268 boolean visitAndLatchMaxTier(int absoluteTier, int tier, Group from) {
269 latchTiers(absoluteTier, tier, from);
270 --remainingRequirements;
271 return remainingRequirements == 0;
272 }
273
274 Step step() {
275 return step;
276 }
277
278 public int absoluteTier() {
279 return absoluteTier;
280 }
281
282 int tier() {
283 return tier;
284 }
285
286 int depth() {
287 return depth;
288 }
289
290 int breadth() {
291 return breadth;
292 }
293
294 int top() {
295 return top;
296 }
297
298 int center() {
299 return center;
300 }
301
302 public void updateCenter(int top, int center) {
303 this.top = top;
304 this.center = center;
305 }
306 }
307}