com.net2plan.gui.utils.topologyPane.jung.JUNGCanvas.java Source code

Java tutorial

Introduction

Here is the source code for com.net2plan.gui.utils.topologyPane.jung.JUNGCanvas.java

Source

/*******************************************************************************
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser Public License v2.1
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/lgpl.html
 * <p>
 * Contributors:
 * Pablo Pavon Mario - initial API and implementation
 ******************************************************************************/

package com.net2plan.gui.utils.topologyPane.jung;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JComponent;

import org.apache.commons.collections15.Transformer;
import org.apache.commons.collections15.functors.ConstantTransformer;

import com.net2plan.gui.utils.IVisualizationCallback;
import com.net2plan.gui.utils.topologyPane.GUILink;
import com.net2plan.gui.utils.topologyPane.GUINode;
import com.net2plan.gui.utils.topologyPane.ITopologyCanvasPlugin;
import com.net2plan.gui.utils.topologyPane.TopologyPanel;
import com.net2plan.gui.utils.topologyPane.visualizationControl.VisualizationConstants;
import com.net2plan.gui.utils.topologyPane.jung.osmSupport.state.OSMStateManager;
import com.net2plan.interfaces.networkDesign.Configuration;
import com.net2plan.interfaces.networkDesign.Node;
import com.net2plan.internal.CommandLineParser;
import com.net2plan.internal.plugins.ITopologyCanvas;
import com.net2plan.utils.Triple;

import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.algorithms.layout.StaticLayout;
import edu.uci.ics.jung.graph.DirectedOrderedSparseMultigraph;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.util.Context;
import edu.uci.ics.jung.graph.util.EdgeIndexFunction;
import edu.uci.ics.jung.visualization.Layer;
import edu.uci.ics.jung.visualization.RenderContext;
import edu.uci.ics.jung.visualization.VisualizationServer;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import edu.uci.ics.jung.visualization.control.GraphMousePlugin;
import edu.uci.ics.jung.visualization.control.LayoutScalingControl;
import edu.uci.ics.jung.visualization.control.PluggableGraphMouse;
import edu.uci.ics.jung.visualization.control.ScalingControl;
import edu.uci.ics.jung.visualization.control.ScalingGraphMousePlugin;
import edu.uci.ics.jung.visualization.decorators.ConstantDirectionalEdgeValueTransformer;
import edu.uci.ics.jung.visualization.decorators.EdgeShape;
import edu.uci.ics.jung.visualization.renderers.BasicEdgeLabelRenderer;
import edu.uci.ics.jung.visualization.renderers.BasicVertexLabelRenderer;
import edu.uci.ics.jung.visualization.renderers.DefaultEdgeLabelRenderer;
import edu.uci.ics.jung.visualization.renderers.Renderer;
import edu.uci.ics.jung.visualization.transform.MutableTransformer;
import edu.uci.ics.jung.visualization.transform.shape.GraphicsDecorator;
import edu.uci.ics.jung.visualization.util.ArrowFactory;

/**
 * Topology canvas using JUNG library [<a href='#jung'>JUNG</a>].
 *
 * @see <a name='jung'></a><a href='http://jung.sourceforge.net/'>Java Universal Network/Graph Framework (JUNG) website</a>
 */
@SuppressWarnings("unchecked")
public final class JUNGCanvas implements ITopologyCanvas {
    private final IVisualizationCallback callback;

    private double currentInterLayerDistanceInNpCoordinates;

    private final Graph<GUINode, GUILink> g;
    private final Layout<GUINode, GUILink> l;
    private final VisualizationViewer<GUINode, GUILink> vv;
    private final PluggableGraphMouse gm;
    private final ScalingControl scalingControl;
    private final Transformer<GUINode, Point2D> transformNetPlanCoordinatesToJungCoordinates;
    private final Transformer<Context<Graph<GUINode, GUILink>, GUILink>, Shape> originalEdgeShapeTransformer;
    private VisualizationServer.Paintable paintableAssociatedToBackgroundImage;

    private final OSMStateManager osmStateManager;

    /**
     * Default constructor.
     *
     * @since 0.2.3
     */
    public JUNGCanvas(IVisualizationCallback callback, TopologyPanel topologyPanel) {
        this.callback = callback;

        transformNetPlanCoordinatesToJungCoordinates = vertex -> {
            final int vlIndex = this.callback.getVisualizationState()
                    .getCanvasVisualizationOrderRemovingNonVisible(vertex.getLayer());
            final double interLayerDistanceInNpCoord = currentInterLayerDistanceInNpCoordinates;
            final Point2D basePositionInNetPlanCoord = vertex.getAssociatedNetPlanNode().getXYPositionMap();
            return new Point2D.Double(basePositionInNetPlanCoord.getX(),
                    -(basePositionInNetPlanCoord.getY() + (vlIndex * interLayerDistanceInNpCoord)));
        };

        g = new DirectedOrderedSparseMultigraph<>();
        l = new StaticLayout<>(g, transformNetPlanCoordinatesToJungCoordinates);
        vv = new VisualizationViewer<>(l);

        osmStateManager = new OSMStateManager(callback, topologyPanel, this);

        originalEdgeShapeTransformer = new EdgeShape.QuadCurve<>();
        ((EdgeShape.QuadCurve<GUINode, GUILink>) originalEdgeShapeTransformer).setControlOffsetIncrement(10); // how much they separate from the direct line (default is 20)
        //((EdgeShape.QuadCurve<GUINode, GUILink>) originalEdgeShapeTransformer).setEdgeIndexFunction(DefaultParallelEdgeIndexFunction.<GUINode, GUILink>getInstance()); // how much they separate from the direct line (default is 20)
        /* This functions gives an index to the links to show separate (curved): the order among the parallel links (BUT NOW only among the separated ones among them) */
        ((EdgeShape.QuadCurve<GUINode, GUILink>) originalEdgeShapeTransformer)
                .setEdgeIndexFunction(new EdgeIndexFunction<GUINode, GUILink>() {
                    public void reset(Graph<GUINode, GUILink> graph, GUILink e) {
                    }

                    public void reset() {
                    }

                    public int getIndex(Graph<GUINode, GUILink> graph, GUILink e) {
                        final GUINode u = e.getOriginNode();
                        final GUINode v = e.getDestinationNode();
                        final HashSet<GUILink> commonEdgeSet = new HashSet<>(graph.getInEdges(v));
                        commonEdgeSet.retainAll(graph.getOutEdges(u));
                        commonEdgeSet.removeIf(ee -> !ee.isShownSeparated());
                        int count = 0;
                        for (GUILink other : commonEdgeSet)
                            if (other == e)
                                return count;
                            else
                                count++;
                        throw new RuntimeException();
                    }
                });
        /* Customize the graph */
        vv.getRenderContext().setVertexDrawPaintTransformer(n -> n.getDrawPaint());
        vv.getRenderContext().setVertexFillPaintTransformer(n -> n.getFillPaint());
        vv.getRenderContext().setVertexFontTransformer(n -> n.getFont());

        vv.getRenderContext().setVertexIconTransformer(gn -> gn.getIcon());

        vv.getRenderContext().setVertexIncludePredicate(
                guiNodeContext -> callback.getVisualizationState().isVisibleInCanvas(guiNodeContext.element));
        vv.getRenderer().setVertexLabelRenderer(new NodeLabelRenderer());
        vv.setVertexToolTipTransformer(node -> node.getToolTip());

        vv.getRenderContext().setEdgeIncludePredicate(
                context -> callback.getVisualizationState().isVisibleInCanvas(context.element));
        vv.getRenderContext().setEdgeArrowPredicate(
                context -> callback.getVisualizationState().isVisibleInCanvas(context.element)
                        && context.element.getHasArrow());
        vv.getRenderContext().setEdgeArrowStrokeTransformer(i -> i.getArrowStroke());
        vv.getRenderContext()
                .setEdgeArrowTransformer(new ConstantTransformer(ArrowFactory.getNotchedArrow(7, 10, 5)));
        vv.getRenderContext().setEdgeLabelClosenessTransformer(new ConstantDirectionalEdgeValueTransformer(.6, .6));
        vv.getRenderContext().setEdgeStrokeTransformer(i -> i.getEdgeStroke());

        vv.getRenderContext().setEdgeDrawPaintTransformer(e -> e.getEdgeDrawPaint());
        vv.getRenderContext().setArrowDrawPaintTransformer(e -> e.getArrowDrawPaint());
        vv.getRenderContext().setArrowFillPaintTransformer(e -> e.getArrowFillPaint());

        vv.getRenderContext().setEdgeLabelRenderer(new DefaultEdgeLabelRenderer(Color.BLUE));
        vv.getRenderer().setEdgeLabelRenderer(new BasicEdgeLabelRenderer<GUINode, GUILink>() {
            public void labelEdge(RenderContext<GUINode, GUILink> rc, Layout<GUINode, GUILink> layout, GUILink e,
                    String label) {
                if (callback.getVisualizationState().isCanvasShowLinkLabels())
                    super.labelEdge(rc, layout, e, e.getLabel());
            }
        });
        vv.setEdgeToolTipTransformer(link -> link.getToolTip());
        vv.getRenderContext().setEdgeShapeTransformer(
                c -> c.element.isShownSeparated() ? originalEdgeShapeTransformer.transform(c)
                        : new Line2D.Float(0.0f, 0.0f, 1.0f, 0.0f));

        // Background controller
        this.paintableAssociatedToBackgroundImage = null;

        gm = new PluggableGraphMouse();
        vv.setGraphMouse(gm);

        scalingControl = new LayoutScalingControl();
        ITopologyCanvasPlugin scalingPlugin = new ScalingCanvasPlugin(scalingControl, MouseEvent.NOBUTTON);
        addPlugin(scalingPlugin);

        vv.setOpaque(false);
        vv.setBackground(new Color(0, 0, 0, 0));

        this.updateInterLayerDistanceInNpCoordinates(callback.getVisualizationState().getInterLayerSpaceInPixels());

        //        reset();
    }

