使用Prefuse绘制可交互的Radical Graph

 如果你想使用Prefuse绘制一个可交互的网络图,本文可能会帮助你。

1. 支持直接绘制GraphML格式的图
2. 支持Node和Edge上的提示(Tooltip)
3. 支持搜索
4. 支持选中结点后高亮邻结点

在绘制这个图的时候碰到不少问题,对Prefuse也没有深入去看。感谢网友们分享的信息。如果你对细节感兴趣,可以去http://prefuse.org/doc/manual/。如果你想要一些Demo,可以去http://prefuse.org/gallery/。

还有一些问题尚未得到解决,如果你找到了办法,记得告诉我☺

  1. 如何控制图中结点的分布,比如结点之间的距离,稀疏性?

  2. 如何过滤结点。比如只是显示出度入度高的结点?

  3. 如何支持嵌套图?没事尝试过,说不定已经支持了。

  4. 如何支持动态的删除或添加结点及边?

上马,

ArrowEdgeRenderer

import prefuse.render.EdgeRenderer;
import prefuse.util.ColorLib;
import prefuse.visual.VisualItem;
import java.awt.*;
public class ArrowEdgeRenderer extends EdgeRenderer
{
    public ArrowEdgeRenderer(int edgeType, int arrowType)
    {
        super(edgeType,arrowType);
    }
    public void render(Graphics2D g, VisualItem item)
    {
        super.render(g, item);
        // render the edge arrow head, if appropriate
        if ( m_curArrow != null )
        {
            g.setPaint(ColorLib.getColor(item.getStrokeColor()));
            super.setArrowHeadSize(10, 10);
            g.fill(m_curArrow);
        }
    }
}

TooltipControl

import prefuse.Display;
import prefuse.Visualization;
import prefuse.controls.ControlAdapter;
import prefuse.visual.VisualItem;
import java.awt.event.MouseEvent;
public class TooltipControl extends ControlAdapter {
    public static final String GRAPH_EDGE_TOOLTIP = "weight";
    public static final String GRAPH_NODE_TOOPTIP = "color";
    Visualization m_vis;
    public TooltipControl(Visualization vis){
        super();
        m_vis = vis;
    }
    public void itemEntered(VisualItem item, MouseEvent e) {
        Display d = (Display) e.getSource();
        item.setHighlighted(true);
        System.out.println("itemEntered");
        if (item.getGroup().equals("tree.edges")) {
            StringBuffer msg = new StringBuffer();
            msg.append("<html>weight:" + item.getString(GRAPH_EDGE_TOOLTIP));
            msg.append("</html>");
            d.setToolTipText(msg.toString());
            //m_vis.run("update");
        } else {
            StringBuffer msg = new StringBuffer();
            msg.append("<html>color:" + item.getString(GRAPH_NODE_TOOPTIP));
            msg.append("</html>");
            d.setToolTipText(msg.toString());
        }
    }
    public void itemExited(VisualItem item, MouseEvent e) {
        Display d = (Display) e.getSource();
        d.setToolTipText("");
        //m_vis.run("update");
    }
    public void itemClicked(VisualItem item, MouseEvent e) {
    }
    public void itemPressed(VisualItem item, MouseEvent e) {
    }
    public void itemReleased(VisualItem item, MouseEvent e) {
    }
}//end of class DataMouseControl

RadialGraphView

import java.awt.*;
import java.awt.event.MouseEvent;
import java.util.Iterator;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import prefuse.Constants;
import prefuse.Display;
import prefuse.Visualization;
import prefuse.action.ActionList;
import prefuse.action.GroupAction;
import prefuse.action.ItemAction;
import prefuse.action.RepaintAction;
import prefuse.action.animate.ColorAnimator;
import prefuse.action.animate.PolarLocationAnimator;
import prefuse.action.animate.QualityControlAnimator;
import prefuse.action.animate.VisibilityAnimator;
import prefuse.action.assignment.ColorAction;
import prefuse.action.assignment.FontAction;
import prefuse.action.layout.CollapsedSubtreeLayout;
import prefuse.action.layout.graph.RadialTreeLayout;
import prefuse.activity.SlowInSlowOutPacer;
import prefuse.controls.*;
import prefuse.data.Graph;
import prefuse.data.Node;
import prefuse.data.Table;
import prefuse.data.Tuple;
import prefuse.data.event.TupleSetListener;
import prefuse.data.io.GraphMLReader;
import prefuse.data.query.SearchQueryBinding;
import prefuse.data.search.PrefixSearchTupleSet;
import prefuse.data.search.SearchTupleSet;
import prefuse.data.tuple.DefaultTupleSet;
import prefuse.data.tuple.TupleSet;
import prefuse.render.AbstractShapeRenderer;
import prefuse.render.DefaultRendererFactory;
import prefuse.render.EdgeRenderer;
import prefuse.render.LabelRenderer;
import prefuse.util.ColorLib;
import prefuse.util.FontLib;
import prefuse.util.ui.JFastLabel;
import prefuse.util.ui.JSearchPanel;
import prefuse.util.ui.UILib;
import prefuse.visual.VisualItem;
import prefuse.visual.expression.InGroupPredicate;
import prefuse.visual.sort.TreeDepthItemSorter;

/**
 * Demonstration of a node-link tree viewer
 *
 * @version 1.0
 * @author <a href="http://jheer.org">jeffrey heer</a>
 */
public class RadialGraphView extends Display {
    public static final String DATA_FILE = "g.xml";
    public static final String GRAPH_FONT = "Times New Roman";
    public static final String GRAPH_LABEL = "color";
    private static final String tree = "tree";
    private static final String treeNodes = "tree.nodes";
    private static final String treeEdges = "tree.edges";
    private static final String linear = "linear";
    private LabelRenderer m_nodeRenderer;
    private EdgeRenderer m_edgeRenderer;
    private String m_label = "label";
    public RadialGraphView(Graph g, String label) {
        super(new Visualization());
        m_label = label;
        // -- set up visualization --
        m_vis.add(tree, g);
        // -- make edge can be interactive
        m_vis.setInteractive(treeEdges, null, true);
        // -- set up renderers --
        m_nodeRenderer = new LabelRenderer(m_label);
        m_nodeRenderer.setRenderType(AbstractShapeRenderer.RENDER_TYPE_FILL);
        m_nodeRenderer.setHorizontalAlignment(Constants.CENTER);
        m_nodeRenderer.setRoundedCorner(8,8);
        // we can use Constants.EDGE_TYPE_LINE and Constants.EDGE_TYPE_CURVE, Constants.EDGE_TYPE_COUNT has exception???
        m_edgeRenderer = new ArrowEdgeRenderer(Constants.EDGE_TYPE_LINE,Constants.EDGE_ARROW_FORWARD);
        DefaultRendererFactory rf = new DefaultRendererFactory(m_nodeRenderer);
        rf.add(new InGroupPredicate(treeEdges), m_edgeRenderer);
        rf.add(new InGroupPredicate(treeNodes), m_nodeRenderer);
        m_vis.setRendererFactory(rf);
        // colors
        ItemAction nodeColor = new NodeColorAction(treeNodes);
        ItemAction textColor = new TextColorAction(treeNodes);
        m_vis.putAction("textColor", textColor);
        ItemAction edgeColor = new ColorAction(treeEdges,
                VisualItem.STROKECOLOR, ColorLib.rgb(200,200,200));
        FontAction fonts = new FontAction(treeNodes,
                FontLib.getFont(GRAPH_FONT, 20));
        fonts.add("ingroup('_focus_')", FontLib.getFont(GRAPH_FONT, 22));
        // recolor
        ActionList recolor = new ActionList();
        recolor.add(nodeColor);
        recolor.add(textColor);
        m_vis.putAction("recolor", recolor);
        // repaint
        ActionList repaint = new ActionList();
        repaint.add(recolor);
        repaint.add(new RepaintAction());
        m_vis.putAction("repaint", repaint);
        // animate paint change
        ActionList animatePaint = new ActionList(400);
        animatePaint.add(new ColorAnimator(treeNodes));
        animatePaint.add(new RepaintAction());
        m_vis.putAction("animatePaint", animatePaint);
        // create the tree layout action
        RadialTreeLayout treeLayout = new RadialTreeLayout(tree);
        //treeLayout.setAngularBounds(-Math.PI/2, Math.PI);
        m_vis.putAction("treeLayout", treeLayout);
        CollapsedSubtreeLayout subLayout = new CollapsedSubtreeLayout(tree);
        m_vis.putAction("subLayout", subLayout);
        // create the filtering and layout
        ActionList filter = new ActionList();
        filter.add(new TreeRootAction(tree));
        filter.add(fonts);
        filter.add(treeLayout);
        filter.add(subLayout);
        filter.add(textColor);
        filter.add(nodeColor);
        filter.add(edgeColor);
        m_vis.putAction("filter", filter);
        // animated transition
        ActionList animate = new ActionList(1250);
        animate.setPacingFunction(new SlowInSlowOutPacer());
        animate.add(new QualityControlAnimator());
        animate.add(new VisibilityAnimator(tree));
        animate.add(new PolarLocationAnimator(treeNodes, linear));
        animate.add(new ColorAnimator(treeNodes));
        animate.add(new RepaintAction());
        m_vis.putAction("animate", animate);
        m_vis.alwaysRunAfter("filter", "animate");
        // ------------------------------------------------
        // initialize the display
        setSize((int)Toolkit.getDefaultToolkit().getScreenSize().getWidth(), (int)Toolkit.getDefaultToolkit().getScreenSize().getHeight() - 200);
        //JFrame.setLocationRelativeTo(null);
        setItemSorter(new TreeDepthItemSorter());
        addControlListener(new DragControl());
        addControlListener(new ZoomToFitControl());
        addControlListener(new ZoomControl());
        addControlListener(new PanControl());
        addControlListener(new FocusControl(1, "filter"));
        addControlListener(new HoverActionControl("repaint"));
        // show tooltip when mouse hover on the control
        addControlListener(new TooltipControl(m_vis));
        //
        addControlListener(new NeighborHighlightControl());
        // ------------------------------------------------
        // filter graph and perform layout
        m_vis.run("filter");
        // maintain a set of items that should be interpolated linearly
        // this isn't absolutely necessary, but makes the animations nicer
        // the PolarLocationAnimator should read this set and act accordingly
        m_vis.addFocusGroup(linear, new DefaultTupleSet());
        m_vis.getGroup(Visualization.FOCUS_ITEMS).addTupleSetListener(
                new TupleSetListener() {
                    public void tupleSetChanged(TupleSet t, Tuple[] add, Tuple[] rem) {
                        TupleSet linearInterp = m_vis.getGroup(linear);
                        if ( add.length < 1 ) return; linearInterp.clear();
                        for ( Node n = (Node)add[0]; n!=null; n=n.getParent() )
                            linearInterp.addTuple(n);
                    }
                }
        );
        SearchTupleSet search = new PrefixSearchTupleSet();
        m_vis.addFocusGroup(Visualization.SEARCH_ITEMS, search);
        search.addTupleSetListener(new TupleSetListener() {
            public void tupleSetChanged(TupleSet t, Tuple[] add, Tuple[] rem) {
                m_vis.cancel("animatePaint");
                m_vis.run("recolor");
                m_vis.run("animatePaint");
            }
        });
    }
    // ------------------------------------------------------------------------
    public static void main(String argv[]) {
        String infile = DATA_FILE;
        String label = GRAPH_LABEL;
        if (argv != null && argv.length > 1 ) {
            infile = argv[0];
            label = argv[1];
        }
        UILib.setPlatformLookAndFeel();
        JFrame frame = new JFrame("Radio Graph View");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setContentPane(demo(infile, label));
        frame.pack();
        frame.setVisible(true);
    }
    public static JPanel demo(String datafile, final String label) {
        Graph g = null;
        try {
            g = new GraphMLReader().readGraph(datafile);
            System.out.println("g.isDirected() = " + g.isDirected());
        } catch ( Exception e ) {
            e.printStackTrace();
            System.exit(1);
        }
        return demo(g, label);
    }
    public static JPanel demo(Graph g, final String label) {
        // create a new radial tree view
        final RadialGraphView gview = new RadialGraphView(g, label);
        Visualization vis = gview.getVisualization();
        // create a search panel for the tree map
        SearchQueryBinding sq = new SearchQueryBinding(
                (Table)vis.getGroup(treeNodes), label,
                (SearchTupleSet)vis.getGroup(Visualization.SEARCH_ITEMS));
        JSearchPanel search = sq.createSearchPanel();
        search.setShowResultCount(true);
        search.setBorder(BorderFactory.createEmptyBorder(5,5,4,0));
        search.setFont(FontLib.getFont(GRAPH_FONT, Font.PLAIN, 11));
        final JFastLabel title = new JFastLabel("                 ");
        title.setPreferredSize(new Dimension(350, 20));
        title.setVerticalAlignment(SwingConstants.BOTTOM);
        title.setBorder(BorderFactory.createEmptyBorder(3,0,0,0));
        title.setFont(FontLib.getFont(GRAPH_FONT, Font.PLAIN, 16));
        gview.addControlListener(new ControlAdapter() {
            public void itemEntered(VisualItem item, MouseEvent e) {
                if ( item.canGetString(label) )
                    title.setText(item.getString(label));
            }
            public void itemExited(VisualItem item, MouseEvent e) {
                title.setText(null);
            }
        });
        Box box = new Box(BoxLayout.X_AXIS);
        box.add(Box.createHorizontalStrut(10));
        box.add(title);
        box.add(Box.createHorizontalGlue());
        box.add(search);
        box.add(Box.createHorizontalStrut(3));
        JPanel panel = new JPanel(new BorderLayout());
        panel.add(gview, BorderLayout.CENTER);
        panel.add(box, BorderLayout.SOUTH);
        Color BACKGROUND = Color.WHITE;
        Color FOREGROUND = Color.DARK_GRAY;
        UILib.setColor(panel, BACKGROUND, FOREGROUND);
        return panel;
    }
    // ------------------------------------------------------------------------
    /**
     * Switch the root of the tree by requesting a new spanning tree
     * at the desired root
     */
    public static class TreeRootAction extends GroupAction {
        public TreeRootAction(String graphGroup) {
            super(graphGroup);
        }
        public void run(double frac) {
            TupleSet focus = m_vis.getGroup(Visualization.FOCUS_ITEMS);
            if ( focus==null || focus.getTupleCount() == 0 ) return;
            Graph g = (Graph)m_vis.getGroup(m_group);
            Node f = null;
            Iterator tuples = focus.tuples();
            while (tuples.hasNext() && !g.containsTuple(f=(Node)tuples.next()))
            {
                f = null;
            }
            if ( f == null ) return;
            g.getSpanningTree(f);
        }
    }
    /**
     * Set node fill colors
     */
    public static class NodeColorAction extends ColorAction {
        public NodeColorAction(String group) {
            super(group, VisualItem.FILLCOLOR, ColorLib.rgba(255,255,255,0));
            add("_hover", ColorLib.gray(220,230));
            add("ingroup('_search_')", ColorLib.rgb(255,190,190));
            add("ingroup('_focus_')", ColorLib.rgb(198,229,229));
            add("_fixed", ColorLib.rgb(255,100,100));
            add("_highlight", ColorLib.rgb(255,200,125));
        }
    } // end of inner class NodeColorAction
    /**
     * Set node text colors
     */
    public static class TextColorAction extends ColorAction {
        public TextColorAction(String group) {
            super(group, VisualItem.TEXTCOLOR, ColorLib.gray(0));
            add("_hover", ColorLib.rgb(255,0,0));
        }
    } // end of inner class TextColorAction

} // end of class RadialGraphView

g.xml跟前面的GraphML一样。运行的效果:

使用Prefuse绘制可交互的Radical Graph

你可能感兴趣的:(使用Prefuse绘制可交互的Radical Graph)