前篇回顧:上篇《漫談可視化Prefuse(二)---一分鍾學會Prefuse》主要通過一個Prefuse的具體實例了解了構建一個Prefuse application的具體步驟。一個Prefuse Application需要經過數據導入(文本數據、數據庫)->Prefuse數據結構接收數據->注冊各種效果的Actions->渲染Renderer->交互展現Display的流程。
摸清了Prefuse那些看似眼花繚亂的框架結構,剩下的就是抽絲剝繭,順藤摸瓜,結合Manual和API掀開Prefuse的神秘面紗。首先從Prefuse的數據結構開始:
1.prefuse.data:該包主要包含了接口:Node(點)、Edge(邊)、Tuple(元組)
類:CascadedTable(級聯表)、Graph(圖)、Schema(模式)、SpanningTree(生成樹)、Table(表)、Tree(樹)。每個結構都有自己的屬性和方法,下面列舉了一些接口和類的主要成員:
Edge: 方法:Node getAdjacentNode(Node n) 返回給定節點的相鄰節點集合 方法:Graph getGraph() 返回改變所在圖形對象graph 方法:Node getSourceNode()/getTargetNode() 返回源節點、目標節點 方法:boolean isDirected() 判斷有向圖還是無向圖 Node: Graph getGraph() 返回節點所在的圖形graph int getInDegree() 返回節點入度個數 int getOutDegree() 返回節點出度個數 int getDegree() 返回節點度數 java.util.Iterator inEdges() 返回指向該節點的邊的迭代器 java.util.Iterator outEdges() 返回從節點指出的邊的迭代器 java.util.Iterator edges() 返回邊的迭代器 java.util.Iterator inNeighbors() 返回所有鏈入該節點的迭代器 java.util.Iterator outNeighbors() Get an iterator over all adjacent nodes connected to this node by an outgoing edge (i.e., all nodes "pointed" to by this one). 返回所有鏈出該節點的迭代器 java.util.Iterator neighbors() 返回所有近鄰節點的迭代器 Node getParent() 返回父節點 Edge getParentEdge() 返回父邊 int getDepth() 返回深度(根節點深度為0) int getChildCount() 返回子節點個數 int getChildIndex(Node child) 返回節點索引值 Node getChild(int idx) 根據序號獲得相應節點 Node getFirstChild() 獲得第一個子節點 Node getLastChild() 獲得最后一個子節點 Node getPreviousSibling() 得到上一個兄弟節點 Node getNextSibling() 得到下一個兄弟節點 java.util.Iterator children() 獲得孩子節點的迭代器 java.util.Iterator childEdges() 獲得子邊集合的迭代器 Tuple: Schema getSchema() 返回tuple數據概要 Table getTable() 返回tuple所在圖表 int getRow() 返回tuple所在行數 Graph: 構造方法: public Graph()創建一個空的無向圖; public Graph(boolean directed)true為有向,false為無向; public Graph(Table nodes, Boolean directed)根據給定節點集合創建有向/無向圖; public Graph(Table nodes, boolean directed, java.lang.String nodeKey, java.lang.String sourceKey,java.lang.String targetKey) nodeKey起到標示作用,如果為null,則默認使用row numbers(行號) public Graph(Table nodes, Table edges,Boolean directed) public Graph(Table nodes, Table edges, boolean directed, java.lang.String sourceKey, java.lang.String targetKey) 還有一些常用方法如添加刪除節點或邊等。 Schema: Schema類是表示一個表格的列,其屬性包括列名、數據類型、默認值。 其構造方法有: Schema() Schema(int ncols) Schema(java.lang.String[] names, java.lang.Class[] types) Schema(java.lang.String[] names, java.lang.Class[] types, java.lang.Object[] defaults) 主要方法有添加刪除列,獲取列名,鎖定Schema對象等。 Table: 表格是由一系列行和列數組組成的,每一行即為一個數據記錄,每一列是由指定數據域和數據類的數據組成。表格的數據可以直接通過使用行數和列名稱進行訪問。 表格的行可以插入和刪除。 Table的構造方法: Table() Table(int nrows, int ncols) protected Table(int nrows, int ncols, java.lang.Class tupleType) 其主要包含添加刪除行或列 Tree: 構造方法如下: Tree() Tree(Table nodes, Table edges) Tree(Table nodes, Table edges, java.lang.String sourceKey, java.lang.String targetKey) Tree(Table nodes, Table edges, java.lang.String nodeKey, java.lang.String sourceKey, java.lang.String targetKey) 其方法主要有獲取刪除子節點、父節點、根節點
2.prefuse.data下還有一些包如:
prefuse.data.column
該包中主要介紹了列屬性中可以有不同類型的列值,如BooleanColumn、DateColumn表示列中存儲布爾類型和日期類型的值。列類的抽象基類是AbstractColumn。
prefuse.data.event
該包主要包含一些監聽類,比如ColumnListener、ExpressionListener等分別表示針對不同對象的監聽。
3.Prefuse還至於一些表達式的應用以及Prefuse對於各類表達式的解析
prefuse.data.expression(表達式)
該包主要包含了Prefuse關於表達式用法的類,有AndPredicate、NotPredicate、OrPredicate、ArithmeticExpression、BinaryExpression等分別表示AndPredicate(與)、OrPredicate(或)、NotPredicate(非)、ArithmeticExpression(算術表達式)、BinaryExpression(二進制表達式)等。
prefuse.data.expression.parser(表達式解析)
該包包含如何解析表達式Expression的類。
Prefuse支持的表達式涵蓋很全面:
支持操作符運算、流程控制如:”x+y“(加運算)、"x^y"(平方運算)、”x>y“(比較運算)、”IF test THEN x ELSE
y“(if-then-else流程)等等;
一般常用函數如:"ISNODE()"(判斷當前Tuple是否是一個節點)、”DEGREE()“(如果當前Tuple圖中節點,返回該節點的度數)、”TREEDEPTH()“(如果當前Tuple是圖中節點,則返回該節點在書中的深度)等;
數學運算如:"ABS(x)"(絕對值運算)、”SIN(x)“(正弦運算)、"FLOOR(x)"(向下取整)、”SUM(a,b,c...)“(求和運算)等等;
常用字符串運算如:”CAP(str)“(首字母大寫)、”REPEAT(str,count)“(字符串替換操作)、”REVERSE(str)“(反轉字符串)等等;
顏色控制函數如:”RGB(r,g,b)“(RGB模式顏色賦值)、”GRAY(v)“(灰度值模式顏色賦值)等;
綜上相關表達式的支持,大大增強了Prefuse的靈活性,豐富了Prefuse的一些操作功能。
4.有關包prefuse.data.io以及prefuse.data.io.sql已經在《漫談可視化Prefuse(一)---從SQL Server數據庫讀取數據》通過離子闡述過。
但是為了提高用戶可操作性,也為了熟悉Java圖形編程如何進行界面之間的傳值,對上面文章中的例子進行了改進,將部分參數如端口號、數據庫用戶名、密碼等信息開放給用戶填寫,提高程序的可操作性和靈活性,后期實際開發還會能夠讓用戶在多中數據庫之間切換。
我的界面傳值思路:首先創建一個接收和讀取數據的對象config,在填寫參數的界面中畫出文本框供用戶填寫參數,在完成按鈕中添加監聽事件,將所填信息賦給對象config,並執行在父界面中畫出讀取數據構成的圖形,代碼如下:
ConnectionToDB.java
public class ConnectionToDB{ public static Visualization vis = new Visualization(); public static Config config = new Config();//存儲、獲取參數對象
public static JFrame jf = new JFrame(); //標簽顯示
public static JLabel nodeLabel = new JLabel("節點查詢:"); public static JLabel edgeLabel = new JLabel("邊查詢:"); public static JLabel strConfigLabel = new JLabel("配置字符串:"); public static JLabel databaseNameLabel = new JLabel("數據庫名稱:"); public static JLabel usernameLabel = new JLabel("用戶名:"); public static JLabel passwordLabel = new JLabel("密碼:"); public static JLabel portNumberLabel = new JLabel("端口號:"); //文本框
public static JTextField nodeText = new JTextField(); public static JTextField edgeText = new JTextField(); public static JTextField strConfigText = new JTextField(); public static JTextField databaseNameText = new JTextField(); public static JTextField usernameText = new JTextField(); public static JTextField passwordText = new JTextField(); public static JTextField portNumberText = new JTextField(); /** * @param args * @throws DataIOException */
public static void main(String[] args) throws DataIOException { //1.構建顯示畫面 2.填寫參數配置 3.配置傳值到主界面 4.主界面圖形展示 //-----------1、構建主界面-----------
JMenu dataMenu = new JMenu("數據導入");//添加菜單按鈕
final JMenuItem dataItem = new JMenuItem("連接數據庫"); dataItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { final JFrame second = new JFrame("second"); second.setSize(400,400); second.setLayout(null); strConfigLabel.setBounds(50, 20, 80, 30); strConfigText.setBounds(130, 20, 150, 30); second.add(strConfigLabel); second.add(strConfigText); databaseNameLabel.setBounds(50, 60, 80, 30); databaseNameText.setBounds(130, 60, 150, 30); second.add(databaseNameLabel); second.add(databaseNameText); usernameLabel.setBounds(50, 100, 80, 30); usernameText.setBounds(130, 100, 150, 30); second.add(usernameLabel); second.add(usernameText); passwordLabel.setBounds(50, 140, 80, 30); passwordText.setBounds(130, 140, 150, 30); second.add(passwordLabel); second.add(passwordText); portNumberLabel.setBounds(50, 180, 80, 30); portNumberText.setBounds(130, 180, 150, 30); second.add(portNumberLabel); second.add(portNumberText); nodeLabel.setBounds(50, 220, 80, 30); nodeText.setBounds(130, 220, 150, 30); second.add(nodeLabel); second.add(nodeText); edgeLabel.setBounds(50, 260, 80, 30); edgeText.setBounds(130, 260, 150, 30); second.add(edgeLabel); second.add(edgeText); JButton ok = new JButton("完成配置"); ok.setBounds(70, 300, 100, 30); second.add(ok); second.setVisible(true); ok.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if(nodeText.getText()!=null && edgeText.getText()!=null){ configPass(); System.out.println("node:"+ config.getNodeSql()); try { actionEvent(); // second.dispose();這種關閉方式也可以
second.dispatchEvent(new WindowEvent(second,WindowEvent.WINDOW_CLOSING));//關閉子窗口
visual(); } catch (DataIOException e1) { e1.printStackTrace(); } } } }); } }); JMenuBar bar = new JMenuBar(); bar.add(dataMenu); dataMenu.add(dataItem); jf.setSize(new Dimension(800, 600)); jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); jf.setJMenuBar(bar); // jf.pack();
jf.setVisible(true); } /** * 存儲配置界面用戶輸入的值 */
private static void configPass() { config.setNodeSql(nodeText.getText()); config.setEdgeSql(edgeText.getText()); config.setStrConfig(strConfigText.getText()); config.setDatabaseName(databaseNameText.getText()); config.setUsername(usernameText.getText()); config.setPassword(passwordText.getText()); config.setPortNumber(portNumberText.getText()); } /** * 連接數據庫並添加相應效果渲染和動作 * @throws DataIOException */
public static void actionEvent() throws DataIOException{ String driver = config.getStrConfig().toString(); String url = "jdbc:sqlserver://localhost:"+config.getPortNumber().toString()+";DatabaseName="+config.getDatabaseName().toString()+""; String username = config.getUsername().toString(); String password = config.getPassword().toString(); DatabaseDataSource dbds = null; try { dbds = ConnectionFactory.getDatabaseConnection(driver, url, username, password); } catch (SQLException e1) { e1.printStackTrace(); } catch (ClassNotFoundException e1) { e1.printStackTrace(); } System.out.println(config.getEdgeSql()); Table nodes = dbds.getData(config.getNodeSql().toString()); Table edges = dbds.getData(config.getEdgeSql().toString()); Graph graph = new Graph(nodes, edges, false, "id", "sid", "tid"); vis.add("graph", graph); LabelRenderer label = new LabelRenderer("name"); label.setRoundedCorner(10, 10); vis.setRendererFactory(new DefaultRendererFactory(label)); int[] palette = new int[]{ColorLib.rgb(255, 180, 180),ColorLib.rgb(190, 190, 255)}; DataColorAction fill = new DataColorAction("graph.nodes" , "gender" , Constants.NOMINAL, VisualItem.FILLCOLOR,palette); ColorAction text = new ColorAction("graph.nodes", VisualItem.TEXTCOLOR, ColorLib.gray(0)); ColorAction edges1 = new ColorAction("graph.edges", VisualItem.STROKECOLOR, ColorLib.gray(200)); ActionList color = new ActionList(); color.add(fill); color.add(text); color.add(edges1); ActionList layout = new ActionList(Activity.INFINITY); layout.add(new ForceDirectedLayout("graph")); layout.add(new RepaintAction()); vis.putAction("color", color); vis.putAction("layout", layout); } /** * 添加控制器,顯示圖形 */
public static void visual(){ Display display = new Display(vis); display.setSize(700, 600); display.pan(250, 250); display.addControlListener(new DragControl()); display.addControlListener(new PanControl()); display.addControlListener(new ZoomControl()); display.addControlListener(new WheelZoomControl()); display.addControlListener(new FocusControl(1)); display.addControlListener(new ZoomToFitControl()); jf.add(display); vis.run("color"); vis.run("layout"); } }
Config.java:
public class Config { public String nodeSql; public String edgeSql; public String strConfig; public String databaseName; public String username; public String password; public String portNumber; public String getStrConfig() { return strConfig; } public void setStrConfig(String strConfig) { this.strConfig = strConfig; } public String getDatabaseName() { return databaseName; } public void setDatabaseName(String databaseName) { this.databaseName = databaseName; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getPortNumber() { return portNumber; } public void setPortNumber(String portNumber) { this.portNumber = portNumber; } public String getNodeSql() { return nodeSql; } public void setNodeSql(String nodeSql) { this.nodeSql = nodeSql; } public String getEdgeSql() { return edgeSql; } public void setEdgeSql(String edgeSql) { this.edgeSql = edgeSql; } }
具體圖形如下:
(1)帶有功能菜單的父界面:
(2)參數配置界面:
(3)填寫配置參數界面:
(4)圖形顯示在父界面並關閉配置窗口:
因為之前對於Swing和AWT編程不是很熟悉,考慮在界面傳值也可以做,但是對於多個字符串的傳值可能邏輯比較復雜,這里采用一個類Config用來封裝數據從而完成數據的存儲和讀取的工作。
本文鏈接《漫談可視化Prefuse(三)---Prefuse API數據結構閱讀有感》
http://www.cnblogs.com/bigdataZJ/p/VisualizationPrefuse3.html
后續將繼續API之路,了解Prefuse使用的套路,先順着它,依着它,摸清它的脾性后再一舉拿下它^_^
友情贊助
如果你覺得博主的文章對你那么一點小幫助,恰巧你又有想打賞博主的小沖動,那么事不宜遲,趕緊掃一掃,小額地贊助下,攢個奶粉錢,也是讓博主有動力繼續努力,寫出更好的文章^^。
1. 支付寶 2. 微信