diff --git a/modules/CPMplugin/README.md b/modules/CPMplugin/README.md new file mode 100644 index 000000000..dfba5628e --- /dev/null +++ b/modules/CPMplugin/README.md @@ -0,0 +1,4 @@ +## CPM plugin + +This README supports Markdown, see [syntax](https://help.github.com/articles/markdown-basics/) + diff --git a/modules/CPMplugin/pom.xml b/modules/CPMplugin/pom.xml new file mode 100644 index 000000000..72b9191f7 --- /dev/null +++ b/modules/CPMplugin/pom.xml @@ -0,0 +1,78 @@ + + + 4.0.0 + + gephi-plugin-parent + org.gephi + 0.10.0 + + + my.company + cpm-plugin + 1.0.0 + nbm + + CPM plugin + + + + org.gephi + gephi-toolkit + 0.10.0 + + + org.netbeans.api + org-openide-util-lookup + RELEASE160 + + + org.gephi + utils-longtask + 0.10.0 + + + org.gephi + graph-api + 0.10.0 + + + org.gephi + statistics-api + 0.10.0 + + + + + + + org.apache.netbeans.utilities + nbm-maven-plugin + + Apache 2.0 + Ebrahim Shami + qsomeis@gmail.com + + https://github.com/qfewzz/gephi-plugins + + + + + + + + + + + + oss-sonatype + oss-sonatype + https://oss.sonatype.org/content/repositories/snapshots/ + + true + + + + + + diff --git a/modules/CPMplugin/src/main/java/com/plugin/CPM.java b/modules/CPMplugin/src/main/java/com/plugin/CPM.java new file mode 100644 index 000000000..dbf65fb96 --- /dev/null +++ b/modules/CPMplugin/src/main/java/com/plugin/CPM.java @@ -0,0 +1,277 @@ +package com.plugin; + + +import org.gephi.graph.api.*; +import org.gephi.utils.longtask.spi.LongTask; +import org.gephi.utils.progress.ProgressTicket; +import org.openide.util.Lookup; + +import java.util.*; + + +public class CPM implements org.gephi.statistics.spi.Statistics, LongTask { + + private String report = ""; + private boolean cancel = false; + + private ProgressTicket progressTicket; + private int k = 0; + private Set> Cliques = new HashSet>(); + GenQueue> Bk = new GenQueue>(); + + public class SortByID implements Comparator { + + public int compare(Node n1, Node n2) { + if (n1.getStoreId() > n2.getStoreId()) { + return 1; + } else { + return -1; + } + } + } + + // + public Object getLastElement(final Collection c) { + /* + final Iterator itr = c.iterator(); + Object lastElement = itr.next(); + while (itr.hasNext()) { + lastElement = itr.next(); + } + return lastElement; + */ + return null; + } + + class GenQueue { + + private LinkedList list = new LinkedList(); + + public void enqueue(E item) { + list.addLast(item); + } + + public E dequeue() { + return list.pollFirst(); + } + + public boolean hasItems() { + return !list.isEmpty(); + } + + public int size() { + return list.size(); + } + + public void addItems(GenQueue q) { + while (q.hasItems()) { + list.addLast(q.dequeue()); + } + } + } + // + + private Vector getLargerIndexNodes(Graph g, Node vi) { + Vector output = new Vector(); + for (Node n : g.getNodes()) { + + boolean b1 = n.getStoreId() > vi.getStoreId(), + b2 = g.getEdge(n, vi) != null, + b3 = g.getEdge(vi, n) != null; + + if (b1 && (b2 || b3)) { + output.addElement(n); + } + } + + return output; + } + + private boolean checkBk1IsClique(Graph g, TreeSet Bk1) { + for (Node firstNode : Bk1) { + for (Node secondNode : Bk1) { + if (firstNode == secondNode) { + continue; + } + if (g.getEdge(firstNode, secondNode) == null && + g.getEdge(secondNode, firstNode) == null) { //One edge is missing in the Bk+1 clique + return false; + } + } + } + + return true; + } + + Random r = new Random(); + + @Override + public void execute(GraphModel gm) { + /*for (int i = 0; i < 2; i++) { + Graph graph = gm.getGraphVisible(); + Node node = gm.factory().newNode(); + + node.setLabel(String.valueOf(r.nextInt())); + node.setX(r.nextInt(500)); + node.setY(r.nextInt(500)); + node.setSize(10f); + graph.addNode(node); + }*/ + + + Graph g = gm.getGraphVisible(); + + g.readLock(); + + //Firstly add each node as an item in Bk + int count = 0; + TreeSet tmp; + + for (Node n : g.getNodes()) { + count++; + //Trick: if the node's degree is less than k-1, it can not involve in k-clique + if (g.getDegree(n) >= k - 1) { + tmp = new TreeSet(new SortByID()); + tmp.add(n); + Bk.enqueue(tmp); //Add the B1 (node itself) to the queue + } + } + + //Now start the iterative process for finding cliques + tmp = Bk.dequeue(); + + while (tmp != null) { + if (cancel) { + //Empty variables + Bk.list.clear(); + tmp.clear(); + Cliques.clear(); + return; + } + + //Search for Bk+1 + Node vi = tmp.last(); //(Node) getLastElement(tmp); + Vector largerIndexes = getLargerIndexNodes(g, vi); + + for (Node vj : largerIndexes) { + TreeSet Bk1 = new TreeSet(new SortByID()); + Bk1.addAll(tmp); //Clone current Bk into Bk+1 + Bk1.add(vj); + if (Bk1.size() <= getK() && checkBk1IsClique(g, Bk1)) { + + if (Bk1.size() == getK()) { //A clique of size k found. Finish expanding this Bk+1 here. + Cliques.add(Bk1); + } else if (Bk1.size() < getK()) { + Bk.enqueue(Bk1); //k should be checked for finding cliques of size k. + } else { //Clique with larger size will be omitted. + report += "
Larger Clique Found. It should not be here
"; + } + } + } + + tmp = Bk.dequeue(); //Check next item + } + g.readUnlock(); + + //Algorithm finished. + //Write the output + report += "Clique Detection started. Nodes with " + (k - 1) + " edges will not be included."; + report += "

"; + report += "Found Cliques of size " + getK() + ".
Now making new graph ...
Clearing old graph ..."; + + //edit the graph + g.clear(); + report += " [+]
Creating new nodes ..."; + + gm = Lookup.getDefault().lookup(GraphController.class).getGraphModel(); + int nID = 0; + Set nodes = new HashSet(); + + for (Set firstClique : Cliques) { //Create the nodes + Node firstNode = gm.factory().newNode(String.valueOf(nID++)); + firstNode.setX(-500 + r.nextInt(1000)); + firstNode.setY(-500 + r.nextInt(1000)); + firstNode.setSize(8f); + + String nodeLabel = ""; + + for (Node n : firstClique) { + nodeLabel += n.getLabel() + ","; + } + + nodeLabel = nodeLabel.substring(0, nodeLabel.length() - 1); //remove last , + firstNode.setLabel(nodeLabel); + + nodes.add(firstNode); + } + + report += "[+]
Detecting and creating the edges ..."; + HashSet edges = new HashSet(); + + for (Node vi : nodes) { + for (Node vj : nodes) { + if ((vi != vj) && (getSharedNodes(vi, vj) == k - 1)) { + if (g.isDirected()) { + edges.add(gm.factory().newEdge(vi, vj, true)); + } else { + edges.add(gm.factory().newEdge(vi, vj, false)); + } + } + } + } + + report += "[+]
Redrawing new graph ..."; + for (Node n : nodes) { + g.addNode(n); + } + + + for (Edge e : edges) { + g.addEdge(e); + } + + report += "[+]
Done!


Palla, Gergely, Imre Derényi, Illés Farkas, and Tamás Vicsek. \"Uncovering the overlapping community structure of complex networks in nature and society.\" Nature 435, no. 7043 (2005): 814-818"; + + } + + private int getSharedNodes(Node vi, Node vj) { + String[] firstCliqueNodes = vi.getLabel().split(","); + String[] secondCliqueNodes = vj.getLabel().split(","); + + int sharedNodes = 0; + + for (String n1 : firstCliqueNodes) { + for (String n2 : secondCliqueNodes) { + if (n1.equals(n2)) { + sharedNodes++; + } + } + } + + return sharedNodes; + } + + @Override + public String getReport() { + return report; + } + + @Override + public boolean cancel() { + cancel = true; + return true; + } + + @Override + public void setProgressTicket(ProgressTicket pt) { + this.progressTicket = pt; + } + + public int getK() { + return k; + } + + public void setK(int k) { + this.k = k; + } +} diff --git a/modules/CPMplugin/src/main/java/com/plugin/CPMBuilder.java b/modules/CPMplugin/src/main/java/com/plugin/CPMBuilder.java new file mode 100644 index 000000000..0a9a70702 --- /dev/null +++ b/modules/CPMplugin/src/main/java/com/plugin/CPMBuilder.java @@ -0,0 +1,25 @@ +package com.plugin; + +import org.gephi.statistics.spi.Statistics; +import org.gephi.statistics.spi.StatisticsBuilder; +import org.openide.util.lookup.ServiceProvider; + +@ServiceProvider (service = StatisticsBuilder.class) +public class CPMBuilder implements org.gephi.statistics.spi.StatisticsBuilder { + + @Override + public String getName() { + return "Clique Percolation Method"; + } + + @Override + public Statistics getStatistics() { + return new CPM(); + } + + @Override + public Class getStatisticsClass() { + return CPM.class; + } + +} diff --git a/modules/CPMplugin/src/main/java/com/plugin/CPMPanel.java b/modules/CPMplugin/src/main/java/com/plugin/CPMPanel.java new file mode 100644 index 000000000..aa7d1d68d --- /dev/null +++ b/modules/CPMplugin/src/main/java/com/plugin/CPMPanel.java @@ -0,0 +1,77 @@ +package com.plugin;/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ + + +import javax.swing.*; +import java.awt.*; + + +public class CPMPanel extends JPanel { + + JTextField kvalue; + + @SuppressWarnings("unchecked") + public CPMPanel() { + //this.setLayout(null); + JLabel jXHeader1 = new JLabel(); + + jXHeader1.setText("Enter the value for k (clique size, ex: k = 3 will find triangualrs). Higher values of k may take more time for computation. This algorithm is NP-Hard, so use it carefully."); // NOI18N +// jXHeader1.setTitle("Clique Detector"); + + JLabel label = new JLabel("Enter value of k here:"); + label.setFont(new Font("Times New Roman", Font.ROMAN_BASELINE, 13)); + this.add(label); + kvalue = new JTextField(); + this.add(kvalue); + Insets insets = this.getInsets(); + + Dimension size = label.getPreferredSize(); + label.setBounds(20 + insets.left, 30 + insets.top, size.width, size.height); + + Dimension size1 = kvalue.getPreferredSize(); + kvalue.setBounds(20 + insets.left, 130 + insets.top, size1.width + 20, size1.height); + + GroupLayout layout = new GroupLayout(this); + this.setLayout(layout); + + layout.setHorizontalGroup( + layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addComponent(jXHeader1, GroupLayout.DEFAULT_SIZE, 536, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(label) + .addContainerGap(354, Short.MAX_VALUE)) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(kvalue) + .addContainerGap(382, Short.MAX_VALUE)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(jXHeader1, GroupLayout.PREFERRED_SIZE, 80, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(label) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(kvalue) + .addContainerGap(187, Short.MAX_VALUE)) + ); + } + + public int getK() { + int i = 0; + try { + i = Integer.parseInt(kvalue.getText()); + } catch (Exception ex) { + return 0; + } + return i; + } + + public void setK(int k) { + this.kvalue.setText(String.valueOf(k)); + } +} diff --git a/modules/CPMplugin/src/main/java/com/plugin/CPMUI.java b/modules/CPMplugin/src/main/java/com/plugin/CPMUI.java new file mode 100644 index 000000000..15e9009ac --- /dev/null +++ b/modules/CPMplugin/src/main/java/com/plugin/CPMUI.java @@ -0,0 +1,71 @@ +package com.plugin;/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ + +import org.gephi.statistics.spi.Statistics; +import org.gephi.statistics.spi.StatisticsUI; +import org.openide.util.lookup.ServiceProvider; + +import javax.swing.*; + +@ServiceProvider(service = StatisticsUI.class) +public class CPMUI implements StatisticsUI { + + private CPMPanel panel; + private CPM myCliqueDetector; + + @Override + public JPanel getSettingsPanel() { + panel = new CPMPanel(); + return panel; + } + + @Override + public void setup(Statistics ststcs) { + this.myCliqueDetector = (CPM) ststcs; + if (panel != null) { + panel.setK(myCliqueDetector.getK()); + } + } + + @Override + public void unsetup() { + if (panel != null) { + myCliqueDetector.setK(panel.getK()); + } + panel = null; + } + + @Override + public Class getStatisticsClass() { + return CPM.class; + } + + @Override + public String getValue() { + return null; + } + + @Override + public String getDisplayName() { + return "Clique Percolation Method"; + } + + @Override + public String getShortDescription() { + return "Clique Percolation Method implementaion in gephi"; + } + + @Override + public String getCategory() { + return CATEGORY_NETWORK_OVERVIEW; + } + + @Override + public int getPosition() { + return 800; + } + +} diff --git a/modules/CPMplugin/src/main/nbm/manifest.mf b/modules/CPMplugin/src/main/nbm/manifest.mf new file mode 100644 index 000000000..93ac04c07 --- /dev/null +++ b/modules/CPMplugin/src/main/nbm/manifest.mf @@ -0,0 +1,5 @@ +Manifest-Version: 1.0 +OpenIDE-Module-Name: CPM plugin +OpenIDE-Module-Short-Description: Clique Percolation Method implementation +OpenIDE-Module-Long-Description: Simple algorithm for detecting overlapping communities based on node centeric method. +OpenIDE-Module-Display-Category: Tool diff --git a/pom.xml b/pom.xml index afb8f2bf5..0e5165d3c 100644 --- a/pom.xml +++ b/pom.xml @@ -12,6 +12,7 @@ + modules/CPMplugin