    @Override
    public void addPlugin(ITopologyCanvasPlugin plugin) {
        gm.add(new GraphMousePluginAdapter(plugin));
    }

    @Override
    public void removePlugin(ITopologyCanvasPlugin plugin) {
        if (plugin instanceof GraphMousePlugin)
            gm.remove((GraphMousePlugin) plugin);
    }

    /**
     * Converts a point from the SWING coordinates system into a point from the JUNG coordinates system.
     *
     * @param jungLayoutCoord (@code Point2D) on the SWING canvas.
     * @return (@code Point2D) on the JUNG canvas.
     */
    @Override
    public Point2D getCanvasPointFromNetPlanPoint(Point2D npCoord) {
        Point2D layoutOrViewCoordinates = vv.getRenderContext().getMultiLayerTransformer()
                .inverseTransform(Layer.LAYOUT, npCoord);
        layoutOrViewCoordinates.setLocation(layoutOrViewCoordinates.getX(), -layoutOrViewCoordinates.getY());

        return layoutOrViewCoordinates;
    }

    public void resetTransformer() {
        vv.getRenderContext().getMultiLayerTransformer().setToIdentity();
    }

    @Override
    public Point2D getCanvasPointFromScreenPoint(Point2D screenPoint) {
        return vv.getRenderContext().getMultiLayerTransformer().inverseTransform(Layer.LAYOUT, screenPoint);
    }

    public Rectangle getCurrentCanvasViewWindow() {
        return vv.getRenderContext().getMultiLayerTransformer().inverseTransform(vv.getBounds()).getBounds();
    }

    @Override
    public String getDescription() {
        return "";
    }

    @Override
    public JComponent getCanvasComponent() {
        return vv;
    }

    @Override
    public String getName() {
        return "JUNG Canvas";
    }

    @Override
    public GUILink getEdge(MouseEvent e) {
        final VisualizationViewer<GUINode, GUILink> vv = (VisualizationViewer<GUINode, GUILink>) e.getSource();
        GraphElementAccessor<GUINode, GUILink> pickSupport = vv.getPickSupport();
        if (pickSupport != null) {
            final Point p = e.getPoint();
            return pickSupport.getEdge(vv.getModel().getGraphLayout(), p.getX(), p.getY());
        }

        return null;
    }

    @Override
    public GUINode getVertex(MouseEvent e) {
        final VisualizationViewer<GUINode, GUILink> vv = (VisualizationViewer<GUINode, GUILink>) e.getSource();
        GraphElementAccessor<GUINode, GUILink> pickSupport = vv.getPickSupport();
        if (pickSupport != null) {
            final Point p = e.getPoint();
            final GUINode vertex = pickSupport.getVertex(vv.getModel().getGraphLayout(), p.getX(), p.getY());
            if (vertex != null)
                return vertex;
        }

        return null;
    }

    @Override
    public Set<GUINode> getAllVertices() {
        return Collections.unmodifiableSet(new HashSet<>(g.getVertices()));
    }

    @Override
    public Set<GUILink> getAllEdges() {
        return Collections.unmodifiableSet(new HashSet<>(g.getEdges()));
    }

    public Transformer<GUINode, Point2D> getTransformer() {
        return transformNetPlanCoordinatesToJungCoordinates;
    }

    public Layout<GUINode, GUILink> getLayout() {
        return l;
    }

    @Override
    public List<Triple<String, String, String>> getParameters() {
        return null;
    }

