edu.uci.ics.jung.samples.PerspectiveVertexImageShaperDemo.java Source code

Java tutorial

Introduction

Here is the source code for edu.uci.ics.jung.samples.PerspectiveVertexImageShaperDemo.java

Source

/*
 * Copyright (c) 2003, the JUNG Project and the Regents of the University of
 * California All rights reserved.
 * 
 * This software is open-source under the BSD license; see either "license.txt"
 * or http://jung.sourceforge.net/license.txt for a description.
 * 
 */
package edu.uci.ics.jung.samples;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.HashMap;
import java.util.Map;

import javax.media.jai.PerspectiveTransform;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JApplet;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JSlider;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import org.apache.commons.collections15.Transformer;

import edu.uci.ics.jung.algorithms.layout.FRLayout;
import edu.uci.ics.jung.graph.DirectedSparseMultigraph;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.util.EdgeType;
import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
import edu.uci.ics.jung.visualization.LayeredIcon;
import edu.uci.ics.jung.visualization.VisualizationServer;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
import edu.uci.ics.jung.visualization.control.ScalingControl;
import edu.uci.ics.jung.visualization.decorators.DefaultVertexIconTransformer;
import edu.uci.ics.jung.visualization.decorators.EllipseVertexShapeTransformer;
import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
import edu.uci.ics.jung.visualization.decorators.VertexIconShapeTransformer;
import edu.uci.ics.jung.visualization.jai.PerspectiveImageLensSupport;
import edu.uci.ics.jung.visualization.jai.PerspectiveLayoutTransformSupport;
import edu.uci.ics.jung.visualization.jai.PerspectiveTransformSupport;
import edu.uci.ics.jung.visualization.picking.PickedState;
import edu.uci.ics.jung.visualization.renderers.Checkmark;

/**
 * Demonstrates the use of images to represent graph vertices.
 * The images are added to the DefaultGraphLabelRenderer and can
 * either be offset from the vertex, or centered on the vertex.
 * Additionally, the relative positioning of the label and
 * image is controlled by subclassing the DefaultGraphLabelRenderer
 * and setting the appropriate properties on its JLabel superclass
 *  FancyGraphLabelRenderer
 * 
 * The images used in this demo (courtesy of slashdot.org) are
 * rectangular but with a transparent background. When vertices
 * are represented by these images, it looks better if the actual
 * shape of the opaque part of the image is computed so that the
 * edge arrowheads follow the visual shape of the image. This demo
 * uses the FourPassImageShaper class to compute the Shape from
 * an image with transparent background.
 * 
 * @author Tom Nelson
 * 
 */
public class PerspectiveVertexImageShaperDemo extends JApplet {

    /**
    * 
    */
    private static final long serialVersionUID = -1943329822820885759L;

    /**
      * the graph
      */
    Graph<Number, Number> graph;

    /**
     * the visual component and renderer for the graph
     */
    VisualizationViewer<Number, Number> vv;

    /**
     * some icon names to use
     */
    String[] iconNames = { "apple", "os", "x", "linux", "inputdevices", "wireless", "graphics3", "gamespcgames",
            "humor", "music", "privacy" };

    PerspectiveTransformSupport viewSupport;
    PerspectiveTransformSupport layoutSupport;

    /**
     * create an instance of a simple graph with controls to
     * demo the zoom features.
     * 
     */
    @SuppressWarnings("serial")
    public PerspectiveVertexImageShaperDemo() {

        // create a simple graph for the demo
        graph = new DirectedSparseMultigraph<Number, Number>();
        Number[] vertices = createVertices(11);

        // a Map for the labels
        Map<Number, String> map = new HashMap<Number, String>();
        for (int i = 0; i < vertices.length; i++) {
            map.put(vertices[i], iconNames[i % iconNames.length]);
        }

        // a Map for the Icons
        Map<Number, Icon> iconMap = new HashMap<Number, Icon>();
        for (int i = 0; i < vertices.length; i++) {
            String name = "/images/topic" + iconNames[i] + ".gif";
            try {
                Icon icon = new LayeredIcon(
                        new ImageIcon(PerspectiveVertexImageShaperDemo.class.getResource(name)).getImage());
                iconMap.put(vertices[i], icon);
            } catch (Exception ex) {
                System.err.println("You need slashdoticons.jar in your classpath to see the image " + name);
            }
        }

        createEdges(vertices);

        final VertexStringerImpl<Number> vertexStringerImpl = new VertexStringerImpl<Number>(map);

        final VertexIconShapeTransformer<Number> vertexImageShapeFunction = new VertexIconShapeTransformer<Number>(
                new EllipseVertexShapeTransformer<Number>());

        FRLayout<Number, Number> layout = new FRLayout<Number, Number>(graph);
        layout.setMaxIterations(100);
        vv = new VisualizationViewer<Number, Number>(layout, new Dimension(400, 400));

        vv.setBackground(Color.decode("0xffffdd"));
        final DefaultVertexIconTransformer<Number> vertexIconFunction = new DefaultVertexIconTransformer<Number>();
        vertexImageShapeFunction.setIconMap(iconMap);
        vertexIconFunction.setIconMap(iconMap);
        vv.getRenderContext().setVertexShapeTransformer(vertexImageShapeFunction);
        vv.getRenderContext().setVertexIconTransformer(vertexIconFunction);
        vv.getRenderContext().setVertexLabelTransformer(vertexStringerImpl);
        PickedState<Number> ps = vv.getPickedVertexState();
        ps.addItemListener(new PickWithIconListener(vertexIconFunction));

        vv.addPostRenderPaintable(new VisualizationServer.Paintable() {
            int x;
            int y;
            Font font;
            FontMetrics metrics;
            int swidth;
            int sheight;
            String str = "Thank You, slashdot.org, for the images!";

            public void paint(Graphics g) {
                Dimension d = vv.getSize();
                if (font == null) {
                    font = new Font(g.getFont().getName(), Font.BOLD, 20);
                    metrics = g.getFontMetrics(font);
                    swidth = metrics.stringWidth(str);
                    sheight = metrics.getMaxAscent() + metrics.getMaxDescent();
                    x = (d.width - swidth) / 2;
                    y = (int) (d.height - sheight * 1.5);
                }
                g.setFont(font);
                Color oldColor = g.getColor();
                g.setColor(Color.lightGray);
                g.drawString(str, x, y);
                g.setColor(oldColor);
            }

            public boolean useTransform() {
                return false;
            }
        });

        // add a listener for ToolTips
        vv.setVertexToolTipTransformer(new ToStringLabeller<Number>());

        Container content = getContentPane();
        final GraphZoomScrollPane panel = new GraphZoomScrollPane(vv);
        content.add(panel);

        final DefaultModalGraphMouse<Number, Number> graphMouse = new DefaultModalGraphMouse<Number, Number>();

        vv.setGraphMouse(graphMouse);

        this.viewSupport = new PerspectiveImageLensSupport<Number, Number>(vv);
        this.layoutSupport = new PerspectiveLayoutTransformSupport<Number, Number>(vv);

        final ScalingControl scaler = new CrossoverScalingControl();

        JButton plus = new JButton("+");
        plus.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                scaler.scale(vv, 1.1f, vv.getCenter());
            }
        });
        JButton minus = new JButton("-");
        minus.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                scaler.scale(vv, 0.9f, vv.getCenter());
            }
        });
        final JSlider horizontalSlider = new JSlider(-120, 120, 0) {

            /* (non-Javadoc)
             * @see javax.swing.JComponent#getPreferredSize()
             */
            @Override
            public Dimension getPreferredSize() {
                return new Dimension(80, super.getPreferredSize().height);
            }
        };

        final JSlider verticalSlider = new JSlider(-120, 120, 0) {

            /* (non-Javadoc)
             * @see javax.swing.JComponent#getPreferredSize()
             */
            @Override
            public Dimension getPreferredSize() {
                return new Dimension(super.getPreferredSize().width, 80);
            }
        };
        verticalSlider.setOrientation(JSlider.VERTICAL);
        final ChangeListener changeListener = new ChangeListener() {

            public void stateChanged(ChangeEvent e) {
                int vval = -verticalSlider.getValue();
                int hval = horizontalSlider.getValue();

                Dimension d = vv.getSize();
                PerspectiveTransform pt = null;
                pt = PerspectiveTransform.getQuadToQuad(vval, hval, d.width - vval, -hval, d.width + vval,
                        d.height + hval, -vval, d.height - hval,

                        0, 0, d.width, 0, d.width, d.height, 0, d.height);

                viewSupport.getPerspectiveTransformer().setPerspectiveTransform(pt);
                layoutSupport.getPerspectiveTransformer().setPerspectiveTransform(pt);
                vv.repaint();
            }
        };
        horizontalSlider.addChangeListener(changeListener);
        verticalSlider.addChangeListener(changeListener);

        JPanel perspectivePanel = new JPanel(new BorderLayout());
        JPanel perspectiveCenterPanel = new JPanel(new BorderLayout());
        perspectivePanel.setBorder(BorderFactory.createTitledBorder("Perspective Controls"));
        final JButton center = new JButton("Center");
        center.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                horizontalSlider.setValue(0);
                verticalSlider.setValue(0);
            }
        });

        final JCheckBox noText = new JCheckBox("No Text");
        noText.addItemListener(new ItemListener() {

            public void itemStateChanged(ItemEvent e) {
                JCheckBox cb = (JCheckBox) e.getSource();
                vertexStringerImpl.setEnabled(!cb.isSelected());
                vv.repaint();
            }
        });
        JPanel centerPanel = new JPanel();
        centerPanel.add(noText);

        ButtonGroup radio = new ButtonGroup();
        JRadioButton none = new JRadioButton("None");
        none.addItemListener(new ItemListener() {
            public void itemStateChanged(ItemEvent e) {
                boolean selected = e.getStateChange() == ItemEvent.SELECTED;
                if (selected) {
                    if (viewSupport != null) {
                        viewSupport.deactivate();
                    }
                    if (layoutSupport != null) {
                        layoutSupport.deactivate();
                    }
                }
                center.setEnabled(!selected);
                horizontalSlider.setEnabled(!selected);
                verticalSlider.setEnabled(!selected);
            }
        });
        none.setSelected(true);

        JRadioButton hyperView = new JRadioButton("View");
        hyperView.addItemListener(new ItemListener() {
            public void itemStateChanged(ItemEvent e) {
                viewSupport.activate(e.getStateChange() == ItemEvent.SELECTED);
            }
        });

        JRadioButton hyperModel = new JRadioButton("Layout");
        hyperModel.addItemListener(new ItemListener() {
            public void itemStateChanged(ItemEvent e) {
                layoutSupport.activate(e.getStateChange() == ItemEvent.SELECTED);
            }
        });
        radio.add(none);
        radio.add(hyperView);
        radio.add(hyperModel);

        JMenuBar menubar = new JMenuBar();
        JMenu modeMenu = graphMouse.getModeMenu();
        menubar.add(modeMenu);

        panel.setCorner(menubar);

        JPanel scaleGrid = new JPanel(new GridLayout(2, 0));
        scaleGrid.setBorder(BorderFactory.createTitledBorder("Zoom"));
        JPanel controls = new JPanel(new BorderLayout());
        scaleGrid.add(plus);
        scaleGrid.add(minus);
        controls.add(scaleGrid, BorderLayout.WEST);

        JPanel lensPanel = new JPanel(new GridLayout(2, 0));
        lensPanel.add(none);
        lensPanel.add(hyperView);
        lensPanel.add(hyperModel);

        perspectivePanel.add(lensPanel, BorderLayout.WEST);
        perspectiveCenterPanel.add(horizontalSlider, BorderLayout.SOUTH);
        perspectivePanel.add(verticalSlider, BorderLayout.EAST);
        perspectiveCenterPanel.add(center);
        perspectivePanel.add(perspectiveCenterPanel);
        controls.add(perspectivePanel, BorderLayout.EAST);

        controls.add(centerPanel);
        content.add(controls, BorderLayout.SOUTH);
    }

    /**
     * A simple implementation of VertexStringer that
     * gets Vertex labels from a Map  
     * 
     * @author Tom Nelson 
     *
     *
     */
    class VertexStringerImpl<V> implements Transformer<V, String> {

        Map<V, String> map = new HashMap<V, String>();

        boolean enabled = true;

        public VertexStringerImpl(Map<V, String> map) {
            this.map = map;
        }

        public String transform(Object v) {
            if (isEnabled()) {
                return map.get(v);
            } else {
                return "";
            }
        }

        /**
         * @return Returns the enabled.
         */
        public boolean isEnabled() {
            return enabled;
        }

        /**
         * @param enabled The enabled to set.
         */
        public void setEnabled(boolean enabled) {
            this.enabled = enabled;
        }
    }

    /**
     * create some vertices
     * @param count how many to create
     * @return the Vertices in an array
     */
    private Number[] createVertices(int count) {
        Number[] v = new Number[count];
        for (int i = 0; i < count; i++) {
            v[i] = new Integer(i);
            graph.addVertex(v[i]);
        }
        return v;
    }

    /**
     * create edges for this demo graph
     * @param v an array of Vertices to connect
     */
    void createEdges(Number[] v) {
        graph.addEdge(new Double(Math.random()), v[0], v[1], EdgeType.DIRECTED);
        graph.addEdge(new Double(Math.random()), v[3], v[0], EdgeType.DIRECTED);
        graph.addEdge(new Double(Math.random()), v[0], v[4], EdgeType.DIRECTED);
        graph.addEdge(new Double(Math.random()), v[4], v[5], EdgeType.DIRECTED);
        graph.addEdge(new Double(Math.random()), v[5], v[3], EdgeType.DIRECTED);
        graph.addEdge(new Double(Math.random()), v[2], v[1], EdgeType.DIRECTED);
        graph.addEdge(new Double(Math.random()), v[4], v[1], EdgeType.DIRECTED);
        graph.addEdge(new Double(Math.random()), v[8], v[2], EdgeType.DIRECTED);
        graph.addEdge(new Double(Math.random()), v[3], v[8], EdgeType.DIRECTED);
        graph.addEdge(new Double(Math.random()), v[6], v[7], EdgeType.DIRECTED);
        graph.addEdge(new Double(Math.random()), v[7], v[5], EdgeType.DIRECTED);
        graph.addEdge(new Double(Math.random()), v[0], v[9], EdgeType.DIRECTED);
        graph.addEdge(new Double(Math.random()), v[9], v[8], EdgeType.DIRECTED);
        graph.addEdge(new Double(Math.random()), v[7], v[6], EdgeType.DIRECTED);
        graph.addEdge(new Double(Math.random()), v[6], v[5], EdgeType.DIRECTED);
        graph.addEdge(new Double(Math.random()), v[4], v[2], EdgeType.DIRECTED);
        graph.addEdge(new Double(Math.random()), v[5], v[4], EdgeType.DIRECTED);
        graph.addEdge(new Double(Math.random()), v[4], v[10], EdgeType.DIRECTED);
        graph.addEdge(new Double(Math.random()), v[10], v[4], EdgeType.DIRECTED);
    }

    public static class PickWithIconListener implements ItemListener {
        DefaultVertexIconTransformer<Number> imager;
        Icon checked;

        public PickWithIconListener(DefaultVertexIconTransformer<Number> imager) {
            this.imager = imager;
            checked = new Checkmark(Color.red);
        }

        public void itemStateChanged(ItemEvent e) {
            Icon icon = imager.transform((Number) e.getItem());
            if (icon != null && icon instanceof LayeredIcon) {
                if (e.getStateChange() == ItemEvent.SELECTED) {
                    ((LayeredIcon) icon).add(checked);
                } else {
                    ((LayeredIcon) icon).remove(checked);
                }
            }
        }
    }

    /**
     * a simple Icon that draws a checkmark in the lower-right quadrant of its
     * area. Used to draw a checkmark on Picked Vertices.
     */
    //    public static class Checkmark implements Icon {
    //
    //            GeneralPath path = new GeneralPath();
    //            AffineTransform highlight = AffineTransform.getTranslateInstance(-1,-1);
    //            AffineTransform lowlight = AffineTransform.getTranslateInstance(1,1);
    //            AffineTransform shadow = AffineTransform.getTranslateInstance(2,2);
    //            Color color;
    //            public Checkmark() {
    //                this(Color.red);
    //            }
    //            public Checkmark(Color color) {
    //                this.color = color;
    //                path.moveTo(10,17);
    //                path.lineTo(13,20);
    //                path.lineTo(20,13);
    //            }
    //        public void paintIcon(Component c, Graphics g, int x, int y) {
    //            Shape shape = AffineTransform.getTranslateInstance(x, y).createTransformedShape(path);
    //            Graphics2D g2d = (Graphics2D)g;
    //            g2d.addRenderingHints(Collections.singletonMap(RenderingHints.KEY_ANTIALIASING, 
    //                    RenderingHints.VALUE_ANTIALIAS_ON));
    //            g2d.setStroke(new BasicStroke(4));
    //            g2d.setColor(Color.darkGray);
    //            g2d.draw(shadow.createTransformedShape(shape));
    //            g2d.setColor(Color.black);
    //            g2d.draw(lowlight.createTransformedShape(shape));
    //            g2d.setColor(Color.white);
    //            g2d.draw(highlight.createTransformedShape(shape));
    //            g2d.setColor(color);
    //            g2d.draw(shape);
    //        }
    //
    //        public int getIconWidth() {
    //            return 20;
    //        }
    //
    //        public int getIconHeight() {
    //            return 20;
    //        }
    //    }
    //   /**
    //     * An icon that is made up of a collection of Icons.
    //     * They are rendered in layers starting with the first
    //     * Icon added (from the constructor).
    //     * 
    //     * @author Tom Nelson
    //     *
    //     */
    //    public static class LayeredIcon extends ImageIcon {
    //
    //      /**
    //       * 
    //       */
    //       private static final long serialVersionUID = -2975294939874762164L;
    //      Set<Icon> iconSet = new LinkedHashSet<Icon>();
    //
    //      public LayeredIcon(Image image) {
    //            super(image);
    //      }
    //
    //        public void paintIcon(Component c, Graphics g, int x, int y) {
    //            super.paintIcon(c, g, x, y);
    //            Dimension d = new Dimension(getIconWidth(), getIconHeight());
    //            for (Iterator iterator = iconSet.iterator(); iterator.hasNext();) {
    //                Icon icon = (Icon) iterator.next();
    //                 Dimension id = new Dimension(icon.getIconWidth(), icon.getIconHeight());
    //                 int dx = (d.width - id.width)/2;
    //                 int dy = (d.height - id.height)/2;
    //                icon.paintIcon(c, g, x+dx, y+dy);
    //            }
    //        }
    //
    //      public void add(Icon icon) {
    //         iconSet.add(icon);
    //      }
    //
    //      public boolean remove(Icon icon) {
    //         return iconSet.remove(icon);
    //      }
    //   }

    /**
     * a driver for this demo
     */
    public static void main(String[] args) {
        JFrame frame = new JFrame();
        Container content = frame.getContentPane();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        content.add(new PerspectiveVertexImageShaperDemo());
        frame.pack();
        frame.setVisible(true);
    }
}