All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.graphstream.ui.layout.HierarchicalLayout Maven / Gradle / Ivy

Go to download

The GraphStream library. With GraphStream you deal with graphs. Static and Dynamic. You create them from scratch, from a file or any source. You display and render them. This package contains algorithms and generators.

There is a newer version: 2.0
Show newest version
/*
 * Copyright 2006 - 2015
 *     Stefan Balev     
 *     Julien Baudry    
 *     Antoine Dutot    
 *     Yoann Pigné      
 *     Guilhelm Savin   
 * 
 * This file is part of GraphStream .
 * 
 * GraphStream is a library whose purpose is to handle static or dynamic
 * graph, create them from scratch, file or any source and display them.
 * 
 * This program is free software distributed under the terms of two licenses, the
 * CeCILL-C license that fits European law, and the GNU Lesser General Public
 * License. You can  use, modify and/ or redistribute the software under the terms
 * of the CeCILL-C license as circulated by CEA, CNRS and INRIA at the following
 * URL  or under the terms of the GNU LGPL as published by
 * the Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 * 
 * This program 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 Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see .
 * 
 * The fact that you are presently reading this means that you have had
 * knowledge of the CeCILL-C and LGPL licenses and that you accept their terms.
 */
package org.graphstream.ui.layout;

import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;

import org.graphstream.algorithm.Prim;
import org.graphstream.algorithm.SpanningTree;
import org.graphstream.algorithm.generator.BarabasiAlbertGenerator;
import org.graphstream.algorithm.util.FibonacciHeap;
import org.graphstream.graph.Edge;
import org.graphstream.graph.Graph;
import org.graphstream.graph.Node;
import org.graphstream.graph.implementations.AdjacencyListGraph;
import org.graphstream.stream.PipeBase;
import org.graphstream.ui.geom.Point3;
import org.graphstream.ui.view.Viewer;

public class HierarchicalLayout extends PipeBase implements Layout {

	public static enum Rendering {
		VERTICAL, HORIZONTAL, DISK
	}

	static class Position {
		int level;
		int order;
		String parent;
		boolean changed;
		double x, y;

		Position(int level, int order) {
			this.level = level;
			this.order = order;
			this.changed = true;
		}
	}

	final HashMap nodesPosition;
	final LinkedList roots;
	final Graph internalGraph;

	boolean structureChanged;

	Rendering renderingType;

	Point3 hi, lo;

	long lastStep;

	int nodeMoved;

	double distanceBetweenLevels = 1;
	double levelWidth = 1, levelHeight = 1;

	public HierarchicalLayout() {
		roots = new LinkedList();
		// listeners = new LinkedList();
		nodesPosition = new HashMap();
		internalGraph = new AdjacencyListGraph("hierarchical_layout-intern");
		hi = new Point3();
		lo = new Point3();
		renderingType = Rendering.VERTICAL;
	}

	public void setRoots(String... rootsId) {
		roots.clear();

		if (rootsId != null) {
			for (String id : rootsId)
				roots.add(id);
		}
	}

	// public void addListener(LayoutListener listener) {
	// listeners.add(listener);
	// }

	public void clear() {
		// TODO Auto-generated method stub

	}

	public void compute() {
		nodeMoved = 0;

		if (structureChanged) {
			structureChanged = false;
			computePositions();
		}

		publishPositions();
		lastStep = System.currentTimeMillis();
	}

	protected void computePositions() {
		final int[] levels = new int[internalGraph.getNodeCount()];
		Arrays.fill(levels, -1);

		final int[] columns = new int[internalGraph.getNodeCount()];

		LinkedList roots = new LinkedList(), roots2 = new LinkedList();

		if (this.roots.size() > 0) {
			for (int i = 0; i < this.roots.size(); i++)
				roots.add(internalGraph.getNode(this.roots.get(i)));
		}

		SpanningTree tree = new Prim("weight", "inTree");
		tree.init(internalGraph);
		tree.compute();

		if (roots.size() == 0) {
			int max = internalGraph.getNode(0).getDegree();
			int maxIndex = 0;

			for (int i = 1; i < internalGraph.getNodeCount(); i++)
				if (internalGraph.getNode(i).getDegree() > max) {
					max = internalGraph.getNode(i).getDegree();
					maxIndex = i;
				}

			roots.add(internalGraph.getNode(maxIndex));
		}

		Box rootBox = new Box();
		LevelBox rootLevelBox = new LevelBox(0);
		LinkedList levelBoxes = new LinkedList();

		rootLevelBox.add(rootBox);
		levelBoxes.add(rootLevelBox);

		for (int i = 0; i < roots.size(); i++) {
			Node n = roots.get(i);
			levels[n.getIndex()] = 0;
			columns[n.getIndex()] = i;
			setBox(rootBox, n);
		}

		do {
			while (roots.size() > 0) {
				Node root = roots.poll();
				int level = levels[root.getIndex()] + 1;
				Box box = getChildrenBox(root);

				for (Edge e : root.getEdgeSet()) {
					if (e.getAttribute(tree.getFlagAttribute()).equals(
							tree.getFlagOn())) {
						Node op = e.getOpposite(root);

						if (levels[op.getIndex()] < 0
								|| level < levels[op.getIndex()]) {
							levels[op.getIndex()] = level;
							roots2.add(op);
							op.setAttribute("parent", root);
							setBox(box, op);
						}
					}
				}
			}

			roots.addAll(roots2);
			roots2.clear();
		} while (roots.size() > 0);

		FibonacciHeap boxes = new FibonacciHeap();
		boxes.add(0, rootBox);

		for (int i = 0; i < internalGraph.getNodeCount(); i++) {
			Box box = getChildrenBox(internalGraph.getNode(i));

			if (box != null) {
				boxes.add(box.level, box);

				while (levelBoxes.size() <= box.level)
					levelBoxes.add(new LevelBox(levelBoxes.size()));

				levelBoxes.get(box.level).add(box);
			}
		}

		for (int i = 0; i < levelBoxes.size(); i++)
			levelBoxes.get(i).sort();

		while (boxes.size() > 0)
			renderBox(boxes.extractMin());

		hi.x = hi.y = Double.MIN_VALUE;
		lo.x = lo.y = Double.MAX_VALUE;

		for (int idx = 0; idx < internalGraph.getNodeCount(); idx++) {
			Node n = internalGraph.getNode(idx);
			double y = n.getNumber("y");
			double x = n.getNumber("x");

			if (!n.hasNumber("oldX") || n.getNumber("oldX") != x
					|| !n.hasNumber("oldY") || n.getNumber("oldY") != y) {
				n.setAttribute("oldX", x);
				n.setAttribute("oldY", y);
				n.addAttribute("changed");
				nodeMoved++;
			}

			hi.x = Math.max(hi.x, x);
			hi.y = Math.max(hi.y, y);
			lo.x = Math.min(lo.x, x);
			lo.y = Math.min(lo.y, y);
		}
	}

	protected void setBox(Box box, Node node) {
		if (node.hasAttribute("box"))
			getBox(node).remove(node);

		box.add(node);
		node.setAttribute("box", box);

		if (!node.hasAttribute("children"))
			node.addAttribute("children", new Box(node, 1));

		getChildrenBox(node).level = box.level + 1;
	}

	protected static Box getBox(Node node) {
		Box box = node.getAttribute("box");
		return box;
	}

	protected static Box getChildrenBox(Node node) {
		Box box = node.getAttribute("children");
		return box;
	}