    @Override
    public void refresh() {
        vv.repaint();
    }

    @Override
    public void resetPickedStateAndRefresh() {
        vv.getPickedVertexState().clear();
        vv.getPickedEdgeState().clear();
        refresh();
    }

    @Override
    public void rebuildCanvasGraphAndRefresh() {
        for (GUILink gl : new ArrayList<>(g.getEdges()))
            g.removeEdge(gl);
        for (GUINode gn : new ArrayList<>(g.getVertices()))
            g.removeVertex(gn);
        for (GUINode gn : callback.getVisualizationState().getCanvasAllGUINodes())
            g.addVertex(gn);
        for (GUILink gl : callback.getVisualizationState().getCanvasAllGUILinks(true, true))
            g.addEdge(gl, gl.getOriginNode(), gl.getDestinationNode());

        updateAllVerticesXYPosition();
        refresh();
    }

    @Override
    public void zoomAll() {
        osmStateManager.zoomAll();
    }

    @Override
    public void updateAllVerticesXYPosition() {
        osmStateManager.updateNodesXYPosition();
    }

    @Override
    public void moveVertexToXYPosition(GUINode npNode, Point2D point) {
        l.setLocation(npNode, point);
    }

    @Override
    public Point2D getCanvasPointFromMovement(final Point2D point) {
        return osmStateManager.getCanvasCoordinateFromScreenPoint(point);
    }

    @Override
    public void panTo(Point2D initialPoint, Point2D destinationPoint) {
        osmStateManager.panTo(initialPoint, destinationPoint);
    }

    @Override
    public void addNode(Point2D position) {
        osmStateManager.addNode(position);
    }

    @Override
    public void removeNode(Node node) {
        osmStateManager.removeNode(node);
    }

    @Override
    public void runOSMSupport() {
        osmStateManager.setRunningState();
    }

    @Override
    public void stopOSMSupport() {
        osmStateManager.setStoppedState();
    }

    @Override
    public boolean isOSMRunning() {
        return osmStateManager.isMapActivated();
    }

    @Override
    public void moveCanvasTo(Point2D destinationPoint) {
        final MutableTransformer layoutTransformer = vv.getRenderContext().getMultiLayerTransformer()
                .getTransformer(Layer.LAYOUT);
        layoutTransformer.translate(destinationPoint.getX(), destinationPoint.getY());
    }

    @Override
    public void zoomIn() {
        osmStateManager.zoomIn();
    }

    @Override
    public void zoomOut() {
        osmStateManager.zoomOut();
    }

    @Override
    public void zoom(Point2D centerPoint, float scale) {
        scalingControl.scale(vv, scale, centerPoint);
    }

