/*
Copyright 2008-2010 Gephi
Authors : Patick J. McSweeney <pjmcswee@syr.edu>, Sebastien Heymann <seb@gephi.org>
Website : http://www.gephi.org
This file is part of Gephi.
Gephi is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
Gephi is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Gephi. If not, see <http://www.gnu.org/licenses/>.
*/
package org.gephi.statistics.plugin;
import java.io.File;
import java.io.IOException;
import org.gephi.data.attributes.api.AttributeModel;
import org.gephi.statistics.spi.Statistics;
import org.gephi.graph.api.*;
import org.gephi.utils.TempDirUtils;
import org.gephi.utils.TempDirUtils.TempDir;
import org.gephi.utils.longtask.spi.LongTask;
import org.gephi.utils.progress.Progress;
import org.gephi.utils.progress.ProgressTicket;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartRenderingInfo;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.entity.StandardEntityCollection;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
/**
* This class measures how closely the degree distribution of a
* network follows a power-law scale. An alpha value between 2 and 3
* implies a power law.
* @author pjmcswees
*/
public class DegreeDistribution implements Statistics, LongTask {
/** The combined In/Out-degree distribution. */
private double[][] combinedDistribution;
/** The In-degree distribution. */
private double[][] inDistribution;
/** The out-degree distribution. */
private double[][] outDistribution;
/** Remember if the metric has been canceled. */
private boolean isCanceled;
/** The progress meter for this metric. */
private ProgressTicket progress;
/** Indicates if this network should be directed or undirected.*/
private boolean isDirected;
/** The powerlaw value for the combined in/out-degree of this network. */
private double combinedAlpha;
/** The powerlaw value for the in-degree of this network. */
private double inAlpha;
/** The powerlaw value for the out-degree of this network. */
private double outAlpha;
/** The powerlaw value for the combined in/out-degree of this network. */
private double combinedBeta;
/** The powerlaw value for the in-degree of this network. */
private double inBeta;
/** The powerlaw value for the out-degree of this network. */
private double outBeta;
public DegreeDistribution() {
GraphController graphController = Lookup.getDefault().lookup(GraphController.class);
if (graphController != null && graphController.getModel() != null) {
isDirected = graphController.getModel().isDirected();
}
}
/**
* @param isDirected Indicates the metric's interpretation of this network.
*/
public void setDirected(boolean isDirected) {
this.isDirected = isDirected;
}
public boolean isDirected() {
return isDirected;
}
/**
* @return The combined in/out-degree power law value for this network.
*/
public double getCombinedPowerLaw() {
return this.combinedAlpha;
}
/**
* @return The combined in/out-degree power law value for this network.
*/
public double getInPowerLaw() {
return this.inAlpha;
}
/**
* @return The combined in/out-degree power law value for this network.
*/
public double getOutPowerLaw() {
return this.outAlpha;
}
/**
* Calculates the degree distribution for this network.
* Either a combined degree distribution or separate
* in-degree distribution and out-degree distribution
* is calculated based on the mDirected variable.
*
* @param graphModel
*/
public void execute(GraphModel graphModel, AttributeModel attributeModel) {
//Get the graph from the graphController, based
//on the mDirected variable.
HierarchicalGraph graph;
if (isDirected) {
graph = graphModel.getHierarchicalDirectedGraphVisible();
} else {
graph = graphModel.getHierarchicalUndirectedGraphVisible();
}
execute(graph, attributeModel);
}
public void execute(HierarchicalGraph graph, AttributeModel attributeModel) {
//Mark this as not yet canceled.
isCanceled = false;
graph.readLock();
//Start
Progress.start(progress, graph.getNodeCount());
//Consider the in and out degree of every node
if (isDirected) {
inDistribution = new double[2][2 * graph.getNodeCount()];
outDistribution = new double[2][2 * graph.getNodeCount()];
} else {
combinedDistribution = new double[2][2 * graph.getNodeCount()];
}
int nodeCount = 0;
for (Node node : graph.getNodes()) {
if (isDirected) {
int inDegree = ((HierarchicalDirectedGraph) graph).getTotalInDegree(node);
int outDegree = ((HierarchicalDirectedGraph) graph).getTotalOutDegree(node);
inDistribution[1][inDegree]++;
outDistribution[1][outDegree]++;
inDistribution[0][inDegree] = inDegree;
outDistribution[0][outDegree] = outDegree;
} else {
int combinedDegree = ((HierarchicalUndirectedGraph) graph).getTotalDegree(node);
combinedDistribution[1][combinedDegree]++;
combinedDistribution[0][combinedDegree] = combinedDegree;
}
Progress.progress(progress, nodeCount);
nodeCount++;
if (isCanceled) {
graph.readUnlockAll();
return;
}
}
graph.readUnlock();
if (isDirected) {
double[] inFit = new double[2];
double[] outFit = new double[2];
leastSquares(inDistribution[1], inFit);
leastSquares(outDistribution[1], outFit);
inAlpha = inFit[1];
inBeta = inFit[0];
outAlpha = outFit[1];
outBeta = outFit[0];
} else {
double[] fit = new double[2];
leastSquares(combinedDistribution[1], fit);
combinedAlpha = fit[1];
combinedBeta = fit[0];
}
}
/**
* Fits the logarithm distribution/degree to a straight line of the form:
* a + b *x which is then interpreted as a*x^y in the non-logarithmic scale
*
* @param dist The distribution of node degrees to fit to a logarithmized straight line
*
* @return An array of 2 doubles
* index 0: b
* index 1: a
*
* For more see Wolfram Least Squares Fitting
*/
public void leastSquares(double[] dist, double[] res) {
//Vararibles to compute
double SSxx = 0;
double SSxy = 0;
//double SSyy = 0;
//Compute the average log(x) value when for positive (>0) values
double avgX = 0;
double avgY = 0;
double nonZero = 0;
for (int i = 1; i < dist.length; i++) {
if (dist[i] > 0) {
avgX += Math.log(i);
avgY += Math.log(dist[i]);
nonZero++;
}
if (isCanceled) {
return;
}
}
avgX /= nonZero;
avgY /= nonZero;
//compute the variance of log(x)
for (int i = 1; i < dist.length; i++) {
if (dist[i] > 0) {
SSxx += Math.pow(Math.log(i) - avgX, 2);
//SSyy += Math.pow(Math.log(dist[i]) - avgY, 2);
SSxy += (Math.log(i) - avgX) * (Math.log(dist[i]) - avgY);
}
}
//Compute and return the results
res[0] = SSxy / SSxx;
res[1] = avgY - res[0] * avgX;
}
/**
* @return A String report based on the interpretation of the network.
*/
public String getReport() {
return (isDirected) ? getDirectedReport() : getUndirectedReport();
}
/**
*
* @return The directed version of the report.
*/
private String getDirectedReport() {
double inMax = 0;
XYSeries inSeries2 = new XYSeries("Series 2");
for (int i = 1; i < inDistribution[1].length; i++) {
if (inDistribution[1][i] > 0) {
inSeries2.add((Math.log(inDistribution[0][i]) / Math.log(Math.E)), (Math.log(inDistribution[1][i]) / Math.log(Math.E)));
inMax = (float) Math.max((Math.log(inDistribution[0][i]) / Math.log(Math.E)), inMax);
}
}
double inA = inAlpha;
double inB = inBeta;
String inImageFile = "";
String outImageFile = "";
try {
XYSeries inSeries1 = new XYSeries(inAlpha + " ");
inSeries1.add(0, inA);
inSeries1.add(inMax, inA + inB * inMax);
XYSeriesCollection inDataset = new XYSeriesCollection();
inDataset.addSeries(inSeries1);
inDataset.addSeries(inSeries2);
JFreeChart inChart = ChartFactory.createXYLineChart(
"In-Degree Distribution",
"In-Degree",
"Occurrence",
inDataset,
PlotOrientation.VERTICAL,
true,
false,
false);
XYPlot inPlot = (XYPlot) inChart.getPlot();
XYLineAndShapeRenderer inRenderer = new XYLineAndShapeRenderer();
inRenderer.setSeriesLinesVisible(0, true);
inRenderer.setSeriesShapesVisible(0, false);
inRenderer.setSeriesLinesVisible(1, false);
inRenderer.setSeriesShapesVisible(1, true);
inRenderer.setSeriesShape(1, new java.awt.geom.Ellipse2D.Double(0, 0, 1, 1));
inPlot.setBackgroundPaint(java.awt.Color.WHITE);
inPlot.setDomainGridlinePaint(java.awt.Color.GRAY);
inPlot.setRangeGridlinePaint(java.awt.Color.GRAY);
inPlot.setRenderer(inRenderer);
final ChartRenderingInfo info = new ChartRenderingInfo(new StandardEntityCollection());
TempDir tempDir = TempDirUtils.createTempDir();
final String fileName = "inDistribution.png";
final File file1 = tempDir.createFile(fileName);
inImageFile = "<IMG SRC=\"file:" + file1.getAbsolutePath() + "\" " + "WIDTH=\"600\" HEIGHT=\"400\" BORDER=\"0\" USEMAP=\"#chart\"></IMG>";
ChartUtilities.saveChartAsPNG(file1, inChart, 600, 400, info);
double outMax = 0;
XYSeries outSeries2 = new XYSeries("Series 2");
for (int i = 1; i < outDistribution[1].length; i++) {
if (outDistribution[1][i] > 0) {
outSeries2.add((Math.log(outDistribution[0][i]) / Math.log(Math.E)), (Math.log(outDistribution[1][i]) / Math.log(Math.E)));
outMax = (float) Math.max((Math.log(outDistribution[0][i]) / Math.log(Math.E)), outMax);
}
}
double outA = outAlpha;
double outB = outBeta;
XYSeries outSeries1 = new XYSeries(outAlpha + " ");
outSeries1.add(0, outA);
outSeries1.add(outMax, outA + outB * outMax);
XYSeriesCollection outDataset = new XYSeriesCollection();
outDataset.addSeries(outSeries1);
outDataset.addSeries(outSeries2);
JFreeChart outchart = ChartFactory.createXYLineChart(
"Out-Degree Distribution",
"Out-Degree",
"Occurrence",
outDataset,
PlotOrientation.VERTICAL,
true,
false,
false);
XYPlot outPlot = (XYPlot) outchart.getPlot();
XYLineAndShapeRenderer outRenderer = new XYLineAndShapeRenderer();
outRenderer.setSeriesLinesVisible(0, true);
outRenderer.setSeriesShapesVisible(0, false);
outRenderer.setSeriesLinesVisible(1, false);
outRenderer.setSeriesShapesVisible(1, true);
outRenderer.setSeriesShape(1, new java.awt.geom.Ellipse2D.Double(0, 0, 1, 1));
outPlot.setBackgroundPaint(java.awt.Color.WHITE);
outPlot.setDomainGridlinePaint(java.awt.Color.GRAY);
outPlot.setRangeGridlinePaint(java.awt.Color.GRAY);
outPlot.setRenderer(outRenderer);
final ChartRenderingInfo info2 = new ChartRenderingInfo(new StandardEntityCollection());
final String fileName2 = "outDistribution.png";
final File file2 = tempDir.createFile(fileName2);
outImageFile = "<IMG SRC=\"file:" + file2.getAbsolutePath() + "\" " + "WIDTH=\"600\" HEIGHT=\"400\" BORDER=\"0\" USEMAP=\"#chart\"></IMG>";
ChartUtilities.saveChartAsPNG(file2, outchart, 600, 400, info2);
} catch (IOException e) {
Exceptions.printStackTrace(e);
}
String report = "<HTML> <BODY> <h1>Degree Distribution Metric Report </h1> "
+ "<hr>"
+ "<br>"
+ "<h2> Parameters: </h2>"
+ "Network Interpretation: " + (isDirected ? "directed" : "undirected") + "<br>"
+ "<br> <h2> Results: </h2>"
+ "In-Degree Power Law: -" + inAlpha + "\n <BR>"
+ inImageFile + "<br>Out-Degree Power Law: -" + outAlpha + "\n <BR>"
+ outImageFile + "</BODY> </HTML>";
return report;
}
/**
*
* @return The undirected version of this report.
*/
private String getUndirectedReport() {
double max = 0;
XYSeries series2 = new XYSeries("Series 2");
for (int i = 1; i < combinedDistribution[1].length; i++) {
if (combinedDistribution[1][i] > 0) {
series2.add((Math.log(combinedDistribution[0][i]) / Math.log(Math.E)), (Math.log(combinedDistribution[1][i]) / Math.log(Math.E)));
max = (float) Math.max((Math.log(combinedDistribution[0][i]) / Math.log(Math.E)), max);
}
}
double a = combinedAlpha;
double b = combinedBeta;
XYSeries series1 = new XYSeries(combinedAlpha + " ");
series1.add(0, a);
series1.add(max, a + b * max);
XYSeriesCollection dataset = new XYSeriesCollection();
dataset.addSeries(series1);
dataset.addSeries(series2);
JFreeChart chart = ChartFactory.createXYLineChart(
"Degree Distribution",
"Degree",
"Occurrence",
dataset,
PlotOrientation.VERTICAL,
true,
false,
false);
XYPlot plot = (XYPlot) chart.getPlot();
XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer();
renderer.setSeriesLinesVisible(0, true);
renderer.setSeriesShapesVisible(0, false);
renderer.setSeriesLinesVisible(1, false);
renderer.setSeriesShapesVisible(1, true);
renderer.setSeriesShape(1, new java.awt.geom.Ellipse2D.Double(0, 0, 1, 1));
plot.setBackgroundPaint(java.awt.Color.WHITE);
plot.setDomainGridlinePaint(java.awt.Color.GRAY);
plot.setRangeGridlinePaint(java.awt.Color.GRAY);
plot.setRenderer(renderer);
String imageFile = "";
try {
final ChartRenderingInfo info = new ChartRenderingInfo(new StandardEntityCollection());
TempDir tempDir = TempDirUtils.createTempDir();
final String fileName = "distribution.png";
final File file1 = tempDir.createFile(fileName);
imageFile = "<IMG SRC=\"file:" + file1.getAbsolutePath() + "\" " + "WIDTH=\"600\" HEIGHT=\"400\" BORDER=\"0\" USEMAP=\"#chart\"></IMG>";
ChartUtilities.saveChartAsPNG(file1, chart, 600, 400, info);
} catch (IOException e) {
System.out.println(e.toString());
}
String report = "<HTML> <BODY> <h1>Degree Distribution Metric Report </h1> "
+ "<hr>"
+ "<br>"
+ "<h2> Parameters: </h2>"
+ "Network Interpretation: " + (isDirected ? "directed" : "undirected") + "<br>"
+ "<br> <h2> Results: </h2>"
+ "Degree Power Law: -" + combinedAlpha + "\n <BR>"
+ imageFile + "</BODY> </HTML>";
return report;
}
/**
* @return Indicates that the metric canceled flag was set.
*/
public boolean cancel() {
isCanceled = true;
return true;
}
/**
* @param progressTicket Sets the progress meter for the metric.
*/
public void setProgressTicket(ProgressTicket pProgressTicket) {
progress = pProgressTicket;
}
}