2014.10.15日以來的一個月,擠破了頭、跑斷了腿、傷透了心、吃夠了全國最大餐飲連鎖店——沙縣小吃。其中酸甜苦辣,絕不是三言兩語能夠說得清道的明的。校招的兄弟姐妹們,你們懂得……
體會最深的一句話還是:出來混,遲早要還的。
一個月過去了,迷茫之際有太多無奈,無奈中又夾雜些許慶幸,歲月匆匆,人生不息,奮斗不止。
遵守最初的諾言,繼續走我可視化的道路:
上集摘要:一個月博文中大概介紹了可視化的一些常用工具,從可操作性、實用性、交互性等各方面進行了簡單的對比和總結,具體參見http://www.cnblogs.com/bigdataZJ/p/VisualizationSoloShow.html,結合自己的需求,挑出了Prefuse和Processing兩員大將出來露了一手,詳情請見http://www.cnblogs.com/bigdataZJ/p/VisualizationSoloShow2.html
一番角逐之后,Prefuse工具集脫穎而出,其強大的展示效果、開發者友好的API說明文檔、豐富的自帶Demo無一不讓我對其欲罷不能。下面我們來好好分析下Prefuse的強大之處:
1.Prefuse主要特征:
(1)任意數據類型的表格、圖和樹形數據結構,數據索引、選擇查詢,有效的內存占用
(2)具有布局、着色、大小、圖形編碼、扭曲、動畫等多個組件
(3)具有交互控制庫
(4)支持動畫過渡,通過一系列的活動調度機制
(5)支持平移、縮放等視圖變換
(6)對於交互過濾數據的動態查詢
(7)能夠使用可用的搜索引擎進行文本檢索
(8)具有布局和動畫的力導向模擬引擎
(9)靈活的多視圖展現,包括“概述+細節”和“小倍數”顯示
(10)內置類SQL語句查詢,可以用於編寫查詢語句實現查詢指定字段的數據
(11)提供查詢語句到Prefuse數據結構的數據映射的SQL查詢
(12)簡單、開發者友好的APIs文檔
2.Prefuse模型:
(1)prefuse.data包提供了 Table, Graph, Tree等數據結構;提供了一個data tables,他的行使用一個類 Tuple來表示;這個包中,Node和Edge來表示圖或者樹的一些成員。
作為一種高級特征的工具集,Prefuse提供了一種解釋性的表達式語言,該語言可以用來請求Prefuse中的數據結構並根據已有的數據列創建衍生的列數據。表達式語言的功能實現類在prefuse.data.expression包中,文本表達式解析類在ExpressionParser類中。
(2)prefuse.data.io包提供了文件的讀寫,包括表,圖和樹的結構,其中,表的格式:CSV和任意分割的文本文件,對於網絡,有 GraphML和 TreeML(XML也能);prefuse.data.io.sql包提供了對SQL數據庫的查詢,並返回一個prefuse表
(3)可視化抽象是通過將數據添加到Visulization實例中來得到的,它除了包含原始數據外,還建立了一套完整的可視化體系,包括x、y的坐標軸,顏色,大小字體等值,任意的Tuple, Node, 或者 Edge被添加到Visulization實例中時候,相關的VisualItems實例就建立好了,如NodeItem和 EdgeItem就是VisualItems的實例。(也就是說,可視化抽象實現了添加的數據元素與VisualItems之間的映射)
(4)可視化映射工作由Action模塊來完成,它是有一系列獨立的處理模塊組成的,這些模塊來完成可視性、布局計算、顏色設定以及任何其他的可視化工作。prefuse.action包以及其子包會提供一系列布局,形變,動畫以及可視化編碼的工作。
(5)Renderer模塊決定了VisualItems的出現情況,Renderers模塊負責計算顯示區域,即如何將可視化圖形繪制在屏幕上。RendererFactory用來對Renderer進行管理,體現在給VisualItems分配適當的Renderer上。
(6)交互工作,Display組建負責完成交互方面的工作,起到一個類似於攝像機的功能,對顯示的區域進行選取,縮放。它直接與用戶相關。
一個Visualization可以與多個Display實例關聯,以實現多視圖參數配置,比如“概述+詳細”以及小倍數顯示視圖等。
(7)每個Display實例都支持若干個Controls,他們負責處理Display上鼠標和鍵盤的action。prefuse.controls包提供了一個預處理的控制器可以用來完成旋轉縮放Display的工作,通過prefuse.controls包的子類ControlAdapter可以實現對Display的控制。
(8)最后,prefuse.data.query 包提供了動態查詢綁定(?)的功能,這些綁定能夠生成合適的用戶界面組建,來直接操作這些查詢。
3.Prefuse自帶Demo---GraphView.java詳解
下面是自己在研讀Prefuse源碼文件夾demos下的GraphView加的一些注釋:
1 //start of class GraphView 2 3 public class GraphView extends JPanel { 4 5 private static final String graph = "graph"; 6 7 private static final String nodes = "graph.nodes"; 8 9 private static final String edges = "graph.edges"; 10 11 private Visualization m_vis; 12 13 14 15 public GraphView(Graph g, String label) { 16 17 super(new BorderLayout());//在GraphView的構造函數中調用超類的構造方法,並創建布局BorderLayout對象。 18 19 // create a new, empty visualization for our data 20 21 m_vis = new Visualization();//創建Visualization對象,使用默認的渲染工廠(DefaultRendererFactory)。Visualization類負責管理源數據與可視化組件之間的映射。 22 23 // -------------------------------------------------------------------- 24 25 // set up the renderers 26 27 LabelRenderer tr = new LabelRenderer(); 28 29 tr.setRoundedCorner(8, 8); 30 31 m_vis.setRendererFactory(new efaultRendererFactory(tr));//新建標簽渲染器並注冊到Visualization上,使用的還是DefaultRendererFactory。 32 33 // -------------------------------------------------------------------- 34 // register the data with a visualization 35 36 // adds graph to visualization and sets renderer label field 37 38 setGraph(g, label);// 向Visualization添加圖形Graph並為標簽域賦值。 39 40 41 42 // fix selected focus nodes 聲明一個數據元組集合,並為該集合添加一個數據元組的監聽器 43 44 TupleSet focusGroup = m_vis.getGroup(Visualization.FOCUS_ITEMS); 45 46 focusGroup.addTupleSetListener(new TupleSetListener() { 47 48 public void tupleSetChanged(TupleSet ts, Tuple[] add, Tuple[] rem) 49 50 { 51 52 for ( int i=0; i<rem.length; ++i ) 53 54 ((VisualItem)rem[i]).setFixed(false); 55 56 for ( int i=0; i<add.length; ++i ) { 57 58 ((VisualItem)add[i]).setFixed(false); 59 60 ((VisualItem)add[i]).setFixed(true); 61 62 } 63 64 if ( ts.getTupleCount() == 0 ) { 65 66 ts.addTuple(rem[0]); 67 68 ((VisualItem)rem[0]).setFixed(false); 69 70 } 71 72 m_vis.run("draw"); 73 74 } 75 76 });//聲明一個數據元組集合,並通過匿名內部類的形式為該集合添加一個數據元組的監聽器(TupleSetListener),其中ts:變化的數據元組;add:已經加入的元組數組集合;rem:移除的數據集合。 77 78 // -------------------------------------------------------------------- 79 80 // create actions to process the visual data 81 82 83 84 int hops = 30; 85 86 final GraphDistanceFilter filter = new GraphDistanceFilter(graph, hops); 87 88 89 90 ColorAction fill = new ColorAction(nodes, 91 92 VisualItem.FILLCOLOR, ColorLib.rgb(200,200,255)); 93 94 fill.add(VisualItem.FIXED, ColorLib.rgb(255,100,100)); 95 96 fill.add(VisualItem.HIGHLIGHT, ColorLib.rgb(255,200,125)); 97 98 99 100 ActionList draw = new ActionList(); 101 102 draw.add(filter); 103 104 draw.add(fill); 105 106 draw.add(new ColorAction(nodes, VisualItem.STROKECOLOR, 0)); 107 108 draw.add(new ColorAction(nodes, VisualItem.TEXTCOLOR, ColorLib.rgb(0,0,0))); 109 110 draw.add(new ColorAction(edges, VisualItem.FILLCOLOR, ColorLib.gray(200))); 111 112 draw.add(new ColorAction(edges, VisualItem.STROKECOLOR, ColorLib.gray(200)));// 根據設定距離hops新建一個圖形距離過濾器類;針對nodes,采取完全填充顏色的方式(FILLCOLOR),並對聚焦點(fixed )、高亮點(與fixed node相鄰的點即highlight)以及剩余點分別賦予不同的顏色表現.將GraphDistanceFilter和ColorAction都注冊到聲明的ActionList對象上,並同時添加點與邊的描邊顏色以及填充顏色的ColorAction。 113 114 ActionList animate = new ActionList(Activity.INFINITY); 115 116 animate.add(new ForceDirectedLayout(graph)); 117 118 animate.add(fill); 119 120 animate.add(new RepaintAction());//聲明一個ActionList的animate對象,在該對象上添加布局方式(這里采用力導向布局方法ForceDirectedLayout),並添加上面的ColorAction類的fill對象以及一個重繪圖形Action。 121 // finally, we register our ActionList with the Visualization. 122 123 // we can later execute our Actions by invoking a method on our 124 125 // Visualization, using the name we've chosen below. 126 127 m_vis.putAction("draw", draw); 128 129 m_vis.putAction("layout", animate); 130 131 m_vis.runAfter("draw", "layout");//將draw和animate注冊到m_vis上,后面通過Visualization的方法觸發執行每個注冊的Action。 132 133 // -------------------------------------------------------------------- 134 135 // set up a display to show the visualization 136 Display display = new Display(m_vis); 137 display.setSize(700,700); 138 display.pan(350, 350); 139 display.setForeground(Color.GRAY); 140 display.setBackground(Color.WHITE); 141 142 143 144 // main display controls 145 display.addControlListener(new FocusControl(1)); 146 display.addControlListener(new DragControl()); 147 display.addControlListener(new PanControl()); 148 display.addControlListener(new ZoomControl()); 149 display.addControlListener(new WheelZoomControl()); 150 display.addControlListener(new ZoomToFitControl()); 151 display.addControlListener(new NeighborHighlightControl());//通過Display展現Visualization包括:設置畫布大小,平移范圍,前景背景顏色以及添加聚焦、拖拽、平移、縮放、滑輪、縮放至適合顯示、緊鄰高亮監聽器。 152 153 154 155 // overview display 156 157 // Display overview = new Display(vis); 158 159 // overview.setSize(290,290); 160 161 // overview.addItemBoundsListener(new FitOverviewListener()); 162 163 164 165 display.setForeground(Color.GRAY); 166 167 display.setBackground(Color.WHITE); 168 169 170 171 // -------------------------------------------------------------------- 172 173 // launch the visualization 174 175 176 177 // create a panel for editing force values 178 179 ForceSimulator fsim = ((ForceDirectedLayout)animate.get(0)).getForceSimulator(); 180 181 JForcePanel fpanel = new JForcePanel(fsim); 182 183 184 185 final JValueSlider slider = new JValueSlider("Distance", 0, hops, hops); 186 187 slider.addChangeListener(new ChangeListener() { 188 189 public void stateChanged(ChangeEvent e) { 190 191 filter.setDistance(slider.getValue().intValue());//只要調節面板上的值有變動就執行下面的run函數,重新布局界面 192 193 m_vis.run("draw"); 194 195 } 196 197 }); 198 199 slider.setBackground(Color.WHITE); 200 201 slider.setPreferredSize(new Dimension(300,30)); 202 203 slider.setMaximumSize(new Dimension(300,30));//設置調節面板的背景顏色、大小 204 205 206 207 208 209 Box cf = new Box(BoxLayout.Y_AXIS); 210 211 cf.add(slider); 212 213 cf.setBorder(BorderFactory.createTitledBorder("Connectivity Filter")); 214 215 fpanel.add(cf); 216 217 //fpanel.add(opanel); 218 219 fpanel.add(Box.createVerticalGlue()); 220 221 222 223 // create a new JSplitPane to present the interface 224 225 JSplitPane split = new JSplitPane(); 226 227 split.setLeftComponent(display); 228 229 split.setRightComponent(fpanel); 230 231 split.setOneTouchExpandable(true); 232 233 split.setContinuousLayout(false); 234 235 split.setDividerLocation(700);//為整張畫布布局,包括左邊、右邊應該呈現什么內容等 236 237 // now we run our action list 238 239 //m_vis.run("draw"); 240 241 add(split); 242 243 } 244 245 246 247 public void setGraph(Graph g, String label) { 248 249 // update labeling 250 251 DefaultRendererFactory drf = (DefaultRendererFactory) 252 253 m_vis.getRendererFactory(); 254 255 ((LabelRenderer)drf.getDefaultRenderer()).setTextField(label); 256 257 // update graph 258 259 m_vis.removeGroup(graph); 260 261 VisualGraph vg = m_vis.addGraph(graph, g); 262 263 m_vis.setValue(edges, null, VisualItem.INTERACTIVE, Boolean.FALSE); 264 265 VisualItem f = (VisualItem)vg.getNode(0); 266 267 m_vis.getGroup(Visualization.FOCUS_ITEMS).setTuple(f); 268 269 f.setFixed(false); 270 271 } 272 273 274 275 // ------------------------------------------------------------------------ 276 277 // Main and demo methods 278 279 280 281 public static void main(String[] args) { 282 283 UILib.setPlatformLookAndFeel(); 284 285 286 287 // create graphview 288 289 String datafile = null; 290 291 String label = "label"; 292 293 if ( args.length > 1 ) {//如果用戶在運行時有參數傳值則分別賦值給datafile和label 294 295 datafile = args[0]; 296 297 label = args[1]; 298 299 } 300 301 JFrame frame = demo(datafile, label); //通過調用demo函數完成整個界面的設計布局等,最終呈現一個JFrame 302 303 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 關閉按鈕的動作為退出 304 } 305 306 307 308 public static JFrame demo() { 309 310 return demo((String)null, "label"); 311 312 } 313 314 315 316 public static JFrame demo(String datafile, String label) { 317 318 Graph g = null; 319 320 if ( datafile == null ) { 321 322 g = GraphLib.getGrid(15,15);//如果datafile為空,則通過調用圖形庫GraphLib中的getGrid得到15*15的網狀圖形,如下圖所示 323 label = "label"; 324 325 } else { 326 327 try { 328 329 g = new GraphMLReader().readGraph(datafile);//否則通過指定路徑讀取datafile文件並轉換為圖形 330 331 } catch ( Exception e ) { 332 333 e.printStackTrace(); 334 335 System.exit(1); 336 337 } 338 339 } 340 341 return demo(g, label); 342 343 } 344 345 346 347 public static JFrame demo(Graph g, String label) { 348 349 final GraphView view = new GraphView(g, label); 350 351 352 353 // set up menu 354 355 JMenu dataMenu = new JMenu("Data");//新建菜單欄 356 357 dataMenu.add(new OpenGraphAction(view));//注冊“打開文件”選項卡 358 359 dataMenu.add(new GraphMenuAction("Grid","ctrl 1",view) {//添加網狀布局選項卡 360 361 protected Graph getGraph() { 362 363 return GraphLib.getGrid(15,15); 364 365 } 366 367 }); 368 369 dataMenu.add(new GraphMenuAction("Clique","ctrl 2",view) {//添加團狀布局選項卡 370 371 protected Graph getGraph() { 372 373 return GraphLib.getClique(10); 374 375 } 376 377 }); 378 379 dataMenu.add(new GraphMenuAction("Honeycomb","ctrl 3",view) {//添加蜂窩狀布局選項卡 380 381 protected Graph getGraph() { 382 383 return GraphLib.getHoneycomb(5); 384 385 } 386 387 }); 388 389 dataMenu.add(new GraphMenuAction("Balanced Tree","ctrl 4",view) {//添加平衡樹布局選項卡 390 391 protected Graph getGraph() { 392 393 return GraphLib.getBalancedTree(3,5); 394 395 } 396 397 }); 398 399 dataMenu.add(new GraphMenuAction("Diamond Tree","ctrl 5",view) { 400 401 protected Graph getGraph() { 402 403 return GraphLib.getDiamondTree(3,3,3); //添加鑽石樹形圖布局選項卡 404 405 } 406 407 }); 408 409 JMenuBar menubar = new JMenuBar(); 410 411 menubar.add(dataMenu);//將以上菜單選項注冊到menubar菜單欄上 412 413 414 415 // launch window 416 417 JFrame frame = new JFrame("p r e f u s e | g r a p h v i e w"); 418 419 frame.setJMenuBar(menubar); 420 421 frame.setContentPane(view); 422 423 frame.pack(); 424 425 frame.setVisible(true);//添加菜單欄、圖形等 426 427 428 429 frame.addWindowListener(new WindowAdapter() { 430 431 public void windowActivated(WindowEvent e) { 432 433 view.m_vis.run("layout"); 434 435 } 436 437 public void windowDeactivated(WindowEvent e) { 438 439 view.m_vis.cancel("layout"); 440 441 } 442 443 }); 444 445 446 447 return frame; 448 449 } 450 451 452 // ------------------------------------------------------------------------ 453 454 /** 455 456 * Swing menu action that loads a graph into the graph viewer. 457 458 * 該類主要負責為每一種布局選項配置相應的快捷鍵 459 460 */ 461 462 public abstract static class GraphMenuAction extends AbstractAction { 463 464 private GraphView m_view; 465 466 public GraphMenuAction(String name, String accel, GraphView view) { 467 468 m_view = view; 469 470 this.putValue(AbstractAction.NAME, name); 471 472 this.putValue(AbstractAction.ACCELERATOR_KEY, 473 474 KeyStroke.getKeyStroke(accel)); 475 476 } 477 478 public void actionPerformed(ActionEvent e) { 479 480 m_view.setGraph(getGraph(), "label"); 481 482 } 483 484 protected abstract Graph getGraph(); 485 486 } 487 488 //該類負責對菜單欄的選項卡的響應 489 490 public static class OpenGraphAction extends AbstractAction { 491 492 private GraphView m_view; 493 494 495 496 public OpenGraphAction(GraphView view) { 497 498 m_view = view; 499 500 this.putValue(AbstractAction.NAME, "Open File..."); 501 502 this.putValue(AbstractAction.ACCELERATOR_KEY, 503 504 KeyStroke.getKeyStroke("ctrl O")); 505 506 } 507 508 public void actionPerformed(ActionEvent e) { 509 510 Graph g = IOLib.getGraphFile(m_view); 511 512 if ( g == null ) return; 513 514 String label = getLabel(m_view, g); 515 516 if ( label != null ) { 517 518 m_view.setGraph(g, label); 519 520 } 521 522 } 523 524 public static String getLabel(Component c, Graph g) { 525 526 // get the column names 527 528 Table t = g.getNodeTable(); 529 530 int cc = t.getColumnCount(); 531 532 String[] names = new String[cc]; 533 534 for ( int i=0; i<cc; ++i ) 535 536 names[i] = t.getColumnName(i); 537 538 539 540 // where to store the result 541 542 final String[] label = new String[1]; 543 544 // -- build the dialog ----- 545 546 // we need to get the enclosing frame first 547 548 while ( c != null && !(c instanceof JFrame) ) { 549 550 c = c.getParent(); 551 } 552 553 final JDialog dialog = new JDialog( 554 555 (JFrame)c, "Choose Label Field", true); 556 557 558 559 // create the ok/cancel buttons 560 561 final JButton ok = new JButton("OK"); 562 563 ok.setEnabled(false); 564 565 ok.addActionListener(new ActionListener() { 566 567 public void actionPerformed(ActionEvent e) { 568 569 dialog.setVisible(false); 570 571 } 572 573 }); 574 575 JButton cancel = new JButton("Cancel"); 576 577 cancel.addActionListener(new ActionListener() { 578 579 public void actionPerformed(ActionEvent e) { 580 581 label[0] = null; 582 583 dialog.setVisible(false); 584 585 } 586 587 }); 588 589 // build the selection list 590 591 final JList list = new JList(names); 592 list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 593 594 list.getSelectionModel().addListSelectionListener( 595 596 new ListSelectionListener() { 597 598 public void valueChanged(ListSelectionEvent e) { 599 600 int sel = list.getSelectedIndex(); 601 602 if ( sel >= 0 ) { 603 604 ok.setEnabled(true); 605 606 label[0] = (String)list.getModel().getElementAt(sel); 607 608 } else { 609 610 ok.setEnabled(false); 611 612 label[0] = null; 613 614 } 615 616 } 617 618 }); 619 620 JScrollPane scrollList = new JScrollPane(list); 621 622 623 624 JLabel title = new JLabel("Choose a field to use for node labels:"); 625 626 627 628 // layout the buttons 629 630 Box bbox = new Box(BoxLayout.X_AXIS); 631 632 bbox.add(Box.createHorizontalStrut(5)); 633 634 bbox.add(Box.createHorizontalGlue()); 635 636 bbox.add(ok); 637 638 bbox.add(Box.createHorizontalStrut(5)); 639 640 bbox.add(cancel); 641 642 bbox.add(Box.createHorizontalStrut(5)); 643 644 645 646 // put everything into a panel 647 648 JPanel panel = new JPanel(new BorderLayout()); 649 650 panel.add(title, BorderLayout.NORTH); 651 652 panel.add(scrollList, BorderLayout.CENTER); 653 654 panel.add(bbox, BorderLayout.SOUTH); 655 656 panel.setBorder(BorderFactory.createEmptyBorder(5,2,2,2)); 657 658 659 660 // show the dialog 661 662 dialog.setContentPane(panel); 663 664 dialog.pack(); 665 666 dialog.setLocationRelativeTo(c); 667 668 dialog.setVisible(true); 669 670 dialog.dispose(); 671 672 673 674 // return the label field selection 675 676 return label[0]; 677 678 } 679 680 } 681 682 //該類負責調整至適合屏幕顯示 683 684 public static class FitOverviewListener implements ItemBoundsListener { 685 686 private Rectangle2D m_bounds = new Rectangle2D.Double(); 687 688 private Rectangle2D m_temp = new Rectangle2D.Double(); 689 690 private double m_d = 15; 691 692 public void itemBoundsChanged(Display d) { 693 694 d.getItemBounds(m_temp); 695 696 GraphicsLib.expand(m_temp, 25/d.getScale()); 697 698 699 700 double dd = m_d/d.getScale(); 701 702 double xd = Math.abs(m_temp.getMinX()-m_bounds.getMinX()); 703 704 double yd = Math.abs(m_temp.getMinY()-m_bounds.getMinY()); 705 706 double wd = Math.abs(m_temp.getWidth()-m_bounds.getWidth()); 707 708 double hd = Math.abs(m_temp.getHeight()-m_bounds.getHeight()); 709 710 if ( xd>dd || yd>dd || wd>dd || hd>dd ) { 711 712 m_bounds.setFrame(m_temp); 713 714 DisplayLib.fitViewToBounds(d, m_bounds, 0); 715 716 } 717 718 } 719 720 } 721 722 723 724 } 725 726 // end of class GraphView
網格視圖:
蜂窩狀視圖:
平衡樹型視圖:
以上介紹了Prefuse的一些特征,模型結構以及自帶Demo GraphView.java的理解,后續會繼續研究Prefuse的其他Demo以及主要接口。
本文鏈接http://www.cnblogs.com/bigdataZJ/p/VisualizationSoloShow3.html
友情贊助
如果你覺得博主的文章對你那么一點小幫助,恰巧你又有想打賞博主的小沖動,那么事不宜遲,趕緊掃一掃,小額地贊助下,攢個奶粉錢,也是讓博主有動力繼續努力,寫出更好的文章^^。
1. 支付寶 2. 微信