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

org.conqat.lib.commons.treemap.SquarifiedTreeMapAlgorithm Maven / Gradle / Ivy

There is a newer version: 2024.7.2
Show newest version
/*
 * Copyright (c) CQSE GmbH
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.conqat.lib.commons.treemap;

import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import org.conqat.lib.commons.collections.CollectionUtils;

/**
 * A layout algorithm using the squarified layout approach described in Mark Bruls, Kees Huizing,
 * and Jarke J. van Wijk: "Squarified Treemaps". This algorithm will maintain nearly perfect squares
 * but does not preserve order.
 * 
 * @see "http://www.win.tue.nl/~vanwijk/stm.pdf"
 */
public class SquarifiedTreeMapAlgorithm implements ITreeMapLayoutAlgorithm {

	/** {@inheritDoc} */
	@Override
	public  void layout(ITreeMapNode rootNode, Rectangle2D targetArea) {
		rootNode.setLayoutRectangle(targetArea);
		layoutChildren(rootNode);
	}

	/** Layouts the children of the given node (if it has any). */
	private  void layoutChildren(ITreeMapNode node) {
		if (node.getChildren().isEmpty()) {
			return;
		}

		Rectangle2D rect = node.getLayoutRectangle();
		double areaScale = rect.getWidth() * rect.getHeight() / node.getArea();

		// sort larger nodes to the front
		List> sortedNodes = new ArrayList<>(node.getChildren());
		Collections.sort(sortedNodes, new Comparator>() {

			@Override
			public int compare(ITreeMapNode node1, ITreeMapNode node2) {
				return Double.compare(node2.getArea(), node1.getArea());
			}
		});

		while (!sortedNodes.isEmpty() && CollectionUtils.getLast(sortedNodes).getArea() <= 0) {
			sortedNodes.remove(sortedNodes.size() - 1);
		}

		int start = 0;
		double shorterSide = Math.min(rect.getWidth(), rect.getHeight());
		for (int end = 1; end <= sortedNodes.size();) {
			if (end < sortedNodes.size() && worstAspectRatio(sortedNodes.subList(start, end), shorterSide,
					areaScale) > worstAspectRatio(sortedNodes.subList(start, end + 1), shorterSide, areaScale)) {
				end += 1;
			} else {
				rect = layoutRow(sortedNodes.subList(start, end), rect, areaScale);
				shorterSide = Math.min(rect.getWidth(), rect.getHeight());
				start = end;
				end = start + 1;
			}
		}

		for (ITreeMapNode child : sortedNodes) {
			layoutChildren(child);
		}
	}

	/**
	 * Layouts the given nodes as a row along the shorter side of the rectangle.
	 */
	private static  Rectangle2D layoutRow(List> nodes, Rectangle2D rect, double areaScale) {
		double overallArea = getArea(nodes);
		if (rect.getWidth() < rect.getHeight()) {
			double height = overallArea * areaScale / rect.getWidth();
			double x = rect.getX();
			for (ITreeMapNode node : nodes) {
				double nodeWidth = node.getArea() * areaScale / height;
				node.setLayoutRectangle(new Rectangle2D.Double(x, rect.getY(), nodeWidth, height));
				x += nodeWidth;
			}
			return new Rectangle2D.Double(rect.getX(), rect.getY() + height, rect.getWidth(),
					rect.getHeight() - height);
		}

		double width = overallArea * areaScale / rect.getHeight();
		double y = rect.getY();
		for (ITreeMapNode node : nodes) {
			double nodeHeight = node.getArea() * areaScale / width;
			node.setLayoutRectangle(new Rectangle2D.Double(rect.getX(), y, width, nodeHeight));
			y += nodeHeight;
		}
		return new Rectangle2D.Double(rect.getX() + width, rect.getY(), rect.getWidth() - width, rect.getHeight());
	}

	/**
	 * Returns the worst aspect ratio is the given nodes were layouted in a rectangle with this side
	 * length.
	 */
	private static  double worstAspectRatio(List> nodes, double minSide, double areaScale) {
		double overallArea = getArea(nodes) * areaScale;
		double side = overallArea / minSide;
		double worst = 1;
		for (ITreeMapNode node : nodes) {
			double aspect = node.getArea() * areaScale / side / side;
			worst = Math.max(worst, Math.max(aspect, 1 / aspect));
		}
		return worst;
	}

	/** Returns the accumulated area for a list of nodes. */
	private static  double getArea(List> nodes) {
		double area = 0;
		for (ITreeMapNode node : nodes) {
			area += node.getArea();
		}
		return area;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy