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

com.googlecode.blaisemath.graph.modules.layout.ComponentCircleLayout Maven / Gradle / Ivy

The newest version!
/*
 * ComponentCircleLayout.java
 * Created on Nov 28, 2011
 */
package com.googlecode.blaisemath.graph.modules.layout;

/*
 * #%L
 * BlaiseGraphTheory
 * --
 * Copyright (C) 2009 - 2016 Elisha Peterson
 * --
 * 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.
 * #L%
 */

import com.google.common.collect.Maps;
import com.googlecode.blaisemath.graph.Graph;
import com.googlecode.blaisemath.graph.GraphUtils;
import com.googlecode.blaisemath.graph.StaticGraphLayout;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

/**
 * This layout places components of a graph in a spiral pattern, with the
 * largest component in the center. The nodes in each component are placed
 * around a circle. Provides a good starting location (when graphs have many
 * components) for iterative layout algorithms that ensures things don't get too
 * far apart, and nodes in the same component start nearby.
 *
 * @author petereb1
 */
public class ComponentCircleLayout implements StaticGraphLayout {

    public static final int DIST_SCALE = 50;
    
    public static final Comparator GRAPH_SIZE_DESCENDING = new Comparator() {
        @Override
        public int compare(Graph o1, Graph o2) {
            return -(o1.nodeCount() == o2.nodeCount() && o1.edgeCount() == o2.edgeCount() ? o1.nodes().toString().compareTo(o2.nodes().toString())
                    : o1.nodeCount() == o2.nodeCount() ? o1.edgeCount() - o2.edgeCount()
                    : o1.nodeCount() - o2.nodeCount());
        }
    };
    
    @Override
    public String toString() {
        return "ComponentCircleLayout";
    }
    
    @Override
    public Class getParametersType() {
        return Void.class;
    }

    @Override
    public  Map layout(Graph graph, Map ic,
            Set fixed, Void parameters) {
        if (graph.isDirected()) {
            graph = GraphUtils.copyAsUndirectedSparseGraph(graph);
        }
        Set components = new TreeSet(GRAPH_SIZE_DESCENDING);
        components.addAll(GraphUtils.componentGraphs(graph));

        Map result = Maps.newHashMap();

        List priors = new ArrayList();
        List layers = new ArrayList();
        for (Graph compt : components) {
            result.putAll(layoutNext(compt, priors, layers));
        }

        return result;
    }

    private static  Map layoutNext(Graph graph, List priors, List layers) {
        Set nodes = graph.nodes();
        int n = nodes.size();

        Map result = Maps.newHashMap();

        Rectangle2D.Double nxt = nextBounds(n, priors, layers);
        double cx = nxt.getCenterX(), cy = nxt.getCenterY();
        double rad = Math.min(nxt.getWidth(), nxt.getHeight());

        if (n == 3 && graph.edgeCount() == 2) {
            Object[] nn = nodes.toArray();
            if (graph.degree((C) nn[0]) == 2) {
                Object nt = nn[1];
                nn[1] = nn[0];
                nn[0] = nt;
            }
            if (graph.degree((C) nn[2]) == 2) {
                Object nt = nn[1];
                nn[1] = nn[2];
                nn[2] = nt;
            }
            assert graph.degree((C) nn[1]) == 2;
            result.put((C) nn[0], new Point2D.Double(cx - rad, 0));
            result.put((C) nn[1], new Point2D.Double(0, 0));
            result.put((C) nn[2], new Point2D.Double(cx + rad, 0));
        } else {
            int rots = (int) (Math.sqrt(n) / 2);
            int i = 0;
            for (C o : nodes) {
                double pct = i++ / (double) n;
                double theta = rots * 2 * Math.PI * pct;
                result.put(o, new Point2D.Double(cx + pct * rad * Math.cos(theta), cy + pct * rad * Math.sin(theta)));
            }
        }
        return result;
    }

    /**
     * Generates position in a sequence of circles.
     */
    private static Rectangle2D.Double nextBounds(int sz, List priors, List layers) {
        double nueRad = DIST_SCALE * Math.sqrt(sz) / Math.PI;
        if (priors.isEmpty()) {
            Rectangle2D.Double r = new Rectangle2D.Double(-nueRad, -nueRad, 2 * nueRad, 2 * nueRad);
            priors.add(r);
            layers.add(0);
            return r;
        }

        Rectangle2D.Double ctr = priors.get(0);
        double cx = ctr.getCenterX(), cy = ctr.getCenterY();
        double cRad = Math.min(ctr.getWidth(), ctr.getHeight());

        if (priors.size() == 1) {
            Rectangle2D.Double r = new Rectangle2D.Double(cx + cRad, cy - nueRad, 2 * nueRad, 2 * nueRad);
            priors.add(r);
            layers.add(1);
            return r;
        } else {
            int lSz = layers.size();
            int layer = layers.get(lSz - 1);
            int startLayer = 0;
            while (layers.get(startLayer) < layer) {
                startLayer++;
            }
            Rectangle2D.Double lProto = priors.get(startLayer);
            double lRad1 = Math.min(lProto.getWidth(), lProto.getHeight());
            double lRad2 = Math.abs(cx - lProto.getCenterX());
            double availableOnLayer = 2 * Math.PI * lRad2; // how much space in this radial layer for layout
            int alreadyOnLayer = lSz - startLayer + 1;
            if (alreadyOnLayer * 2 * lRad1 > availableOnLayer - 2 * lRad1) {
                // start a new layer
                layer++;
                Rectangle2D.Double r = new Rectangle2D.Double(cx + lRad2 + lRad1, cy - nueRad, 2 * nueRad, 2 * nueRad);
                priors.add(r);
                layers.add(layer);
                return r;
            } else {
                // continue existing layer
                double theta = 2 * lRad1 / lRad2 * alreadyOnLayer;
                Rectangle2D.Double r = new Rectangle2D.Double(cx + lRad2 * Math.cos(theta) - nueRad, cy + lRad2 * Math.sin(theta) - nueRad, 2 * nueRad, 2 * nueRad);
                priors.add(r);
                layers.add(layer);
                return r;
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy