diff --git a/modules/BearkatCentralization/README.md b/modules/BearkatCentralization/README.md new file mode 100644 index 000000000..08127ac77 --- /dev/null +++ b/modules/BearkatCentralization/README.md @@ -0,0 +1,40 @@ +# Bearkat Centralization + +Bearkat Centralization is a Gephi Statistics plugin that calculates multiple graph centralization metrics from a single report. + +## Features + +The plugin calculates: + +* Degree Centralization +* Weighted Degree Centralization +* In-Degree Centralization +* Out-Degree Centralization +* Betweenness Centralization +* Eigenvector Centralization +* Closeness Centralization +* Harmonic Closeness Centralization + +The plugin also writes node-level values to the Data Laboratory so users can inspect individual node scores after running the statistic. + +## Usage + +1. Open or import a graph in Gephi. +2. Go to the Statistics panel. +3. Run **Centralization**. +4. View graph-level results in the generated report. +5. View node-level values in **Data Laboratory → Nodes**. + +## Notes + +Harmonic closeness centralization is included because standard closeness centralization can be affected by disconnected graphs or isolates. Harmonic closeness uses reciprocal distances, allowing unreachable nodes to contribute 0. + +Eigenvector centralization may vary across software packages because implementations may use different eigenvector normalization methods and graph-level centralization formulas. + +## Credits + +Programmed by Braell Dotson and Dr. Nate Jones at Sam Houston State University. + +## Reference + +Freeman, Linton C. “Centrality in Social Networks: Conceptual Clarification.” *Social Networks* 1, no. 3 (1978): 215–239. diff --git a/modules/BearkatCentralization/pom.xml b/modules/BearkatCentralization/pom.xml new file mode 100644 index 000000000..85833621f --- /dev/null +++ b/modules/BearkatCentralization/pom.xml @@ -0,0 +1,82 @@ + + + 4.0.0 + + gephi-plugin-parent + org.gephi + 0.11.1 + + + edu.shsu + bearkat-centralization + 1.3.0 + nbm + + Bearkat Centralization + + + + org.netbeans.api + org-openide-util-lookup + RELEASE130 + + + + org.gephi + graph-api + + + + org.gephi + statistics-api + + + + org.gephi + utils-longtask + + + + org.gephi + statistics-plugin + + + + org.gephi + statistics-plugin-ui + + + + + + + org.apache.netbeans.utilities + nbm-maven-plugin + + MIT + Braell Dotson + bdotson10516@gmail.com + https://github.com/bbraell/BearKat-Degree-Centralization---SHSU-Braell-Dotson + https://github.com/bbraell/gephi-plugins.git + + + + + + + + + + + + oss-sonatype + oss-sonatype + https://oss.sonatype.org/content/repositories/snapshots/ + + true + + + + + + diff --git a/modules/BearkatCentralization/src/main/java/com/mycompany/gephidegreeplugin/Centralization.java b/modules/BearkatCentralization/src/main/java/com/mycompany/gephidegreeplugin/Centralization.java new file mode 100644 index 000000000..2a8737d61 --- /dev/null +++ b/modules/BearkatCentralization/src/main/java/com/mycompany/gephidegreeplugin/Centralization.java @@ -0,0 +1,446 @@ +package com.mycompany.gephidegreeplugin; + +import org.gephi.graph.api.Column; +import org.gephi.graph.api.DirectedGraph; +import org.gephi.graph.api.Edge; +import org.gephi.graph.api.Graph; +import org.gephi.graph.api.GraphModel; +import org.gephi.graph.api.Node; +import org.gephi.graph.api.Table; +import org.gephi.statistics.plugin.EigenvectorCentrality; +import org.gephi.statistics.plugin.GraphDistance; +import org.gephi.statistics.spi.Statistics; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; +import java.util.Queue; + +public class Centralization implements Statistics { + + private double degreeCentralization; + private double weightedDegreeCentralization; + private double inDegreeCentralization; + private double outDegreeCentralization; + private double betweennessCentralization; + private double eigenvectorCentralization; + private double closenessCentralization; + private double harmonicClosenessCentralization; + private boolean directed; + + @Override + public void execute(GraphModel graphModel) { + Graph graph = graphModel.getGraph(); + directed = graphModel.isDirected(); + + calculateDegreeCentralization(graphModel, graph); + calculateWeightedDegreeCentralization(graphModel, graph); + + if (directed) { + calculateInDegreeCentralization(graphModel); + calculateOutDegreeCentralization(graphModel); + } + + calculateBetweennessCentralization(graphModel, graph); + calculateEigenvectorCentralization(graphModel, graph); + calculateClosenessCentralization(graphModel, graph); + calculateHarmonicClosenessCentralization(graphModel, graph); + } + + private void calculateDegreeCentralization(GraphModel graphModel, Graph graph) { + int n = graph.getNodeCount(); + + Table nodeTable = graphModel.getNodeTable(); + Column col = nodeTable.getColumn("degree_centrality"); + + if (col == null) { + col = nodeTable.addColumn("degree_centrality", Double.class); + } + + int maxDegree = 0; + + for (Node node : graph.getNodes()) { + int degree = graph.getDegree(node); + maxDegree = Math.max(maxDegree, degree); + node.setAttribute(col, (double) degree); + } + + double numerator = 0; + + for (Node node : graph.getNodes()) { + int degree = graph.getDegree(node); + numerator += maxDegree - degree; + } + + double denominator = (n - 1.0) * (n - 2.0); + degreeCentralization = denominator > 0 ? numerator / denominator : 0; + } + + private void calculateWeightedDegreeCentralization(GraphModel graphModel, Graph graph) { + int n = graph.getNodeCount(); + + Table nodeTable = graphModel.getNodeTable(); + Column col = nodeTable.getColumn("weighted_degree_centrality"); + + if (col == null) { + col = nodeTable.addColumn("weighted_degree_centrality", Double.class); + } + + double maxWeightedDegree = 0; + + for (Node node : graph.getNodes()) { + double weightedDegree = 0; + + for (Edge edge : graph.getEdges(node)) { + double weight = edge.getWeight(); + + if (weight <= 0) { + weight = 1.0; + } + + weightedDegree += weight; + } + + node.setAttribute(col, weightedDegree); + maxWeightedDegree = Math.max(maxWeightedDegree, weightedDegree); + } + + double numerator = 0; + + for (Node node : graph.getNodes()) { + Double weightedDegree = (Double) node.getAttribute(col); + + if (weightedDegree == null) { + weightedDegree = 0.0; + } + + numerator += maxWeightedDegree - weightedDegree; + } + + double denominator = maxWeightedDegree * (n - 1.0); + weightedDegreeCentralization = denominator > 0 ? numerator / denominator : 0; + } + + private void calculateInDegreeCentralization(GraphModel graphModel) { + DirectedGraph graph = graphModel.getDirectedGraph(); + int n = graph.getNodeCount(); + + Table nodeTable = graphModel.getNodeTable(); + Column col = nodeTable.getColumn("in_degree_centrality"); + + if (col == null) { + col = nodeTable.addColumn("in_degree_centrality", Double.class); + } + + int maxInDegree = 0; + + for (Node node : graph.getNodes()) { + int inDegree = graph.getInDegree(node); + maxInDegree = Math.max(maxInDegree, inDegree); + node.setAttribute(col, (double) inDegree); + } + + double numerator = 0; + + for (Node node : graph.getNodes()) { + int inDegree = graph.getInDegree(node); + numerator += maxInDegree - inDegree; + } + + double denominator = (n - 1.0) * (n - 1.0); + inDegreeCentralization = denominator > 0 ? numerator / denominator : 0; + } + + private void calculateOutDegreeCentralization(GraphModel graphModel) { + DirectedGraph graph = graphModel.getDirectedGraph(); + int n = graph.getNodeCount(); + + Table nodeTable = graphModel.getNodeTable(); + Column col = nodeTable.getColumn("out_degree_centrality"); + + if (col == null) { + col = nodeTable.addColumn("out_degree_centrality", Double.class); + } + + int maxOutDegree = 0; + + for (Node node : graph.getNodes()) { + int outDegree = graph.getOutDegree(node); + maxOutDegree = Math.max(maxOutDegree, outDegree); + node.setAttribute(col, (double) outDegree); + } + + double numerator = 0; + + for (Node node : graph.getNodes()) { + int outDegree = graph.getOutDegree(node); + numerator += maxOutDegree - outDegree; + } + + double denominator = (n - 1.0) * (n - 1.0); + outDegreeCentralization = denominator > 0 ? numerator / denominator : 0; + } + + private void calculateBetweennessCentralization(GraphModel graphModel, Graph graph) { + GraphDistance distance = new GraphDistance(); + distance.setDirected(graphModel.isDirected()); + distance.execute(graphModel); + + int n = graph.getNodeCount(); + + Table nodeTable = graphModel.getNodeTable(); + Column betweenCol = nodeTable.getColumn(GraphDistance.BETWEENNESS); + Column myCol = nodeTable.getColumn("betweenness_centrality"); + + if (myCol == null) { + myCol = nodeTable.addColumn("betweenness_centrality", Double.class); + } + + double maxBetweenness = 0; + + for (Node node : graph.getNodes()) { + Double value = (Double) node.getAttribute(betweenCol); + + if (value == null) { + value = 0.0; + } + + node.setAttribute(myCol, value); + maxBetweenness = Math.max(maxBetweenness, value); + } + + double numerator = 0; + + for (Node node : graph.getNodes()) { + Double value = (Double) node.getAttribute(myCol); + + if (value == null) { + value = 0.0; + } + + numerator += maxBetweenness - value; + } + + double denominator; + +if (n > 2) { + if (graphModel.isDirected()) { + denominator = (n - 1.0) * (n - 1.0) * (n - 2.0); + } else { + denominator = ((n - 1.0) * (n - 1.0) * (n - 2.0)) / 2.0; + } +} else { + denominator = 1; +} + betweennessCentralization = denominator > 0 ? numerator / denominator : 0; + } + + private void calculateEigenvectorCentralization(GraphModel graphModel, Graph graph) { + EigenvectorCentrality eigenvector = new EigenvectorCentrality(); + eigenvector.execute(graphModel); + + int n = graph.getNodeCount(); + + Table nodeTable = graphModel.getNodeTable(); + Column eigenCol = nodeTable.getColumn(EigenvectorCentrality.EIGENVECTOR); + Column myCol = nodeTable.getColumn("eigenvector_centrality"); + + if (myCol == null) { + myCol = nodeTable.addColumn("eigenvector_centrality", Double.class); + } + + double maxEigenvector = 0; + + for (Node node : graph.getNodes()) { + Double value = (Double) node.getAttribute(eigenCol); + + if (value == null) { + value = 0.0; + } + + node.setAttribute(myCol, value); + maxEigenvector = Math.max(maxEigenvector, value); + } + + double numerator = 0; + + for (Node node : graph.getNodes()) { + Double value = (Double) node.getAttribute(myCol); + + if (value == null) { + value = 0.0; + } + + numerator += maxEigenvector - value; + } + + double denominator = n - 1.0; + eigenvectorCentralization = denominator > 0 ? numerator / denominator : 0; + } + + private void calculateClosenessCentralization(GraphModel graphModel, Graph graph) { + int n = graph.getNodeCount(); + + Table nodeTable = graphModel.getNodeTable(); + Column closenessCol = nodeTable.getColumn(GraphDistance.CLOSENESS); + Column myCol = nodeTable.getColumn("closeness_centrality"); + + if (myCol == null) { + myCol = nodeTable.addColumn("closeness_centrality", Double.class); + } + + double maxCloseness = 0; + + for (Node node : graph.getNodes()) { + Double value = (Double) node.getAttribute(closenessCol); + + if (value == null) { + value = 0.0; + } + + node.setAttribute(myCol, value); + maxCloseness = Math.max(maxCloseness, value); + } + + double numerator = 0; + + for (Node node : graph.getNodes()) { + Double value = (Double) node.getAttribute(myCol); + + if (value == null) { + value = 0.0; + } + + numerator += maxCloseness - value; + } + + double denominator = n - 2.0; + closenessCentralization = denominator > 0 ? numerator / denominator : 0; + } + + private void calculateHarmonicClosenessCentralization(GraphModel graphModel, Graph graph) { + int n = graph.getNodeCount(); + + Table nodeTable = graphModel.getNodeTable(); + Column col = nodeTable.getColumn("harmonic_closeness_centrality"); + + if (col == null) { + col = nodeTable.addColumn("harmonic_closeness_centrality", Double.class); + } + + double maxHarmonic = 0; + + for (Node node : graph.getNodes()) { + Map distances = bfsDistances(graphModel, graph, node); + double harmonic = 0; + + for (Node other : graph.getNodes()) { + if (!node.equals(other)) { + Integer distance = distances.get(other); + + if (distance != null && distance > 0) { + harmonic += 1.0 / distance; + } + } + } + + node.setAttribute(col, harmonic); + maxHarmonic = Math.max(maxHarmonic, harmonic); + } + + double numerator = 0; + + for (Node node : graph.getNodes()) { + Double harmonic = (Double) node.getAttribute(col); + + if (harmonic == null) { + harmonic = 0.0; + } + + numerator += maxHarmonic - harmonic; + } + + double denominator; + + if (n > 2) { + if (graphModel.isDirected()) { + denominator = (n - 1.0) * (n - 1.0); + } else { + denominator = ((n - 1.0) * (n - 2.0)) / 2.0; + } + } else { + denominator = 1; + } + + harmonicClosenessCentralization = denominator > 0 ? numerator / denominator : 0; +} + +private Map bfsDistances(GraphModel graphModel, Graph graph, Node start) { + Map distances = new HashMap<>(); + Queue queue = new LinkedList<>(); + + distances.put(start, 0); + queue.add(start); + + while (!queue.isEmpty()) { + Node current = queue.poll(); + int currentDistance = distances.get(current); + + for (Node neighbor : graph.getNeighbors(current)) { + if (!distances.containsKey(neighbor)) { + distances.put(neighbor, currentDistance + 1); + queue.add(neighbor); + } + } + } + + return distances; +} + + @Override + public String getReport() { + String directedText; + + if (directed) { + directedText = "

In-Degree Centralization: " + inDegreeCentralization + "

" + + "

Out-Degree Centralization: " + outDegreeCentralization + "

"; + } else { + directedText = "

In-Degree Centralization: Not applicable for undirected graphs

" + + "

Out-Degree Centralization: Not applicable for undirected graphs

"; + } + + return "" + + "

Bearkat Centralization Report

" + + "

Degree Centralization: " + degreeCentralization + "

" ++ "

Weighted Degree Centralization: " + weightedDegreeCentralization + "

" ++ directedText ++ "

Betweenness Centralization: " + betweennessCentralization + "

" ++ "

Eigenvector Centralization: " + eigenvectorCentralization + "

" ++ "

Closeness Centralization: " + closenessCentralization + "

" ++ "

Harmonic Closeness Centralization: " + harmonicClosenessCentralization + "

" + ++ "
" ++ "

Notes and Interpretation

" + ++ "

Closeness Centralization: " ++ "Standard closeness centralization may be affected by disconnected graphs or isolates because shortest-path distances become undefined. " ++ "The closeness values reported by this plugin rely on Gephi's underlying implementation. " ++ "For disconnected networks, Harmonic Closeness Centralization is generally the preferred measure because unreachable nodes contribute 0 through reciprocal distances.

" + ++ "

Eigenvector Centralization: " ++ "Eigenvector centralization may vary across software packages because implementations can differ in eigenvector normalization methods and graph-level centralization normalization. " ++ "Results should therefore be interpreted with attention to the software and methodology used.

" + ++ "
" ++ "

Node-level values for every metric are available in Data Laboratory → Nodes.

" + + "
" ++ "

Citations:

" ++ "

Freeman, Linton C. “Centrality in Social Networks Conceptual Clarification.” " ++ "Social Networks 1, no. 3 (1978): 215–39.

" ++ "

Borgatti, Stephen P. “Identifying Sets of Key Players in a Social Network.” " ++ "Computational & Mathematical Organization Theory 12 (2006): 21–34.

" ++ "

Gil, J. and Schmidt, S. (1996). “The Origin of the Mexican Network of Power.” " ++ "Proceedings of the International Social Network Conference, Charleston, SC, 22–25.

" ++ "

Programmed by Braell Dotson and Dr. Nate Jones at Sam Houston State University.

" + + ""; + } +} \ No newline at end of file diff --git a/modules/BearkatCentralization/src/main/java/com/mycompany/gephidegreeplugin/CentralizationBuilder.java b/modules/BearkatCentralization/src/main/java/com/mycompany/gephidegreeplugin/CentralizationBuilder.java new file mode 100644 index 000000000..62232f93f --- /dev/null +++ b/modules/BearkatCentralization/src/main/java/com/mycompany/gephidegreeplugin/CentralizationBuilder.java @@ -0,0 +1,24 @@ +package com.mycompany.gephidegreeplugin; + +import org.gephi.statistics.spi.Statistics; +import org.gephi.statistics.spi.StatisticsBuilder; +import org.openide.util.lookup.ServiceProvider; + +@ServiceProvider(service = StatisticsBuilder.class) +public class CentralizationBuilder implements StatisticsBuilder { + + @Override +public String getName() { + return "Centralization"; +} + + @Override + public Statistics getStatistics() { + return new Centralization(); + } + + @Override + public Class getStatisticsClass() { + return Centralization.class; + } +} \ No newline at end of file diff --git a/modules/BearkatCentralization/src/main/java/com/mycompany/gephidegreeplugin/CentralizationUI.java b/modules/BearkatCentralization/src/main/java/com/mycompany/gephidegreeplugin/CentralizationUI.java new file mode 100644 index 000000000..88766ed19 --- /dev/null +++ b/modules/BearkatCentralization/src/main/java/com/mycompany/gephidegreeplugin/CentralizationUI.java @@ -0,0 +1,58 @@ +package com.mycompany.gephidegreeplugin; + +import javax.swing.JPanel; +import org.gephi.statistics.spi.Statistics; +import org.gephi.statistics.spi.StatisticsUI; +import org.openide.util.lookup.ServiceProvider; + +@ServiceProvider(service = StatisticsUI.class) + +public class CentralizationUI implements StatisticsUI { + + private Centralization centralization; + + @Override + public JPanel getSettingsPanel() { + return null; + } + + @Override + public void setup(Statistics statistics) { + this.centralization = (Centralization) statistics; + } + + @Override + public void unsetup() { + this.centralization = null; + } + + @Override + public Class getStatisticsClass() { + return Centralization.class; + } + + @Override + public String getValue() { + return "Done"; + } + + @Override +public String getDisplayName() { + return "Centralization"; +} + + @Override + public String getShortDescription() { + return "Measures weighted connections between nodes"; + } + + @Override + public String getCategory() { + return StatisticsUI.CATEGORY_NETWORK_OVERVIEW; + } + + @Override + public int getPosition() { + return 999; + } +} diff --git a/modules/BearkatCentralization/src/main/nbm/manifest.mf b/modules/BearkatCentralization/src/main/nbm/manifest.mf new file mode 100644 index 000000000..f6dad7c95 --- /dev/null +++ b/modules/BearkatCentralization/src/main/nbm/manifest.mf @@ -0,0 +1,5 @@ +Manifest-Version: 1.0 +OpenIDE-Module-Name: Bearkat Centralization +OpenIDE-Module-Short-Description: Computes multiple graph centralization metrics from a single Gephi Statistics report. +OpenIDE-Module-Long-Description: Bearkat Centralization computes degree, weighted degree, in-degree, out-degree, betweenness, eigenvector, closeness, and harmonic closeness centralization from a single Statistics report. The plugin also exports node-level values to the Data Laboratory and supports both directed and undirected networks. +OpenIDE-Module-Display-Category: Metric diff --git a/modules/BearkatCentralization/src/main/resources/com/mycompany/gephidegreeplugin/Bundle.properties b/modules/BearkatCentralization/src/main/resources/com/mycompany/gephidegreeplugin/Bundle.properties new file mode 100644 index 000000000..e15ee32c6 --- /dev/null +++ b/modules/BearkatCentralization/src/main/resources/com/mycompany/gephidegreeplugin/Bundle.properties @@ -0,0 +1,6 @@ +#Localized module labels. Defaults taken from POM (, , ) if unset. +#OpenIDE-Module-Name= +#OpenIDE-Module-Short-Description= +#OpenIDE-Module-Long-Description= +#OpenIDE-Module-Display-Category= +#Mon Mar 30 13:40:32 CDT 2026 diff --git a/pom.xml b/pom.xml index 11572dc3e..8f3af05f5 100644 --- a/pom.xml +++ b/pom.xml @@ -12,6 +12,13 @@ + + + modules/BearkatCentralization + + + modules/BearkatCentralization + modules/GeoLayout modules/KBraceFilter @@ -86,6 +93,7 @@ +