    @Override
    public double getCurrentCanvasScale() {
        return vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).getScale();
    }

    @Override
    public Point2D getCanvasCenter() {
        return vv.getCenter();
    }

    public void setBackgroundImage(final File bgFile, final double x, final double y) {
        final Double x1 = x;
        final Double y1 = y;

        setBackgroundImage(bgFile, x1.intValue(), y1.intValue());
    }

    public void setBackgroundImage(final File bgFile, final int x, final int y) {
        final ImageIcon background = new ImageIcon(bgFile.getAbsolutePath());
        updateBackgroundImage(background, x, y);
    }

    public void setBackgroundImage(final ImageIcon image, final int x, final int y) {
        updateBackgroundImage(image, x, y);
    }

    public void updateBackgroundImage(final ImageIcon icon) {
        updateBackgroundImage(icon, 0, 0);
    }

    public void updateBackgroundImage(final ImageIcon icon, final int x, final int y) {
        if (paintableAssociatedToBackgroundImage != null)
            vv.removePreRenderPaintable(paintableAssociatedToBackgroundImage);
        paintableAssociatedToBackgroundImage = null;
        if (icon != null) {
            this.paintableAssociatedToBackgroundImage = new VisualizationViewer.Paintable() {
                public void paint(Graphics g) {
                    Graphics2D g2d = (Graphics2D) g;
                    AffineTransform oldXform = g2d.getTransform();
                    AffineTransform lat = vv.getRenderContext().getMultiLayerTransformer()
                            .getTransformer(Layer.LAYOUT).getTransform();
                    AffineTransform vat = vv.getRenderContext().getMultiLayerTransformer()
                            .getTransformer(Layer.VIEW).getTransform();
                    AffineTransform at = new AffineTransform();
                    at.concatenate(g2d.getTransform());
                    at.concatenate(vat);
                    at.concatenate(lat);
                    g2d.setTransform(at);
                    g.drawImage(icon.getImage(), x, y, icon.getIconWidth(), icon.getIconHeight(), vv);
                    g2d.setTransform(oldXform);
                }

                public boolean useTransform() {
                    return false;
                }
            };
            vv.addPreRenderPaintable(paintableAssociatedToBackgroundImage);
        }
    }

    @Override
    public final Map<String, String> getCurrentOptions() {
        return CommandLineParser.getParameters(getParameters(), Configuration.getOptions());
    }

    @Override
    public int getPriority() {
        return 0;
    }

    @Override
    public void takeSnapshot() {
        osmStateManager.takeSnapshot();
    }

    private class NodeLabelRenderer extends BasicVertexLabelRenderer<GUINode, GUILink> {
        @Override
        public void labelVertex(RenderContext<GUINode, GUILink> rc, Layout<GUINode, GUILink> layout, GUINode v,
                String label) {
            if (!callback.getVisualizationState().isVisibleInCanvas(v))
                return;
            if (callback.getVisualizationState().isCanvasShowNodeNames() && v.getLayer().isDefaultLayer()) {
                Point2D vertexPositionInPixels = layout.transform(v);
                vertexPositionInPixels = rc.getMultiLayerTransformer().transform(Layer.LAYOUT,
                        vertexPositionInPixels);
                final Component component = prepareRenderer(rc, rc.getVertexLabelRenderer(),
                        "<html><font color='black'>" + v.getLabel() + "</font></html>",
                        rc.getPickedVertexState().isPicked(v), v);
                final GraphicsDecorator g = rc.getGraphicsContext();
                final Dimension dimensionMessage = component.getPreferredSize();
                final Icon vertexIcon = v.getIcon();
                final Rectangle2D boundsVertex = new Rectangle2D.Double(
                        vertexPositionInPixels.getX() - vertexIcon.getIconWidth() / 2,
                        vertexPositionInPixels.getY() - vertexIcon.getIconHeight() / 2, vertexIcon.getIconWidth(),
                        vertexIcon.getIconHeight());
                final Point anchorPointInPixels = getAnchorPoint(boundsVertex, dimensionMessage,
                        Renderer.VertexLabel.Position.NE);
                g.draw(component, rc.getRendererPane(), anchorPointInPixels.x, anchorPointInPixels.y,
                        dimensionMessage.width, dimensionMessage.height, true);
            }
        }

        @Override
        protected Point getAnchorPoint(Rectangle2D vertexBounds, Dimension labelSize,
                Renderer.VertexLabel.Position position) {
            double x;
            double y;
            int offset = 0;
            switch (position) {
            case NE:
                x = vertexBounds.getMaxX() - offset;
                y = vertexBounds.getMinY() + offset - labelSize.height;
                return new Point((int) x, (int) y);
            case CNTR:
                x = vertexBounds.getCenterX() - ((double) labelSize.width / 2);
                y = vertexBounds.getCenterY() - ((double) labelSize.height / 2);
                return new Point((int) x, (int) y);

            default:
                return new Point();
            }

        }
    }

    private class ScalingCanvasPlugin extends ScalingGraphMousePlugin implements ITopologyCanvasPlugin {
        public ScalingCanvasPlugin(ScalingControl scaler, int modifiers) {
            super(scaler, modifiers, VisualizationConstants.SCALE_OUT, VisualizationConstants.SCALE_IN);
            setZoomAtMouse(false);
        }

        @Override
        public void mouseWheelMoved(MouseWheelEvent e) {
            boolean accepted = this.checkModifiers(e);
            if (accepted) {
                VisualizationViewer vv = (VisualizationViewer) e.getSource();
                int amount = e.getWheelRotation();
                if (this.zoomAtMouse) {
                    if (amount > 0) {
                        osmStateManager.zoomOut();
                    } else if (amount < 0) {
                        osmStateManager.zoomIn();
                    }
                } else if (amount > 0) {
                    osmStateManager.zoomOut();
                } else if (amount < 0) {
                    osmStateManager.zoomIn();
                }

                e.consume();
                vv.repaint();
            }

        }
    }

    @Override
    public void updateInterLayerDistanceInNpCoordinates(int interLayerDistanceInPixels) {
        this.currentInterLayerDistanceInNpCoordinates = osmStateManager
                .getCanvasInterlayerDistance(interLayerDistanceInPixels);
    }

    @Override
    public double getInterLayerDistanceInNpCoordinates() {
        return currentInterLayerDistanceInNpCoordinates;
    }
}