1   /****************************************************************************
2    * This demo file is part of yFiles for Java 2.14.
3    * Copyright (c) 2000-2017 by yWorks GmbH, Vor dem Kreuzberg 28,
4    * 72070 Tuebingen, Germany. All rights reserved.
5    * 
6    * yFiles demo files exhibit yFiles for Java functionalities. Any redistribution
7    * of demo files in source code or binary form, with or without
8    * modification, is not permitted.
9    * 
10   * Owners of a valid software license for a yFiles for Java version that this
11   * demo is shipped with are allowed to use the demo source code as basis
12   * for their own yFiles for Java powered applications. Use of such programs is
13   * governed by the rights and conditions as set out in the yFiles for Java
14   * license agreement.
15   * 
16   * THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED
17   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18   * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
19   * NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
21   * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22   * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23   * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24   * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26   *
27   ***************************************************************************/
28  package demo.view.networkmonitoring;
29  
30  import y.anim.AnimationFactory;
31  import y.anim.AnimationObject;
32  import y.anim.AnimationPlayer;
33  import y.anim.CompositeAnimationObject;
34  import y.base.DataMap;
35  import y.base.DataProvider;
36  import y.base.Edge;
37  import y.base.EdgeCursor;
38  import y.base.Node;
39  import y.base.NodeCursor;
40  import y.layout.labeling.SALabeling;
41  import y.util.Maps;
42  import y.view.Drawable;
43  import y.view.EdgeLabel;
44  import y.view.EdgeRealizer;
45  import y.view.GenericEdgeRealizer;
46  import y.view.GenericNodeRealizer;
47  import y.view.Graph2D;
48  import y.view.Graph2DLayoutExecutor;
49  import y.view.Graph2DView;
50  import y.view.LineType;
51  import y.view.NodeLabel;
52  import y.view.NodeRealizer;
53  import y.view.ViewAnimationFactory;
54  
55  import java.awt.Graphics2D;
56  import java.awt.Rectangle;
57  import java.awt.geom.GeneralPath;
58  import java.awt.geom.Point2D;
59  import java.awt.geom.Rectangle2D;
60  import java.beans.PropertyChangeEvent;
61  import java.beans.PropertyChangeListener;
62  import java.util.HashSet;
63  
64  /**
65   * The network view creates a view graph and updates it every time the state of elements in the model graph changes.
66   */
67  class NetworkView implements NetworkModelObserver {
68    private static final TrafficMarker TRAFFIC_MARKER = new TrafficMarker();
69  
70    private final NetworkModel model;
71    private final Graph2DView view;
72    private final DataMap view2model;
73    private final HashSet brokenElements;
74    private final AnimationPlayer zoomDependentPlayer;
75  
76    public NetworkView(NetworkModel model, Graph2DView view, DataMap view2model) {
77      this.model = model;
78      this.view = view;
79      this.view2model = view2model;
80  
81      final Graph2D modelGraph = model.getNetworkModel();
82      final Graph2D viewGraph = view.getGraph2D();
83      final DataProvider nodeTypes = modelGraph.getDataProvider(NetworkModel.NODE_TYPE_DPKEY);
84      final DataProvider elementIds = modelGraph.getDataProvider(NetworkModel.ELEMENT_ID_DPKEY);
85      final DataProvider edgeCapacities = modelGraph.getDataProvider(NetworkModel.ELEMENT_CAPACITY_DPKEY);
86      final DataProvider nodeInfos = modelGraph.getDataProvider(NetworkModel.NODE_INFO_DPKEY);
87  
88      brokenElements = new HashSet(modelGraph.nodeCount() + modelGraph.edgeCount());
89  
90      zoomDependentPlayer = createZoomDependentPlayer(view);
91  
92      // create view nodes that have different configurations for different network node types
93      final DataMap model2view = Maps.createHashedDataMap();
94      for (NodeCursor nc = modelGraph.nodes(); nc.ok(); nc.next()) {
95        final Node modelNode = nc.node();
96        final Node viewNode = viewGraph.createNode();
97        if (nodeTypes != null && nodeTypes.get(modelNode) != null) {
98          NodeRealizer realizer;
99          switch (nodeTypes.getInt(modelNode)) {
100           case NetworkModel.PC:
101             realizer = NetworkMonitoringFactory.createWorkstation();
102             break;
103           case NetworkModel.LAPTOP:
104             realizer = NetworkMonitoringFactory.createLaptop();
105             break;
106           case NetworkModel.SMARTPHONE:
107             realizer = NetworkMonitoringFactory.createSmartphone();
108             break;
109           case NetworkModel.SWITCH:
110             realizer = NetworkMonitoringFactory.createSwitch();
111             break;
112           case NetworkModel.WLAN:
113             realizer = NetworkMonitoringFactory.createWLan();
114             break;
115           case NetworkModel.DATABASE:
116             realizer = NetworkMonitoringFactory.createDatabase();
117             break;
118           case NetworkModel.SERVER:
119             realizer = NetworkMonitoringFactory.createServer();
120             break;
121           default:
122             realizer = new GenericNodeRealizer();
123         }
124         final NodeLabel infoLabel = realizer.getLabel();
125         if (infoLabel != null) {
126           final NetworkNodeInfo nodeInfo = (NetworkNodeInfo) nodeInfos.get(modelNode);
127           infoLabel.setText(nodeInfo.getName() + "\n" + nodeInfo.getIpAddress());
128 
129           // make a collection of info labels visible to show that info labels exist
130           if (viewNode.index() % 9 == 0) {
131             infoLabel.setVisible(true);
132           }
133         }
134         realizer.setCenter(modelGraph.getCenterX(modelNode), modelGraph.getCenterY(modelNode));
135         viewGraph.setRealizer(viewNode, realizer);
136       }
137       if (elementIds != null && elementIds.get(modelNode) != null) {
138         final Object id = elementIds.get(modelNode);
139         view2model.set(viewNode, id);
140         model2view.set(id, viewNode);
141       }
142     }
143 
144     // determine highest capacity in graph to be able to divide them into three groups with different line types
145     int maxCapacity = 0;
146     if (edgeCapacities != null) {
147       for (EdgeCursor ec = modelGraph.edges(); ec.ok(); ec.next()) {
148         final Edge edge = ec.edge();
149         final int capacity = edgeCapacities.getInt(edge);
150         if (capacity > maxCapacity) {
151           maxCapacity = capacity;
152         }
153       }
154     }
155     // create view edges with line thickness modeling capacity.
156     for (EdgeCursor ec = modelGraph.edges(); ec.ok(); ec.next()) {
157       final Edge modelEdge = ec.edge();
158       if (elementIds != null) {
159         final Edge viewEdge = viewGraph.createEdge((Node) model2view.get(elementIds.get(modelEdge.source())),
160             (Node) model2view.get(elementIds.get(modelEdge.target())));
161         final EdgeRealizer realizer = NetworkMonitoringFactory.createConnection();
162         viewGraph.setRealizer(viewEdge, realizer);
163         if (edgeCapacities != null) {
164           final int capacity = edgeCapacities.getInt(modelEdge);
165           if (capacity < maxCapacity * 0.33) {
166             realizer.setLineType(LineType.LINE_3);
167           } else if (capacity < maxCapacity * 0.66) {
168             realizer.setLineType(LineType.LINE_5);
169           } else {
170             realizer.setLineType(LineType.LINE_7);
171           }
172         }
173         final Object id = elementIds.get(modelEdge);
174         view2model.set(viewEdge, id);
175         model2view.set(id, viewEdge);
176       }
177     }
178 
179     // position info labels
180     final SALabeling labeling = new SALabeling();
181     labeling.setPlaceNodeLabels(true);
182     labeling.setPlaceEdgeLabels(false);
183     labeling.setRemoveNodeOverlaps(true);
184     labeling.setDeterministicModeEnabled(true);
185     new Graph2DLayoutExecutor().doLayout(viewGraph, labeling);
186   }
187 
188   /**
189    * Creates an {@link AnimationPlayer} that will be stopped if the view changes to sloppy mode.
190    */
191   private AnimationPlayer createZoomDependentPlayer(Graph2DView view) {
192     final ViewAnimationFactory factory = new ViewAnimationFactory(view);
193     final AnimationPlayer player = factory.createConfiguredPlayer();
194 
195     view.getCanvasComponent().addPropertyChangeListener(new PropertyChangeListener() {
196       public void propertyChange(PropertyChangeEvent evt) {
197         if ("Zoom".equals(evt.getPropertyName())) {
198           final double zoom = ((Double) evt.getNewValue()).doubleValue();
199           if (zoom <= NetworkMonitoringDemo.PAINT_DETAIL_THRESHOLD
200               && player.isPlaying()) {
201             player.stop();
202           }
203         }
204       }
205     });
206 
207     return player;
208   }
209 
210   /**
211    * Updates the view graph with new information of the given {@link DataMap}.
212    */
213   public void update(DataMap dataMap) {
214     final Graph2D viewGraph = view.getGraph2D();
215     Rectangle2D focusRect = null;
216     for (NodeCursor nodeCursor = viewGraph.nodes(); nodeCursor.ok(); nodeCursor.next()) {
217       final Node viewNode = nodeCursor.node();
218       if (dataMap.get(view2model.get(viewNode)) != null) {
219         final double workload = ((Double) dataMap.get(view2model.get(viewNode))).doubleValue();
220         final NodeRealizer realizer = viewGraph.getRealizer(viewNode);
221         if (realizer instanceof GenericNodeRealizer) {
222           final GenericNodeRealizer gnr = (GenericNodeRealizer) realizer;
223           final NetworkData networkData = (NetworkData) gnr.getUserData();
224           networkData.setWorkload(workload);
225           if (networkData.isBroken() && !brokenElements.contains(viewNode)) {
226             brokenElements.add(viewNode);
227             // network node just broke
228             // => update area (node) that gets focused if it is not contained in the current view
229             if (focusRect == null) {
230               focusRect = new Rectangle2D.Double(0, 0, -1, -1);
231             }
232             focusRect.setFrame(realizer.getX(), realizer.getY(), realizer.getWidth(), realizer.getHeight());
233           } else if (!networkData.isBroken() && brokenElements.contains(viewNode)) {
234             brokenElements.remove(viewNode);
235           }
236         }
237       }
238     }
239     for (EdgeCursor ec = viewGraph.edges(); ec.ok(); ec.next()) {
240       final Edge viewEdge = ec.edge();
241       final EdgeRealizer realizer = viewGraph.getRealizer(viewEdge);
242       if (realizer instanceof GenericEdgeRealizer) {
243         final GenericEdgeRealizer ger = (GenericEdgeRealizer) realizer;
244         final NetworkData networkData = (NetworkData) ger.getUserData();
245         if (dataMap.get(view2model.get(viewEdge)) != null) {
246           final double workload = ((Double) dataMap.get(view2model.get(viewEdge))).doubleValue();
247           networkData.setWorkload(workload);
248           final EdgeLabel errorLabel = realizer.getLabel();
249           if (networkData.isBroken() && !brokenElements.contains(viewEdge)) {
250             brokenElements.add(viewEdge);
251             // connection just broke
252             // => update area (edge + source and target) that gets focused if it is not contained in the current view
253             if (focusRect == null) {
254               focusRect = new Rectangle2D.Double(0, 0, -1, -1);
255             }
256             final Node source = viewEdge.source();
257             final Node target = viewEdge.target();
258             final double sourceX = viewGraph.getX(source);
259             final double targetX = viewGraph.getX(target);
260             final double sourceY = viewGraph.getY(source);
261             final double targetY = viewGraph.getY(target);
262             final double minX = Math.min(sourceX, targetX);
263             final double minY = Math.min(sourceY, targetY);
264             final double maxX = Math.max(sourceX + viewGraph.getWidth(source), targetX + viewGraph.getWidth(target));
265             final double maxY = Math.max(sourceY + viewGraph.getHeight(source), targetY + viewGraph.getHeight(target));
266             focusRect.setFrameFromDiagonal(minX, minY, maxX, maxY);
267             errorLabel.setVisible(true);
268           } else if (!networkData.isBroken() && brokenElements.contains(viewEdge)) {
269             brokenElements.remove(viewEdge);
270             errorLabel.setVisible(false);
271           }
272         }
273       }
274     }
275 
276     // animate changes
277     final int stepDuration = model.getUpdateCycle();
278 
279     // change view port only if there just broke a network element
280     if (focusRect != null && !view.getVisibleRect().contains(focusRect)
281         && (focusRect.getCenterX() != view.getCenter().getX() || focusRect.getCenterY() != view.getCenter().getY())) {
282       final AnimationPlayer player = new ViewAnimationFactory(view).createConfiguredPlayer();
283       player.animate(createFocusViewAnimation(focusRect, stepDuration));
284     }
285     if (view.getZoom() > NetworkMonitoringDemo.PAINT_DETAIL_THRESHOLD) {
286       zoomDependentPlayer.animate(createConnectionAnimation(stepDuration, dataMap));
287     } else {
288       view.updateView();
289     }
290   }
291 
292   /**
293    * Creates an animation that focuses the given focus rect if it lies (partly) outside the current view port.
294    */
295   private AnimationObject createFocusViewAnimation(Rectangle2D focusRect, int duration) {
296     // in case a network element outside the current view just broke, focus view on it
297     final Point2D newCenter = new Point2D.Double(focusRect.getCenterX(), focusRect.getCenterY());
298 
299     final int newZoom = 1;
300     final double intermediateZoom = Math.max(NetworkMonitoringDemo.MIN_ZOOM, Math.min(view.getZoom(), newZoom) - 0.4);
301     return AnimationFactory.createEasedAnimation(
302         new FocusViewAnimation(newCenter, newZoom, intermediateZoom, duration));
303   }
304 
305   /**
306    * Creates an animation that visualizes data activity on network connections.
307    */
308   private AnimationObject createConnectionAnimation(int duration, DataMap dataMap) {
309     final Graph2D graph = view.getGraph2D();
310     final ViewAnimationFactory factory = new ViewAnimationFactory(view);
311     final CompositeAnimationObject concurrency = AnimationFactory.createConcurrency();
312 
313     for (EdgeCursor ec = graph.edges(); ec.ok(); ec.next()) {
314       final Edge edge = ec.edge();
315       final EdgeRealizer realizer = graph.getRealizer(edge);
316       final NetworkData networkData = NetworkMonitoringFactory.getNetworkData(realizer);
317       if (dataMap.get(view2model.get(edge)) != null && networkData != null && networkData.getWorkload() > 0) {
318         final GeneralPath path = realizer.getPath();
319         concurrency.addAnimation(factory.traversePath(path, false, TRAFFIC_MARKER, duration));
320         concurrency.addAnimation(factory.traversePath(path, true, TRAFFIC_MARKER, duration));
321       }
322     }
323 
324     return concurrency;
325   }
326 
327   /**
328    * Marker that can be moved along an edge to visualize data activity.
329    */
330   private static class TrafficMarker implements Drawable {
331     private static final int WIDTH = 6;
332     private static final int HEIGHT = 6;
333 
334     public void paint(final Graphics2D g) {
335       final Rectangle bounds = getBounds();
336       g.drawOval(0, 0, bounds.width, bounds.height);
337     }
338 
339     public Rectangle getBounds() {
340       return new Rectangle(0, 0, WIDTH, HEIGHT);
341     }
342   }
343 
344   /**
345    * {@link y.anim.AnimationObject Animation} that moves the view's current center to a new center while zooming to a
346    * new zoom level passing an intermediate zoom level.
347    */
348   private class FocusViewAnimation implements AnimationObject {
349     private final Point2D newCenter;
350     private final double intermediateZoom;
351     private final double newZoom;
352     private final long preferredDuration;
353     private Point2D oldCenter;
354     private double oldZoom;
355 
356     public FocusViewAnimation(Point2D newCenter, double newZoom, double intermediateZoom, long preferredDuration) {
357       this.newCenter = newCenter;
358       this.newZoom = newZoom;
359       this.intermediateZoom = intermediateZoom;
360       this.preferredDuration = preferredDuration;
361     }
362 
363     public void initAnimation() {
364       oldCenter = view.getCenter();
365       oldZoom = view.getZoom();
366     }
367 
368     public void calcFrame(double time) {
369       // move center from old center to new center
370       view.setCenter(
371           (newCenter.getX() - oldCenter.getX()) * time + oldCenter.getX(),
372           (newCenter.getY() - oldCenter.getY()) * time + oldCenter.getY());
373 
374       // Bezier interpolation for a smooth zoom change
375       view.setZoom((1 - time) * (1 - time) * oldZoom + 2 * time * (1 - time) * intermediateZoom + time * time * newZoom);
376     }
377 
378     public void disposeAnimation() {
379     }
380 
381     public long preferredDuration() {
382       return preferredDuration;
383     }
384   }
385 }
386