/*
Copyright 2008-2010 Gephi
Authors : Mathieu Bastian <mathieu.bastian@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.desktop.hierarchy;
import java.awt.Color;
import java.awt.Graphics;
import java.util.ArrayList;
import javax.swing.JPanel;
import org.gephi.graph.api.HierarchicalGraph;
import org.gephi.graph.api.Node;
public class Dendrogram extends JPanel {
//Graph
private HierarchicalGraph graph;
//Internal
private static final int MARGIN = 10;
private int numObjects;
private double maxDistance;
private double minDistance;
private int maxX;
private int maxY;
private int count;
private Color color = Color.BLACK;
private DendrogramNode root;
//Settings
private int maxHeight = 0;
public Dendrogram() {
}
public void refresh(HierarchicalGraph graph) {
this.graph = graph;
if (graph != null) {
numObjects = graph.getNodeCount();
maxHeight = Math.min(graph.getHeight() + 1, maxHeight);
root = buildTree();
//MinMaxDistance
minDistance = Double.POSITIVE_INFINITY;
maxDistance = Double.NEGATIVE_INFINITY;
findMinMaxDistance(root);
} else {
root = null;
}
}
public int getMaxHeight() {
return maxHeight;
}
public void setMaxHeight(int maxHeight) {
this.maxHeight = maxHeight;
}
private DendrogramNode buildTree() {
DendrogramNode rootNode = new DendrogramNode(new DendrogramNode[0]);
Node[] topNodes = graph.getTopNodes().toArray();
if (topNodes.length == 1) {
rootNode = traverseTree(topNodes[0]);
} else if (topNodes.length > 1) {
DendrogramNode[] children = new DendrogramNode[topNodes.length];
for (int i = 0; i < topNodes.length; i++) {
children[i] = traverseTree(topNodes[i]);
}
rootNode = new DendrogramNode(children);
}
return rootNode;
}
private DendrogramNode traverseTree(Node node) {
Node[] nodeChildren = graph.getChildren(node).toArray();
DendrogramNode[] children = new DendrogramNode[nodeChildren.length];
int height = 0;
for (int i = 0; i < nodeChildren.length; i++) {
children[i] = traverseTree(nodeChildren[i]);
height = Math.max(height, children[i].getHeight());
}
DendrogramNode dendrogramNode = new DendrogramNode(children);
if (children.length == 0) {
dendrogramNode.setHeight(0);
} else {
dendrogramNode.setHeight(height + 1);
}
if (graph.isInView(node)) {
dendrogramNode.setRed(true);
}
return dendrogramNode;
}
private void findMinMaxDistance(DendrogramNode node) {
double distance = node.getDistance();
maxDistance = Math.max(maxDistance, distance);
minDistance = Math.min(minDistance, distance);
for (DendrogramNode subNode : node.getChildren()) {
if (subNode != null) {
findMinMaxDistance(subNode);
}
}
}
private void drawLine(int x1, int y1, int x2, int y2, Graphics g) {
g.drawLine(x1, y1, x2, y2);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
/*if ((minDistance == maxDistance) || (Double.isNaN(minDistance)) || (Double.isInfinite(minDistance)) ||
(Double.isNaN(maxDistance)) || (Double.isInfinite(maxDistance))) {
g.drawString("Dendrogram not available for this cluster model. Use an agglomerative clusterer.", MARGIN, MARGIN + 15);
return;
}*/
this.maxX = getWidth() - 2 * MARGIN;
this.maxY = getHeight() - 2 * MARGIN;
g.setColor(Color.WHITE);
g.fillRect(0, 0, getWidth(), getHeight());
Graphics translated = g.create();
translated.translate(MARGIN, MARGIN);
count = 0;
if (root != null) {
paintRecursively(root, root.getDistance(), translated);
}
}
private int weightToYPos(double weight) {
return (int) Math.round(maxY * (((maxDistance - weight) - minDistance) / ((maxDistance - minDistance))));
}
private int countToXPos(int count) {
return (int) Math.round((((double) count) / ((double) numObjects)) * ((double) maxX));
}
private int paintRecursively(DendrogramNode node, double baseDistance, Graphics g) {
int leftPos = -1;
int rightPos = -1;
// doing recursive descent
for (DendrogramNode subNode : node.getChildren()) {
if (subNode != null) {
if (subNode.getChildren().length > 0) {
int currentPos = paintRecursively(subNode, node.getDistance(), g);
if (leftPos == -1) {
leftPos = currentPos;
}
rightPos = currentPos;
}
}
}
g.setColor(color);
// drawing vertical cluster lines of one elemental clusters
for (DendrogramNode subNode : node.getChildren()) {
if (subNode != null) {
if (subNode.getChildren().length == 0) {
int currentPos = countToXPos(count);
if (subNode.isRed()) {
g.setColor(Color.RED);
}
drawLine(currentPos, weightToYPos(node.getDistance()), currentPos, weightToYPos(minDistance), g);
g.setColor(color);
if (leftPos == -1) {
leftPos = currentPos;
}
rightPos = currentPos;
count++;
}
}
}
int middlePos = (rightPos + leftPos) / 2;
if (node.isRed()) {
g.setColor(Color.RED);
}
// painting vertical connections of merged clusters to next cluster
drawLine(middlePos, weightToYPos(baseDistance), middlePos, weightToYPos(node.getDistance()), g);
// painting horizontal connections of merged clusters
drawLine(leftPos, weightToYPos(node.getDistance()), rightPos, weightToYPos(node.getDistance()), g);
return middlePos;
}
public void prepareRendering() {
}
public void finishRendering() {
}
public int getRenderHeight(int preferredHeight) {
int height = getHeight();
if (height < 1) {
height = preferredHeight;
}
return height;
}
public int getRenderWidth(int preferredWidth) {
int width = getWidth();
if (width < 1) {
width = preferredWidth;
}
return width;
}
public void render(Graphics graphics, int width, int height) {
setSize(width, height);
paint(graphics);
}
private class DendrogramNode {
private DendrogramNode[] children;
private double distance;
private boolean red = false;
private int height;
public DendrogramNode(DendrogramNode[] children) {
this.children = children;
distance = children.length;
for (int i = 0; i < children.length; i++) {
DendrogramNode child = children[i];
if (child.height < maxHeight) {
distance -= child.distance;
this.children[i] = null;
numObjects--;
distance--;
} else {
distance += child.distance;
}
}
if (distance == 0 && children.length > 0) {
this.children = new DendrogramNode[0];
}
}
public void removeChildren(int index) {
distance -= children[index].distance;
children[index] = null;
}
public void setHeight(int height) {
this.height = height;
}
public int getHeight() {
return height;
}
public DendrogramNode[] getChildren() {
return children;
}
public double getDistance() {
return distance;
}
public boolean isRed() {
return red;
}
public void setRed(boolean red) {
this.red = red;
}
}
}