• 使用Gephi-Toolkit绘制动态网络图


    使用Gephi绘制动态网络图

    研究课题是关于网络演化的,需要绘制网络动态的演化图,这里主要是边的演化,不同的边有不同的时间。虽然原本的Gephi有动态图的展示,但是Gephi功能太有限了,对图的颜色,节点大小等支持都不够,所以我这里采用Python+Gephi-Toolkit+Premire的方式完成绘制。这里重点在于Python和Gephi-ToolKit这两个,Premire只是将生成的不同时刻的Pdf文件合并成一个视频。

    Python处理原始数据

    我们的原始数据是一个三列的邻接表数据,三列分别为node1, node2, time,记录了一条边的时间。

    0 608 2
    1 248 1
    1 466 1
    1 586 1
    2 262 1
    3 263 1
    

    Gephi识别的gexf格式的文件的格式,关于这个文件格式可在这个网页找到说明 https://gephi.org/gexf/format/

    本质上就是一个xml文件,所以我们使用python中的lxml库来构建,操作xml,根据原始数据,生成相应的gexf文件。lxml的API见https://lxml.de/tutorial.html

    首先我们根据原始数据生成一个edge_t字典,key为元组形式的边,value为边的生成时间:

    # 构造edge_t词典
    def plot_graph_static(data_path, out_path):
        edge_t = dict()
        with open(data_path, 'r') as f:
            for line in f:
                s = line.strip().split(' ')
                edge_t[(int(s[0]), int(s[1]))] = int(s[2])
            print("Edge_t complete!")
    

    接下来就是构造gexf了,按照xml树形结构的方式构建即可。注意

    • gexf标签属性'mode': 'static', 'defaultedgetype': 'undirected'即可

    • 节点不要重复

    • 需要给边额外增加一个属性,且这个属性和边的权重的值都为边的生成时间(从1开始),要想增加属性,应该在graph标签下新增一个class为edge的attributes标签:

      <?xml version='1.0' encoding='UTF-8'?>
      <gexf xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.gexf.net/1.3" version="1.3">
        <graph mode="static" defaultedgetype="undirected">
          <attributes class="edge">
            <attribute id="0" title="StartTime" type="string"/>
          </attributes>
      

      然后在每个edge标签下新增一个attvalues标签:

      <edge source="0" target="608" weight="2">
          <attvalues>
              <attvalue for="0" value="2"/>
          </attvalues>
      </edge>
      

      for属性的值对应之前attribute标签的id属性。之所以这么设置,是因为后面Gephi-Toolkit需要使用。

    最终生成gexf文件的代码如下:

    # xml 编写
        nsmap = {'xsi': 'http://www.w3.org/2001/XMLSchema-instance'}
        gexf = etree.Element('gexf', nsmap=nsmap)
        gexf.set('xmlns', 'http://www.gexf.net/1.3')
        gexf.set('version', '1.3')
    
        graph = etree.SubElement(gexf, 'graph', attrib={'mode': 'static', 'defaultedgetype': 'undirected'})
        
        attributes = etree.SubElement(graph, 'attributes', attrib={'class': 'edge'})
        edge_attr = etree.Element('attribute', attrib={'id': '0', 'title': 'StartTime', 'type': 'string'})
        attributes.append(edge_attr)
    
        nodes = etree.SubElement(graph, 'nodes')
        edges = etree.SubElement(graph, 'edges')
    
        node_list = []  # 保证节点不重复
        for edge in edge_t.keys():
            if edge[0] not in node_list:
                node_list.append(edge[0])
                xml_node = etree.Element('node', attrib={'id': str(edge[0]), 'label': str(edge[0])})
                nodes.append(xml_node)
            if edge[1] not in node_list:
                node_list.append(edge[1])
                xml_node = etree.Element('node', attrib={'id': str(edge[1]), 'label': str(edge[1])})
                nodes.append(xml_node)
            xml_edge = etree.Element('edge', attrib={'source': str(edge[0]), 'target': str(edge[1]),
                                                     'weight': str(edge_t[edge])})  # gephi中边权重不能<=0
            attvalues = etree.SubElement(xml_edge, 'attvalues')
            attvalue = etree.Element('attvalue', attrib={'for':'0', 'value':str(edge_t[edge])})
            attvalues.append(attvalue)
            edges.append(xml_edge)
        gexf_tree = etree.ElementTree(gexf)
        gexf_tree.write(out_path, pretty_print=True, xml_declaration=True, encoding='utf-8')
    

    以上就是主要的代码了,我们记生成的文件为static.gexf,但是关于我们课题,我们需要比较两种不同的演化,所以这两种演化需要相同的布局,而且布局还要好看些,所以我们用Gephi打开static.gexf然后调整布局,节点大小,颜色(关键是布局,后面两个随便),然后输出图文件为gexf格式,命名为static_move.gexf

    然后根据这个布局好了的static_move.gexf,我们根据同一个网络的另一个原始数据(时间不同,之前的是原始时间,这个是我们预测的时间),修改static_move.gexf里面的内容,注意

    • 在获取标签时要注意xml是有命名空间的,<gexf xmlns="http://www.gexf.net/1.3" version="1.3" xmlns:viz="http://www.gexf.net/1.3/viz" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.gexf.net/1.3 http://www.gexf.net/1.3/gexf.xsd">在使用iter迭代标签时要主要标签前需要有命名空间的部分,例如viz标签:

      <node id="608" label="608">
          <viz:size value="100.0"></viz:size>
          <viz:position x="-2221.5906" y="-979.51196"></viz:position>
          <viz:color r="46" g="0" b="18"></viz:color>
      </node>
      

      我们迭代时就需要这么写

      node.iter('{' + cur_nsmap['viz'] + '}' + 'color')

      即使是没有命名空间的标签,也是采用默认的命名空间的,迭代时这么写

      root.iter('{' + cur_nsmap[None] + '}' + 'node')

    • 主要利用lxml对属性的set和get函数修改属性值。

    所以这个在gexf文件中修改边时间的的代码:

    def transfer_gexf(data_path, pattern_path, out_path):
        """
            根据pattern_path里的节点位置,大小等信息,结合datapath的时间信息,重新生成一个gexf文件
        """
        print("Transfering graph...")
        # 构造edge_t词典
        edge_t = dict()
        node_t = dict()
        with open(data_path, 'r') as f:
            for line in f:
                s = line.strip().split(' ')
                edge_t[(int(s[0]), int(s[1]))] = int(s[2])
                if int(s[0]) not in node_t.keys():
                    node_t[int(s[0])] = int(s[2])
                if int(s[1]) not in node_t.keys():
                    node_t[int(s[1])] = int(s[2])
            print("Edge_t complete!")
    
        with open(pattern_path, 'r') as f:
            gexf_tree = etree.parse(f)
            root = gexf_tree.getroot()
            cur_nsmap = root.nsmap
            for edge in root.iter('{' + cur_nsmap[None] + '}' + 'edge'):
                # print(edge)
                cur_edge_t = str(edge_t[(int(edge.get('source')), int(edge.get('target')))])
                edge.set('weight', cur_edge_t)
                for att in edge.iter('{' + cur_nsmap[None] + '}' + 'attvalue'):
                    att.set('value' ,cur_edge_t)
            gexf_tree.write(out_path, pretty_print=True, xml_declaration=True, encoding='utf-8')
    

    生成的gexf记为predict_static.gexf。我们最后是需要把predict_static.gexfstatic_move.gexf都经过Gephi-Toolkit处理,然后生成一堆pdf文件的。所以流程都一样,只不过文件命名要区分一下。

    Gephi-Toolkit处理gexf文件

    我利用gephi-toolkit-demos来了解这个工具,API为https://gephi.org/gephi-toolkit/0.9.2/apidocs/

    首先这是一个java程序,我们安装java1.8, 安装idea集成开发工具,下载maven(一个包管理工具),我参考https://www.jb51.net/article/122326.htm的前面部分内容进行了配置。配置完了后用idea打开gephi-toolkit-demos,右侧maven里面点clean,install安装需要的包。ps:我2020年4月时用还好好的,但是8月份我换电脑后移植过来就install失败,说有两个包在中央仓库(阿里云)找不到。(((φ(◎ロ◎;)φ))),所以我就把我原来电脑上的仓库拷贝复制到新电脑上了。

    demo代码使用很简单,运行Main.java即可:

    // Main.java
    package org.gephi.toolkit.demos;
    
    public class Main {
    
        public static void main(String[] args) {
            MineDynamic mineDynamic = new MineDynamic();
            mineDynamic.script();
        }
    }
    

    后面注释掉了原本的内容没有展示,MineDynamic是我自己根据demo新创建的类。

    MineDynamic.java有6部分,

    1. 初始化,导入文件,准备工作(其实不是很懂,但主要改动就是导入的文件路径而已,其他不用管)

      public void script() {
              //Init a project - and therefore a workspace
              ProjectController pc = Lookup.getDefault().lookup(ProjectController.class);
              pc.newProject();
              Workspace workspace = pc.getCurrentWorkspace();
      
              //Import file
              ImportController importController = Lookup.getDefault().lookup(ImportController.class);
              Container container;
              try {
                  File file = new File(getClass().getResource("/org/gephi/toolkit/demos/bacteria/static_move.gexf").toURI());
                  container = importController.importFile(file);
                  container.getLoader().setEdgeDefault(EdgeDirectionDefault.UNDIRECTED);
              } catch (Exception ex) {
                  ex.printStackTrace();
                  return;
              }
              //Append imported data to GraphAPI
              importController.process(container, new DefaultProcessor(), workspace);
      
              //Prepare
              GraphModel graphModel = Lookup.getDefault().lookup(GraphController.class).getGraphModel();
              AppearanceController appearanceController = Lookup.getDefault().lookup(AppearanceController.class);
              AppearanceModel appearanceModel = appearanceController.getModel();
              FilterController filterController = Lookup.getDefault().lookup(FilterController.class);
      
              UndirectedGraph originGraph = graphModel.getUndirectedGraph();
              System.out.println("OriginNodes: " + originGraph.getNodeCount());
              System.out.println("OriginEdges: " + originGraph.getEdgeCount());
      
    2. 根据id为‘0’的属性划分边

    //Partition with '0' column, which is in the data
            Column column = graphModel.getEdgeTable().getColumn("0");
            Function func = appearanceModel.getEdgeFunction(originGraph, column, PartitionElementColorTransformer.class);
            Partition partition = ((PartitionFunction) func).getPartition();
            System.out.println(partition.size() + " partitions found");
    

    这里就用到之前生成gexf文件时,构建的edge的StartTime属性,id为‘0’.

    1. 根据划分的个数设置颜色(不同时刻边的颜色)

      Object[] colors;
      colors = GenColorBarItemByHSL(Color.CYAN, partition.size());
      for (int p = 0; p < partition.size(); p++) {
          System.out.println(p);
          partition.setColor("" + (p + 1), (Color) colors[p]);
      }
      appearanceController.transform(func);
      
      private Color[] GenColorBarItemByHSL(Color startColor, int num) {
              float[] hsb = Color.RGBtoHSB(startColor.getRed(), startColor.getGreen(), startColor.getBlue(), null);
              float hue = hsb[0];
              float saturation = hsb[1];
              float brightness = hsb[2];
              Color[] colorList = new Color[num];
              for (int i = 0; i < num; i++)
              {
                  Color vColor = Color.getHSBColor((hue + (float)(i) / (float)(num)) % 1, saturation, brightness);
                  colorList[i] = vColor;
              }
              return colorList;
          }
      
    2. 对于每一个时刻, 根据边权重(和那个属性一样的值),过滤掉其他时刻的边,只显示当前时刻的边

      for (double i = 1; i < partition.size() + 1; i++) {
          //Filter by weight
          EdgeWeightBuilder.EdgeWeightFilter edgeWeightFilter = new EdgeWeightBuilder.EdgeWeightFilter();
          edgeWeightFilter.init(graphModel.getGraph());
          edgeWeightFilter.setRange(new Range(0.0, i));     //Remove nodes with degree < 10
          Query query = filterController.createQuery(edgeWeightFilter);
          GraphView view = filterController.filter(query);
          graphModel.setVisibleView(view);    //Set the filter result as the visible view
          //Count nodes and edges on filtered graph
          UndirectedGraph graph = graphModel.getUndirectedGraphVisible();
          System.out.println("Time:" + i + "Nodes: " + graph.getNodeCount() + " Edges: " + graph.getEdgeCount());
      
      
    3. 对于每一个时刻,因为不同权重的边生成图后边的粗细不同,所以我们需要存储当前的权重,然后把图的所有边权重都设为1,绘制完图后在将边的权重还原。

    4. 对于每一个时刻,设置输出图的一些属性(边宽),并输出成pdf

      //Rank color by Degree(Set all node to Red)
      Function degreeRanking = appearanceModel.getNodeFunction(graph, AppearanceModel.GraphFunction.NODE_DEGREE, RankingElementColorTransformer.class);
      RankingElementColorTransformer degreeTransformer = (RankingElementColorTransformer) degreeRanking.getTransformer();
      degreeTransformer.setColors(new Color[]{new Color(0xFF0000), new Color(0xFF0000)});
      degreeTransformer.setColorPositions(new float[]{0f, 1f});
      appearanceController.transform(degreeRanking);
      
      //reset edge weight 1
      Vector edgeWeights = new Vector();
      Column weightCol = graphModel.getEdgeTable().getColumn("weight");
      for (Edge n : graphModel.getGraph().getEdges()) {
          edgeWeights.add(n.getAttribute(weightCol));
          n.setAttribute(weightCol, new Double(1.0f));
      }
      
      //Preview
      PreviewModel model = Lookup.getDefault().lookup(PreviewController.class).getModel();
      model.getProperties().putValue(PreviewProperty.BACKGROUND_COLOR, Color.BLACK);
      model.getProperties().putValue(PreviewProperty.SHOW_NODE_LABELS, Boolean.FALSE);
      model.getProperties().putValue(PreviewProperty.EDGE_COLOR, new EdgeColor(EdgeColor.Mode.ORIGINAL));
      model.getProperties().putValue(PreviewProperty.EDGE_THICKNESS, new Float(20f));
      model.getProperties().putValue(PreviewProperty.EDGE_RESCALE_WEIGHT, Boolean.TRUE);
      model.getProperties().putValue(PreviewProperty.NODE_LABEL_FONT, model.getProperties().getFontValue(PreviewProperty.NODE_LABEL_FONT).deriveFont(1));
      //            model.getProperties().putValue(PreviewProperty.NODE_OPACITY, new Float(50f));
      model.getProperties().putValue(PreviewProperty.NODE_BORDER_COLOR, new DependantColor(Color.RED));
      model.getProperties().putValue(PreviewProperty.NODE_BORDER_WIDTH, new Float(1f));
      
      //Export
      ExportController ec = Lookup.getDefault().lookup(ExportController.class);
      try {
          ec.exportFile(new File("./Result/bacteria/bacteria" + i + ".pdf"));
      } catch (IOException ex) {
          ex.printStackTrace();
          return;
      }
      
      //restore edge weight
      for (Edge n : graphModel.getGraph().getEdges()) {
          n.setAttribute(weightCol, edgeWeights.firstElement());
          edgeWeights.remove(0);
      }
      

    要想理解这些代码,最好还是跑下demo,看下代码,慢慢才能理解(但是Gephi-Toolkit有些看似能工作的代码其实没有效果,比如看上去我可以直接根据weight划分边嘛,为什么多此一举还要自己新建一个属性呢,我试过了,没用,这是我踩过的最大的坑了。)
    8-6注:刚刚重新打开Gephi发现如果在gexf文件中边加了属性,可以直接通过软件改变边的颜色:

    在软件右侧也可以直接通过过滤功能根据Weight过滤边。

    或许某种程度上简单的画图就不需要Gephi-Toolkit了。

  • 相关阅读:
    有序表的合并---顺序表实现
    有序表的合并---链表实现
    双向链表操作
    有序表的合并-用链表操作
    C语言单链表操作
    c语言顺序表操作
    c语言 取余 % 和除法 / 的应用技巧 (在取位数方面的)
    JS打印表格(HTML定义格式)
    富文本编辑器(php)
    利用formdata异步上传图片并预览图片
  • 原文地址:https://www.cnblogs.com/eggplant-is-me/p/13439232.html
Copyright © 2020-2023  润新知