org.eclipse.titanium.graph.visualization.GraphHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.titanium.graph.visualization.GraphHandler.java

Source

/******************************************************************************
 * Copyright (c) 2000-2017 Ericsson Telecom AB
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 ******************************************************************************/
package org.eclipse.titanium.graph.visualization;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Set;

import javax.imageio.ImageIO;

import org.apache.commons.collections15.Transformer;
import org.eclipse.titan.common.logging.ErrorReporter;
import org.eclipse.titanium.error.GUIErrorHandler;
import org.eclipse.titanium.graph.components.EdgeDescriptor;
import org.eclipse.titanium.graph.components.EdgeStroke;
import org.eclipse.titanium.graph.components.NodeDescriptor;
import org.eclipse.titanium.graph.gui.common.CustomSatelliteViewer;
import org.eclipse.titanium.graph.gui.common.CustomVisualizationViewer;
import org.eclipse.titanium.graph.gui.common.Layouts;
import org.eclipse.titanium.graph.gui.menus.NodePopupMenu;
import org.eclipse.titanium.graph.gui.utils.LayoutEntry;
import org.eclipse.titanium.graph.utils.GraphVizWriter;
import org.eclipse.titanium.metrics.IMetricEnum;

import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.graph.DirectedSparseGraph;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.io.PajekNetWriter;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
import edu.uci.ics.jung.visualization.renderers.Renderer.VertexLabel.Position;

/**
 * This class provides tools for jung graph visualization
 * 
 * @author Gabor Jenei
 */
public class GraphHandler {
    public enum ImageExportType {
        /**
         * Select this mode to export the whole graph, in this case the
         * image file maybe bigger than the set screen size
         */
        EXPORT_WHOLE_GRAPH,
        /** Select this mode to export only the seen part of the graph */
        EXPORT_SEEN_GRAPH,
        /** Select this mode to export the Satellite View */
        EXPORT_SATELLITE
    }

    private DirectedSparseGraph<NodeDescriptor, EdgeDescriptor> g;
    private static final Transformer<NodeDescriptor, String> NODE_LABELER = new ToStringLabeller<NodeDescriptor>();

    private CustomVisualizationViewer actVisualisator;
    private Layout<NodeDescriptor, EdgeDescriptor> layout;
    private GraphRenderer<NodeDescriptor, EdgeDescriptor> renderer;
    protected NodePopupMenu popupMenu;
    protected IMetricEnum chosenLayoutMetric = null;
    private CustomSatelliteViewer satView;
    private Set<Set<NodeDescriptor>> clusters;

    /**
     * This method creates an empty GraphHandler, it can be used to oversee the
     * graph drawing mechanism, like Layout changes, storing graph on the disk,
     * colorize and etc.
     * 
     * @see The public methods of {@link GraphHandler}
     */
    public GraphHandler() {
        popupMenu = new NodePopupMenu(this);
    }

    /**
     * This function changes the layout for the graph set in the
     * {@link GraphHandler} class
     * 
     * @param newLayout
     *            : The chosen layout's code
     * @param newWindowSize
     *            : The size of the parent window where to draw (or any
     *            resolution bigger than this)
     * @throws BadLayoutException On wrong layout code or bad graph
     */
    public void changeLayout(final LayoutEntry newLayout, final Dimension newWindowSize) throws BadLayoutException {
        if (g == null) {
            throw new BadLayoutException("You must draw a graph before!", ErrorType.NO_OBJECT);
        }
        Dimension extSize = null;

        if (g.getVertexCount() >= 20) {
            extSize = new Dimension(newWindowSize.height * (g.getVertexCount() / 20),
                    newWindowSize.width * (g.getVertexCount() / 20));
        } else {
            extSize = newWindowSize;
        }

        layout = new LayoutBuilder(g, newLayout, extSize).clusters(clusters).build();

        actVisualisator = new CustomVisualizationViewer(layout, popupMenu);
        actVisualisator.setPreferredSize(new Dimension(newWindowSize.width, newWindowSize.height));
        actVisualisator.getRenderContext().setVertexLabelTransformer(NODE_LABELER);

        final GraphRenderer<NodeDescriptor, EdgeDescriptor> rnd = new GraphRenderer<NodeDescriptor, EdgeDescriptor>(
                NODE_LABELER, actVisualisator.getPickedVertexState(), actVisualisator.getPickedEdgeState());
        setNodeRenderer(rnd, actVisualisator);
        renderer = rnd;
        actVisualisator.getRenderer().getVertexLabelRenderer().setPosition(Position.CNTR);
        actVisualisator.setBackground(Color.white);
        actVisualisator.setDoubleBuffered(false);

        initSatView();
    }

    /**
     * This method makes displayable components from a memory stored graph.
     * 
     * @param g
     *            : A Jung graph to draw
     * @param NODE_LABELER
     *            : A node name <-> node label translator
     * @param windowSize
     *            : The size of the parent window (maybe more)
     * @param layout
     *            : The layout to use for the drawing
     * @return A {@link Component} containing the graph parameter graph drawn.
     * @throws Exception
     */
    public void drawGraph(final DirectedSparseGraph<NodeDescriptor, EdgeDescriptor> g, final Dimension windowSize,
            final LayoutEntry layout) throws BadLayoutException {
        if (g == null) {
            throw new BadLayoutException("There is no graph (it is set null)", ErrorType.NO_OBJECT);
        }
        if (g.getVertexCount() == 0) {
            throw new BadLayoutException("The graph doesn't contain any node", ErrorType.EMPTY_GRAPH);
        }
        this.g = g;
        changeLayout(layout, windowSize);
    }

    /**
     * Saves a Jung graph to a Pajek .net file
     * 
     * @param g
     *            : The graph to save
     * @param path
     *            : The save path
     * @throws BadLayoutException
     *             on file handling error and own inner problems
     */
    public static void saveGraphToPajek(final Graph<NodeDescriptor, EdgeDescriptor> g, final String path)
            throws BadLayoutException {
        if (g == null) {
            throw new BadLayoutException("You must draw a graph before!", ErrorType.NO_OBJECT);
        }

        final Transformer<EdgeDescriptor, Number> edgeWeights = new Transformer<EdgeDescriptor, Number>() {
            @Override
            public Number transform(final EdgeDescriptor e) {
                return e.getWeight();
            }
        };

        try {
            final PajekNetWriter<NodeDescriptor, EdgeDescriptor> writer = new PajekNetWriter<NodeDescriptor, EdgeDescriptor>();
            writer.save(g, path, NODE_LABELER, edgeWeights);
        } catch (IOException e) {
            throw new BadLayoutException("An error occured during writing to the output file.", ErrorType.IO_ERROR,
                    e);
        }
    }

    /**
     * Saves a Jung graph to a .dot file
     * 
     * @param g
     *            : The graph to save
     * @param path
     *            : The save path
     * @param graphName
     *            : The graph name to use in the output file
     * @throws Exception
     *             on file handling error
     */
    public static void saveGraphToDot(final Graph<NodeDescriptor, EdgeDescriptor> g, final String path,
            final String graphName) throws BadLayoutException {

        if (g == null) {
            throw new BadLayoutException("You must draw a graph before!", ErrorType.NO_OBJECT);
        }

        try {
            new GraphVizWriter<NodeDescriptor, EdgeDescriptor>().save(g, path, NODE_LABELER, graphName);
        } catch (IOException e) {
            throw new BadLayoutException("An error occured during writing to the output file!", ErrorType.IO_ERROR,
                    e);
        }
    }

    /**
     * Exports the graph set for this class to a PNG file
     * 
     * @param path
     *            : The PNG file's path
     * @param mode
     *            : The way of export, see {@link GraphHandler}
     *            <code>public static</code> fields for possible values (EXPORT_
     *            named fields)
     * @param size
     *            : This parameter sets the size of the exported image in pixels
     * @throws Exception
     *             on file handling error
     */
    public void saveToImage(final String path, final ImageExportType mode) throws BadLayoutException {
        if (layout == null || actVisualisator == null) {
            throw new BadLayoutException("Either the layout or the visuaizer is not set (is null)",
                    ErrorType.NO_OBJECT);
        }

        VisualizationViewer<NodeDescriptor, EdgeDescriptor> tempVisualisator = null;
        Dimension size = null;
        switch (mode) {
        case EXPORT_SEEN_GRAPH: {
            tempVisualisator = actVisualisator;
            size = actVisualisator.getPreferredSize();
        }
            break;
        case EXPORT_WHOLE_GRAPH: {
            layout = actVisualisator.getGraphLayout();
            if (size == null) {
                size = new Dimension(layout.getSize().width, layout.getSize().height);
            }

            final Transformer<NodeDescriptor, Point2D> trf = new Transformer<NodeDescriptor, Point2D>() {
                @Override
                public Point2D transform(final NodeDescriptor v) {
                    return layout.transform(v);
                }
            };

            tempVisualisator = new VisualizationViewer<NodeDescriptor, EdgeDescriptor>(
                    new LayoutBuilder(g, Layouts.LAYOUT_STATIC, size).transformer(trf).build());
            tempVisualisator.setPreferredSize(size);
            tempVisualisator.setSize(size);
            tempVisualisator.getRenderContext().setVertexLabelTransformer(NODE_LABELER);

            final GraphRenderer<NodeDescriptor, EdgeDescriptor> rnd = new GraphRenderer<NodeDescriptor, EdgeDescriptor>(
                    NODE_LABELER, tempVisualisator.getPickedVertexState(), tempVisualisator.getPickedEdgeState());
            setNodeRenderer(rnd, tempVisualisator);
            tempVisualisator.getRenderer().getVertexLabelRenderer().setPosition(Position.CNTR);
            tempVisualisator.setBackground(Color.white);
            tempVisualisator.setDoubleBuffered(false);
        }
            break;
        case EXPORT_SATELLITE: {
            tempVisualisator = satView;
            size = tempVisualisator.getSize();
        }
            break;
        default:
            ErrorReporter.logError("Unexpected image export type " + mode);
            return;
        }

        BufferedImage image;
        final GUIErrorHandler errorHandler = new GUIErrorHandler();
        try {
            image = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_RGB);
        } catch (OutOfMemoryError e) {
            final long needed = (long) size.width * (long) size.height * 4;
            String temp;
            if (needed < 1024) {
                temp = needed + " bytes";
            } else if (needed < 1024 * 1024) {
                temp = needed / 1024 + " Kbytes";
            } else {
                temp = needed / 1024 / 1024 + " Mbytes";
            }
            final String errorText = "Could not save an image of " + size.width + "*" + size.height
                    + " size as there was not enough free memory (" + temp + ")";
            errorHandler.reportErrorMessage(errorText);
            ErrorReporter.logExceptionStackTrace(errorText, e);
            return;
        }
        final Graphics2D g2 = image.createGraphics();
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        tempVisualisator.paint(g2);
        g2.dispose();
        try {
            ImageIO.write(image, "png", new File(path));
        } catch (IOException e) {
            final String message = "Error while writing to file" + path;
            ErrorReporter.logExceptionStackTrace(message, e);
            errorHandler.reportException(message, e);
        }
    }

    /**
     * This function can set a custom shape, colour etc. for the graph nodes and
     * edges on a given {@link VisualizationViewer}
     * 
     * @param rnd
     *            - the shape, colour etc. describing class's instance
     * @param visualisator
     *            - the visualisator to change
     * @see GraphRenderer
     */
    public static void setNodeRenderer(final GraphRenderer<NodeDescriptor, EdgeDescriptor> rnd,
            final VisualizationViewer<NodeDescriptor, EdgeDescriptor> visualisator) {
        if (visualisator == null || rnd == null) {
            return;
        }
        visualisator.getRenderContext().setVertexShapeTransformer(rnd.getShape());
        visualisator.getRenderContext().setVertexFillPaintTransformer(rnd.getVertexColour());
        visualisator.getRenderContext().setVertexLabelRenderer(rnd.getFont());

        visualisator.getRenderContext().setEdgeDrawPaintTransformer(rnd.getEdgeColour());
        visualisator.getRenderContext().setArrowFillPaintTransformer(rnd.getEdgeColour());
        visualisator.getRenderContext().setArrowDrawPaintTransformer(rnd.getEdgeColour());

        final EdgeStroke<EdgeDescriptor> stroke = new EdgeStroke<EdgeDescriptor>();
        visualisator.getRenderContext().setEdgeStrokeTransformer(stroke);
    }

    /**
     * Changes the visualizers' size
     * 
     * @param newSize
     *            : the new window size to set
     */
    public void changeWindowSize(final Dimension newSize) {
        if (actVisualisator != null) {
            actVisualisator.setPreferredSize(new Dimension(newSize.width, newSize.height));
        }
    }

    /**
     * @return A {@link Component} that contains the whole graph in a small view
     */
    public CustomSatelliteViewer getSatelliteViewer() {
        return satView;
    }

    private void initSatView() {
        if (actVisualisator == null) {
            return;
        }
        satView = new CustomSatelliteViewer(actVisualisator);
        satView.getRenderContext().setVertexLabelTransformer(NODE_LABELER);
        satView.getRenderer().getVertexLabelRenderer().setPosition(Position.CNTR);
        satView.getRenderContext().setVertexShapeTransformer(renderer.getShape());
        satView.getRenderContext().setVertexFillPaintTransformer(renderer.getVertexColour());
        satView.scaleToLayout(new CrossoverScalingControl());
    }

    /**
     * @return A {@link Component} that contains a graph without scrollbars
     */
    public CustomVisualizationViewer getVisualizator() {
        return actVisualisator;
    }

    /**
     * This function does zooming on the graph (both satellite and main views).
     * 
     * @param scale
     *            : The zooming multiplier (zoom out if <1 and zoom in if >1, no
     *            change in case of 1)
     */
    public void zoom(final float scale) {
        if (actVisualisator == null) {
            return;
        }
        final CrossoverScalingControl control = new CrossoverScalingControl();
        control.scale(actVisualisator, scale, actVisualisator.getCenter());
    }

    /**
     * This method sets a clustering for the stored graph. This modification
     * will not be automatically shown on the screen.
     * 
     * @param clusters
     */
    public void setClusters(final Set<Set<NodeDescriptor>> clusters) {
        this.clusters = clusters;
    }

    /**
     * Sets node menu entries that are not used in clustering mode
     * 
     * @param value
     *            : True if the menu entries should be enabled
     */
    public void setMenusEnabled(final boolean value) {
        if (popupMenu == null) {
            return;
        }
        popupMenu.enableGoToDefinition(value);
    }

}