如果你想使用Prefuse绘制一个可交互的网络图,本文可能会帮助你。
1. 支持直接绘制GraphML格式的图
2. 支持Node和Edge上的提示(Tooltip)
3. 支持搜索
4. 支持选中结点后高亮邻结点
在绘制这个图的时候碰到不少问题,对Prefuse也没有深入去看。感谢网友们分享的信息。如果你对细节感兴趣,可以去http://prefuse.org/doc/manual/。如果你想要一些Demo,可以去http://prefuse.org/gallery/。
还有一些问题尚未得到解决,如果你找到了办法,记得告诉我☺
如何控制图中结点的分布,比如结点之间的距离,稀疏性?
如何过滤结点。比如只是显示出度入度高的结点?
如何支持嵌套图?没事尝试过,说不定已经支持了。
如何支持动态的删除或添加结点及边?
上马,
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一样。运行的效果: