漫談可視化Prefuse(六)---改動源碼定制邊粗細


  可視化一路走來,體會很多;博客一路寫來,收獲頗豐;代碼一路碼來,思路越來越清晰。終究還是明白了一句古話:紙上得來終覺淺,絕知此事要躬行。

  跌跌撞撞整合了個可視化小tool,零零碎碎結交了眾多的志同道合之人,迷迷糊糊創建了我"可視化/Prefuse"的QQ群,詳情可查看左側的公告部分。此群旨在結實更多的可視化領域的同仁,也希望可以成為一個開源項目分享與閱讀的平台。源於開源,開源為我們提供了助我們快速成長的養分,開源的每一行代碼都是前輩們心血的結晶,開源是智慧的濃縮。

  在博客園寫博客也是如此,從起初只是一味的平鋪直敘,直抒胸臆,從來沒有估計到還要格式調整,也沒有想到要布局美觀,更不用提什么言簡意賅、一針見血。總之,看看優秀的博客,就如同接觸到一個開源項目,其開篇布局、文章內容、文本樣式都是學習的教材。最近體會比較深的就是:寫博客也體現出博客的特性,博客,不是論文,過多或者過於復雜的理論闡述(除非必不可少)會讓大部分讀者兩眼昏花、四肢無力;博客,也不是小學造句子,過短或過少缺乏整體的連貫性,剛讀起來有點感覺,好吧,還沒高潮就結束了,讓人摸不着頭腦。所以,我發現,但凡獲得點贊多的文章除了內容含金量高以外,還贏在表述以及篇幅的控制,真正站在Reader的角度去闡述問題、解決問題,Reader就會控制不住要點個贊。In a Word,路很長,要學的東西很多,慢慢體味。

 

  上期回顧:《漫談可視化Prefuse(五)---一款屬於我自己的可視化工具》主要介紹了自己開發的一款可視化工具,包含了可視化工具中一些必不可少的功能,有些網友反應期待web版,其實Prefuse是支持applet版本的,這里沒有走web的原因可能還是根據我們的需求走。要知道在以js為堅實后盾的各種可視化庫大行其道的情況下,類似於Gephi、IBM i2等產品仍然具有不可撼動的地位的原因,在於需求催生產品,不同的需求就會對應着不同的產品形態。還有些網友反映披露工具相關細節,下面正文將會有所體現。

 

  本篇主要立足於Prefuse框架講述:1.可視化工具如何根據需求調整邊的粗細;2.try...catch的巧用

 

  1.如何通過邊的粗細來反映邊的權重

  Prefuse框架默認邊的粗細是統一的,都為1。但是我們往往面臨的需求是給出數據格式如下

Source Target Weight
1   2 0.5

  那么我們就可以通過邊的粗細來體現這個Weight屬性,用來表明節點1和節點2之間的親密程度。

  所以,我們針對Prefuse的源碼做一下改動,在此之前,我們先了解有關類的繼承關系:

  類LabelRenderer:

public class LabelRenderer extends AbstractShapeRenderer {
    ......
    public void render(Graphics2D g, VisualItem item) {
		RectangularShape shape = (RectangularShape) getShape(item);
		if (shape == null)
			return;
                ......
    }
    ......  
}

  類EdgeRenderer:

public class EdgeRenderer extends AbstractShapeRenderer {
    ......
    public void render(Graphics2D g, VisualItem item) {
        // render the edge line
        super.render(g, item);
        ......
    }
    ......
}

  抽象類AbstractShapeRenderer:

public abstract class AbstractShapeRenderer implements Renderer {
    ......
    public void render(Graphics2D g, VisualItem item) {
	Shape shape = getShape(item);
        ......
    }
    ......
}    

  從以上三個類可以發現,類LabelRenderer和EdgeRenderer都是繼承自抽象類AbstractShapeRenderer,並且LabelRenderer和EdgeRenderer類都覆寫了父類 render方法。

  兩個類的render方法的最大不同之處在於,EdgeRenderer類顯式調用了抽象父類AbstractShapeRenderer的render方法,並且通過在AbstractShapeRenderer類的render方法中加入:

System.out.println("該item屬於:" + item.getGroup())

  得到的打印信息都是"該item屬於:graph.edges",即表明都是對於邊的渲染。

 

  了解了類的繼承關系,下面給出修改源碼的步驟以實現通過配置文件實現邊粗細的賦值:

  (1)在PrefuseConfig類中添加:

setProperty("data.edge.weight", "weight");

  用於xml文件中確定邊粗細的標示,這里是weight;

 

  (2)在Graph類中添加:

public static final String WEIGHT = PrefuseConfig.get("data.edge.weight");

  用於在GraphMLReader獲取常量;

 

  (3)在GraphMLReader中添加:

public static final String WEIGHT = Graph.WEIGHT;
public static final String WEIGHTID = WEIGHT + '_' + ID;

  方法startDocument()中添加:

m_esch.addColumn(WEIGHT, String.class);//邊粗細相關
m_esch.addColumn(WEIGHTID, String.class);

  方法startElement()中添加:

m_edges.setString(m_row, WEIGHT, atts.getValue(WEIGHT));
m_edges.setString(m_row, WEIGHTID, atts.getValue(WEIGHTID));

  

  (4)在AbstractShapeRenderer中添加:

EdgeItem edge = (EdgeItem)item;
VisualItem vi = (VisualItem)edge.getSourceNode();
item.setStrokeColor(vi.getFillColor());
String weight = null;
try {
	weight = item.get(GraphMLHandler.WEIGHT).toString();
     } catch (Exception e) {
       weight = "1";
}
item.setSize(Double.parseDouble(weight)*10);    

  

  使用的配置文件為xml格式,內容為:

<?xml version="1.0" encoding="UTF-8"?>
<!--  An excerpt of an egocentric social network  -->
<graphml xmlns="http://graphml.graphdrawing.org/xmlns">
<graph edgedefault="undirected">
 
<!-- data schema -->
<key id="name" for="node" attr.name="name" attr.type="string"/>
<key id="gender" for="node" attr.name="gender" attr.type="string"/>
<key id="id" for="node" attr.name="id" attr.type="string"/>
  
<!-- nodes -->  
<node id="1">
 <data key="name">Jeff</data>
 <data key="gender">M</data>
 <data key="id">1</data>
 </node>
<node id="2">
 <data key="name">Ed</data>
 <data key="gender">M</data>
 <data key="id">2</data>
</node>
<node id="3">
 <data key="name">Christiaan</data>
 <data key="gender">M</data>
 <data key="id">3</data>
</node>
<node id="4">
 <data key="name">Emily</data>
 <data key="gender">F</data>
 <data key="id">4</data>
</node>
<node id="5">
 <data key="name">Adam</data>
 <data key="gender">M</data>
 <data key="id">5</data>
</node>
<node id="6">
 <data key="name">Cynthia</data>
 <data key="gender">F</data>
 <data key="id">6</data>
</node>
<node id="7">
 <data key="name">Joylette</data>
 <data key="gender">F</data>
 <data key="id">7</data>
</node>
<node id="8">
 <data key="name">Amanda</data>
 <data key="gender">F</data>
 <data key="id">8</data>
</node>
<node id="9">
 <data key="name">Nathaniel</data>
 <data key="gender">M</data>
 <data key="id">9</data>
</node>

<!-- edges -->
<edge source="1" target="2" directed="true" weight="0.1"></edge>
<edge source="2" target="3" weight="0.2"></edge>
<edge source="3" target="1" weight="0.5"></edge>
<edge source="4" target="5" weight="0.2"></edge>
<edge source="5" target="6" weight="0.1"></edge>
<edge source="6" target="4" weight="0.2"></edge>
<edge source="7" target="8" weight="0.3"></edge>
<edge source="8" target="9" weight="0.1"></edge>
<edge source="9" target="7" weight="0.4"></edge>
<edge source="1" target="4" weight="0.1"></edge>
<edge source="4" target="7" weight="0.2"></edge>
<edge source="7" target="1" weight="0.1"></edge>
</graph>
</graphml>

  

  最終的圖形顯示為:

 

  2.try....catch的巧用

    巧用之處見1(4)中所寫:

EdgeItem edge = (EdgeItem)item;
VisualItem vi = (VisualItem)edge.getSourceNode();
item.setStrokeColor(vi.getFillColor());
String weight = null;
try {
	weight = item.get(GraphMLHandler.WEIGHT).toString();
     } catch (Exception e) {
       weight = "1";
}
item.setSize(Double.parseDouble(weight)*10);  

   因為在實際情況中,會遇到有些xml文件中沒有weight屬性,或者xml部分邊有部分沒有的情況,這時就會面臨在AbstractShapeRenderer中無法獲取weight值的情形,即

item.get(GraphMLHandler.WEIGHT)

   此值不存在或為空,起初通過添加語句:

if(item.get(GraphMLHandler.WEIGHT).toString().equals("") || item.get(GraphMLHandler.WEIGHT).toString() == null){
	weight = "1";
}else{
	weight = item.get(GraphMLHandler.WEIGHT).toString();
}

  等類似手段還是無法捕獲item.get()取不到值的所有情況,后面轉而想到,鑒於要捕獲的是為空異常,索性使用try...catch直接進行捕獲,捕獲后在進行處理。從而有了上面的處理,即當item.get()函數獲取不到值時就捕獲異常,轉入catch部分執行,在這里我們編寫我們想要實現的功能,也就是獲取為空時就將weight設置默認值為1,從而巧妙的解決了考慮所有為空的情況。

 

  從該源碼的經歷發現,需要明確自己想要的功能,明確需要觸及哪些類,明確如何保證修改后的源碼運行的完整性,不能因為修改一處而到處報錯;另外,還需要經常思考,靈活運行一些語句的特性為自己所用。今天就到這吧,如果覺得有用,記得點贊^_^,對可視化(gephi、prefuse、分布式計算、開源)感興趣的可以加群討論。

  本文鏈接:《漫談可視化Prefuse(六)---改動源碼定制邊粗細》

  

友情贊助

如果你覺得博主的文章對你那么一點小幫助,恰巧你又有想打賞博主的小沖動,那么事不宜遲,趕緊掃一掃,小額地贊助下,攢個奶粉錢,也是讓博主有動力繼續努力,寫出更好的文章^^。

    1. 支付寶                          2. 微信

                      

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM