org.conqat.lib.commons.treemap.StripeTreeMapAlgorithm Maven / Gradle / Ivy
Show all versions of teamscale-lib-commons Show documentation
/*
* 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.List;
/**
* The strip layout algorithm adapted from Bederson, Shneiderman, Wattenberg: "Ordered and Quantum
* Treemaps".
*
* This is useful as it tries to minimize the aspect ratio of the generated squares while
* maintaining the original order.
*
* @author Benjamin Hummel
*/
public class StripeTreeMapAlgorithm implements ITreeMapLayoutAlgorithm {
/** {@inheritDoc} */
@Override
public void layout(ITreeMapNode tree, Rectangle2D target) {
tree.setLayoutRectangle(target);
layoutChildren(tree);
}
/** 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 scale = rect.getWidth() * rect.getHeight() / node.getArea();
double layoutX = rect.getMinX();
double lastX = 0;
List> l = new ArrayList<>();
List> lastList = new ArrayList<>();
for (ITreeMapNode child : node.getChildren()) {
double oldAspect = calcAvgAspect(l, rect.getHeight(), scale);
l.add(child);
double newAspect = calcAvgAspect(l, rect.getHeight(), scale);
if (oldAspect < newAspect) {
l.remove(l.size() - 1);
lastX = layoutX;
lastList.clear();
lastList.addAll(l);
layoutX += layoutList(l, rect.getHeight(), layoutX, rect.getMinY(), scale);
l.clear();
l.add(child);
}
}
// last list might be too small, so potentially merge with previous one
lastList.addAll(l);
if (calcAvgAspect(lastList, rect.getHeight(), scale) < calcAvgAspect(l, rect.getHeight(), scale)) {
layoutList(lastList, rect.getHeight(), lastX, rect.getMinY(), scale);
} else {
layoutList(l, rect.getHeight(), layoutX, rect.getMinY(), scale);
}
}
/**
* Calculates the average aspect ratio of the given list of nodes if the provided height may be
* used.
*/
private static double calcAvgAspect(List> l, double layoutHeight, double areaScale) {
if (l.isEmpty()) {
return 1e8;
}
double area = 0;
for (ITreeMapNode node : l) {
area += node.getArea();
}
double layoutWidth = area * areaScale / layoutHeight;
double aspectSum = 0;
for (ITreeMapNode node : l) {
double localHeight = node.getArea() * areaScale / layoutWidth;
double localAspect = Math.max(layoutWidth / localHeight, localHeight / layoutWidth);
aspectSum += localAspect;
}
return aspectSum / l.size();
}
/**
* Layout the given list of nodes in one column.
*
* @param l
* the list of nodes.
* @param layoutHeight
* the height of the column.
* @param layoutX
* the x start value.
* @param layoutY
* the y start value.
* @param areaScale
* the scale factor used to convert from node area to layout area.
* @return the layout width of the column.
*/
private double layoutList(List> l, double layoutHeight, double layoutX, double layoutY,
double areaScale) {
double nodeArea = 0;
for (ITreeMapNode node : l) {
nodeArea += node.getArea();
}
double layoutWidth = nodeArea * areaScale / layoutHeight;
for (ITreeMapNode node : l) {
double nodeHeight = node.getArea() * areaScale / layoutWidth;
node.setLayoutRectangle(new Rectangle2D.Double(layoutX, layoutY, layoutWidth, nodeHeight));
layoutY += nodeHeight;
layoutChildren(node);
}
return layoutWidth;
}
}