	protected void renderBox(Box box) {
		if (box.size() == 0)
			return;

		for (int i = 0; i < box.size(); i++) {
			Node n = box.get(i);

			switch (renderingType) {
			case VERTICAL:
				n.setAttribute("x", box.width * i / (double) box.size());
				n.setAttribute("y", box.height / 2);
				break;
			case DISK:
			case HORIZONTAL:
				n.setAttribute("x", box.width / 2);
				n.setAttribute("y", box.height * i / (double) box.size());
				break;
			}
		}

		double sx = 1, sy = 1;
		double dx = 0, dy = 0;

		if (box.parent != null) {
			Box parentBox = getBox(box.parent);

			switch (renderingType) {
			case VERTICAL:
				sx = 1 / (double) parentBox.size();
				sy = 1 / Math.pow(2, box.level);
				break;
			case DISK:
			case HORIZONTAL:
				sx = 1 / Math.pow(2, box.level);
				sy = 1 / (double) parentBox.size();
				break;
			}
		}

		box.scale(sx, sy);

		if (box.parent != null) {
			Box parentBox = getBox(box.parent);

			dx = box.parent.getNumber("x");
			dy = box.parent.getNumber("y");

			switch (renderingType) {
			case VERTICAL:
				dx -= box.width / 2;
				dy += parentBox.height / 2;
				break;
			case DISK:
			case HORIZONTAL:
				dx += parentBox.width / 2;
				dy -= box.height / 2;
				break;
			}
		}

		box.translate(dx, dy);
	}

	protected void explore(Node parent, Node who, SpanningTree tree,
			int[] levels) {

	}

	protected void publishPositions() {
		for (Node n : internalGraph) {
			if (n.hasAttribute("changed")) {
				n.removeAttribute("changed");

				sendNodeAttributeChanged(sourceId, n.getId(), "xyz", null,
						new double[] { n.getNumber("x"), n.getNumber("y"), 0 });
			}
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.graphstream.ui.layout.Layout#freezeNode(java.lang.String,
	 * boolean)
	 */
	public void freezeNode(String id, boolean frozen) {
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.graphstream.ui.layout.Layout#getForce()
	 */
	public double getForce() {
		return 0;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.graphstream.ui.layout.Layout#getHiPoint()
	 */
	public Point3 getHiPoint() {
		return hi;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.graphstream.ui.layout.Layout#getLastStepTime()
	 */
	public long getLastStepTime() {
		return lastStep;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.graphstream.ui.layout.Layout#getLayoutAlgorithmName()
	 */
	public String getLayoutAlgorithmName() {
		return "Hierarchical";
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.graphstream.ui.layout.Layout#getLowPoint()
	 */
	public Point3 getLowPoint() {
		return lo;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.graphstream.ui.layout.Layout#getNodeMoved()
	 */
	public int getNodeMovedCount() {
		return nodeMoved;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.graphstream.ui.layout.Layout#getQuality()
	 */
	public double getQuality() {
		return 0;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.graphstream.ui.layout.Layout#getStabilization()
	 */
	public double getStabilization() {
		return 1 - nodeMoved / (double) internalGraph.getNodeCount();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.graphstream.ui.layout.Layout#getStabilizationLimit()
	 */
	public double getStabilizationLimit() {
		return 1;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.graphstream.ui.layout.Layout#getSteps()
	 */
	public int getSteps() {
		return 0;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.graphstream.ui.layout.Layout#inputPos(java.lang.String)
	 */
	public void inputPos(String filename) throws IOException {
		throw new UnsupportedOperationException();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.graphstream.ui.layout.Layout#moveNode(java.lang.String, double,
	 * double, double)
	 */
	public void moveNode(String id, double x, double y, double z) {
		// TODO Auto-generated method stub

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.graphstream.ui.layout.Layout#outputPos(java.lang.String)
	 */
	public void outputPos(String filename) throws IOException {
		throw new UnsupportedOperationException();
	}

	// /*
	// * (non-Javadoc)
	// *
	// * @see
	// *
	// org.graphstream.ui.layout.Layout#removeListener(org.graphstream.ui.layout
	// * .LayoutListener)
	// */
	// public void removeListener(LayoutListener listener) {
	// listeners.remove(listener);
	// }

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.graphstream.ui.layout.Layout#setForce(double)
	 */
	public void setForce(double value) {
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.graphstream.ui.layout.Layout#setQuality(int)
	 */
	public void setQuality(double qualityLevel) {
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.graphstream.ui.layout.Layout#setSendNodeInfos(boolean)
	 */
	public void setSendNodeInfos(boolean send) {
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.graphstream.ui.layout.Layout#setStabilizationLimit(double)
	 */
	public void setStabilizationLimit(double value) {
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.graphstream.ui.layout.Layout#shake()
	 */
	public void shake() {
		// No, I will not shake my work
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.graphstream.stream.PipeBase#nodeAdded(java.lang.String, long,
	 * java.lang.String)
	 */
	public void nodeAdded(String sourceId, long timeId, String nodeId) {
		internalGraph.addNode(nodeId);
		structureChanged = true;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.graphstream.stream.PipeBase#nodeRemoved(java.lang.String, long,
	 * java.lang.String)
	 */
	public void nodeRemoved(String sourceId, long timeId, String nodeId) {
		internalGraph.removeNode(nodeId);
		structureChanged = true;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.graphstream.stream.PipeBase#edgeAdded(java.lang.String, long,
	 * java.lang.String, java.lang.String, java.lang.String, boolean)
	 */
	public void edgeAdded(String sourceId, long timeId, String edgeId,
			String fromId, String toId, boolean directed) {
		internalGraph.addEdge(edgeId, fromId, toId, directed);
		structureChanged = true;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.graphstream.stream.PipeBase#edgeRemoved(java.lang.String, long,
	 * java.lang.String)
	 */
	public void edgeRemoved(String sourceId, long timeId, String edgeId) {
		internalGraph.removeEdge(edgeId);
		structureChanged = true;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.graphstream.stream.PipeBase#graphCleared(java.lang.String, long)
	 */
	public void graphCleared(String sourceId, long timeId) {
		internalGraph.clear();
		structureChanged = true;
	}

	static class Box extends LinkedList {
		private static final long serialVersionUID = -1929536876444346726L;

		Node parent;
		int level;
		double x, y;
		double width, height;
		int order;

		Box() {
			this(null, 0);
		}

		Box(Node parent, int level) {
			this.parent = parent;
			this.level = level;
			this.width = 5;
			this.height = 1;
			this.order = 0;
			this.x = 0;
			this.y = 0;
		}

		void scale(double sx, double sy) {
			width *= sx;
			height *= sy;

			for (int i = 0; i < size(); i++) {
				get(i).setAttribute("x", sx * get(i).getNumber("x"));
				get(i).setAttribute("y", sy * get(i).getNumber("y"));
			}
		}

		void translate(double dx, double dy) {
			for (int i = 0; i < size(); i++) {
				get(i).setAttribute("x", dx + get(i).getNumber("x"));
				get(i).setAttribute("y", dy + get(i).getNumber("y"));
			}
		}
	}

	static class LevelBox extends LinkedList {
		private static final long serialVersionUID = -5818919480025868466L;

		int level;

		LevelBox(int level) {
			this.level = level;
		}

		void sort() {
			if (level > 0) {
				Collections.sort(this, new Comparator() {
					public int compare(Box b0, Box b1) {
						Box pb0 = getBox(b0.parent);
						Box pb1 = getBox(b1.parent);

						if (pb0.order < pb1.order)
							return -1;
						else if (pb0.order > pb1.order)
							return 1;

						return 0;
					}
				});
			}

			for (int i = 0; i < size(); i++)
				get(i).order = i;
		}
	}

	public static void main(String... args) {
		Graph g = new AdjacencyListGraph("g");
		BarabasiAlbertGenerator gen = new BarabasiAlbertGenerator();
		HierarchicalLayout hl = new HierarchicalLayout();
		gen.addSink(g);
		gen.begin();
		for (int i = 0; i < 200; i++)
			gen.nextEvents();
		gen.end();

		Viewer v = g.display(false);
		v.enableAutoLayout(hl);
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy