/* Copyright 2008-2010 Gephi Authors : Mathieu Jacomy 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.layout.plugin.forceAtlas; import java.util.ArrayList; import java.util.List; import org.gephi.data.attributes.type.TimeInterval; import org.gephi.dynamic.DynamicUtilities; import org.gephi.graph.api.Edge; import org.gephi.graph.api.HierarchicalGraph; import org.gephi.graph.api.Node; import org.gephi.graph.api.NodeData; import org.gephi.layout.plugin.AbstractLayout; import org.gephi.layout.plugin.ForceVectorUtils; import org.gephi.layout.plugin.ForceVectorNodeLayoutData; import org.gephi.layout.spi.Layout; import org.gephi.layout.spi.LayoutBuilder; import org.gephi.layout.spi.LayoutProperty; import org.openide.util.NbBundle; /** * * @author Mathieu Jacomy */ public class ForceAtlasLayout extends AbstractLayout implements Layout { //Graph protected HierarchicalGraph graph; //Properties public double inertia; private double repulsionStrength; private double attractionStrength; private double maxDisplacement; private boolean freezeBalance; private double freezeStrength; private double freezeInertia; private double gravity; private double speed; private double cooling; private boolean outboundAttractionDistribution; private boolean adjustSizes; //Dynamic Weight private TimeInterval timeInterval; public ForceAtlasLayout(LayoutBuilder layoutBuilder) { super(layoutBuilder); } public void resetPropertiesValues() { inertia = 0.1; setRepulsionStrength(200d); setAttractionStrength(10d); setMaxDisplacement(10d); setFreezeBalance(true); setFreezeStrength(80d); setFreezeInertia(0.2); setGravity(30d); setOutboundAttractionDistribution(false); setAdjustSizes(false); setSpeed(1d); setCooling(1d); } public void initAlgo() { this.graph = graphModel.getHierarchicalGraphVisible(); } public void goAlgo() { this.graph = graphModel.getHierarchicalGraphVisible(); this.timeInterval = DynamicUtilities.getVisibleInterval(dynamicModel); graph.readLock(); Node[] nodes = graph.getNodes().toArray(); Edge[] edges = graph.getEdgesAndMetaEdges().toArray(); for (Node n : nodes) { if (n.getNodeData().getLayoutData() == null || !(n.getNodeData().getLayoutData() instanceof ForceVectorNodeLayoutData)) { n.getNodeData().setLayoutData(new ForceVectorNodeLayoutData()); } } for (Node n : nodes) { ForceVectorNodeLayoutData layoutData = n.getNodeData().getLayoutData(); layoutData.old_dx = layoutData.dx; layoutData.old_dy = layoutData.dy; layoutData.dx *= inertia; layoutData.dy *= inertia; } // repulsion if (isAdjustSizes()) { for (Node n1 : nodes) { for (Node n2 : nodes) { if (n1 != n2) { ForceVectorUtils.fcBiRepulsor_noCollide(n1.getNodeData(), n2.getNodeData(), getRepulsionStrength() * (1 + graph.getDegree(n1)) * (1 + graph.getDegree(n2))); } } } } else { for (Node n1 : nodes) { for (Node n2 : nodes) { if (n1 != n2) { ForceVectorUtils.fcBiRepulsor(n1.getNodeData(), n2.getNodeData(), getRepulsionStrength() * (1 + graph.getDegree(n1)) * (1 + graph.getDegree(n2))); } } } } // attraction if (isAdjustSizes()) { if (isOutboundAttractionDistribution()) { for (Edge e : edges) { Node nf = e.getSource(); Node nt = e.getTarget(); double bonus = (nf.getNodeData().isFixed() || nt.getNodeData().isFixed()) ? (100) : (1); bonus *= getWeight(e); ForceVectorUtils.fcBiAttractor_noCollide(nf.getNodeData(), nt.getNodeData(), bonus * getAttractionStrength() / (1 + graph.getDegree(nf))); } } else { for (Edge e : edges) { Node nf = e.getSource(); Node nt = e.getTarget(); double bonus = (nf.getNodeData().isFixed() || nt.getNodeData().isFixed()) ? (100) : (1); bonus *= getWeight(e); ForceVectorUtils.fcBiAttractor_noCollide(nf.getNodeData(), nt.getNodeData(), bonus * getAttractionStrength()); } } } else { if (isOutboundAttractionDistribution()) { for (Edge e : edges) { Node nf = e.getSource(); Node nt = e.getTarget(); double bonus = (nf.getNodeData().isFixed() || nt.getNodeData().isFixed()) ? (100) : (1); bonus *= getWeight(e); ForceVectorUtils.fcBiAttractor(nf.getNodeData(), nt.getNodeData(), bonus * getAttractionStrength() / (1 + graph.getDegree(nf))); } } else { for (Edge e : edges) { Node nf = e.getSource(); Node nt = e.getTarget(); double bonus = (nf.getNodeData().isFixed() || nt.getNodeData().isFixed()) ? (100) : (1); bonus *= getWeight(e); ForceVectorUtils.fcBiAttractor(nf.getNodeData(), nt.getNodeData(), bonus * getAttractionStrength()); } } } // gravity for (Node n : nodes) { float nx = n.getNodeData().x(); float ny = n.getNodeData().y(); double d = 0.0001 + Math.sqrt(nx * nx + ny * ny); double gf = 0.0001 * getGravity() * d; ForceVectorNodeLayoutData layoutData = n.getNodeData().getLayoutData(); layoutData.dx -= gf * nx / d; layoutData.dy -= gf * ny / d; } // speed if (isFreezeBalance()) { for (Node n : nodes) { ForceVectorNodeLayoutData layoutData = n.getNodeData().getLayoutData(); layoutData.dx *= getSpeed() * 10f; layoutData.dy *= getSpeed() * 10f; } } else { for (Node n : nodes) { ForceVectorNodeLayoutData layoutData = n.getNodeData().getLayoutData(); layoutData.dx *= getSpeed(); layoutData.dy *= getSpeed(); } } // apply forces for (Node n : nodes) { NodeData nData = n.getNodeData(); ForceVectorNodeLayoutData nLayout = nData.getLayoutData(); if (!nData.isFixed()) { double d = 0.0001 + Math.sqrt(nLayout.dx * nLayout.dx + nLayout.dy * nLayout.dy); float ratio; if (isFreezeBalance()) { nLayout.freeze = (float) (getFreezeInertia() * nLayout.freeze + (1 - getFreezeInertia()) * 0.1 * getFreezeStrength() * (Math.sqrt(Math.sqrt((nLayout.old_dx - nLayout.dx) * (nLayout.old_dx - nLayout.dx) + (nLayout.old_dy - nLayout.dy) * (nLayout.old_dy - nLayout.dy))))); ratio = (float) Math.min((d / (d * (1f + nLayout.freeze))), getMaxDisplacement() / d); } else { ratio = (float) Math.min(1, getMaxDisplacement() / d); } nLayout.dx *= ratio / getCooling(); nLayout.dy *= ratio / getCooling(); float x = nData.x() + nLayout.dx; float y = nData.y() + nLayout.dy; nData.setX(x); nData.setY(y); } } graph.readUnlock(); } public void endAlgo() { for (Node n : graph.getNodes()) { n.getNodeData().setLayoutData(null); } } @Override public boolean canAlgo() { return true; } private float getWeight(Edge edge) { if(timeInterval!=null) { return edge.getWeight(timeInterval.getLow(), timeInterval.getHigh()); } else { return edge.getWeight(); } } public LayoutProperty[] getProperties() { List<LayoutProperty> properties = new ArrayList<LayoutProperty>(); final String FORCE_ATLAS = "Force Atlas"; try { properties.add(LayoutProperty.createProperty( this, Double.class, NbBundle.getMessage(ForceAtlasLayout.class, "forceAtlas.inertia.name"), FORCE_ATLAS, NbBundle.getMessage(ForceAtlasLayout.class, "forceAtlas.inertia.desc"), "getInertia", "setInertia")); properties.add(LayoutProperty.createProperty( this, Double.class, NbBundle.getMessage(ForceAtlasLayout.class, "forceAtlas.repulsionStrength.name"), FORCE_ATLAS, NbBundle.getMessage(ForceAtlasLayout.class, "forceAtlas.repulsionStrength.desc"), "getRepulsionStrength", "setRepulsionStrength")); properties.add(LayoutProperty.createProperty( this, Double.class, NbBundle.getMessage(ForceAtlasLayout.class, "forceAtlas.attractionStrength.name"), FORCE_ATLAS, NbBundle.getMessage(ForceAtlasLayout.class, "forceAtlas.attractionStrength.desc"), "getAttractionStrength", "setAttractionStrength")); properties.add(LayoutProperty.createProperty( this, Double.class, NbBundle.getMessage(ForceAtlasLayout.class, "forceAtlas.maxDisplacement.name"), FORCE_ATLAS, NbBundle.getMessage(ForceAtlasLayout.class, "forceAtlas.maxDisplacement.desc"), "getMaxDisplacement", "setMaxDisplacement")); properties.add(LayoutProperty.createProperty( this, Boolean.class, NbBundle.getMessage(ForceAtlasLayout.class, "forceAtlas.freezeBalance.name"), FORCE_ATLAS, NbBundle.getMessage(ForceAtlasLayout.class, "forceAtlas.freezeBalance.desc"), "isFreezeBalance", "setFreezeBalance")); properties.add(LayoutProperty.createProperty( this, Double.class, NbBundle.getMessage(ForceAtlasLayout.class, "forceAtlas.freezeStrength.name"), FORCE_ATLAS, NbBundle.getMessage(ForceAtlasLayout.class, "forceAtlas.freezeStrength.desc"), "getFreezeStrength", "setFreezeStrength")); properties.add(LayoutProperty.createProperty( this, Double.class, NbBundle.getMessage(ForceAtlasLayout.class, "forceAtlas.freezeInertia.name"), FORCE_ATLAS, NbBundle.getMessage(ForceAtlasLayout.class, "forceAtlas.freezeInertia.desc"), "getFreezeInertia", "setFreezeInertia")); properties.add(LayoutProperty.createProperty( this, Double.class, NbBundle.getMessage(ForceAtlasLayout.class, "forceAtlas.gravity.name"), FORCE_ATLAS, NbBundle.getMessage(ForceAtlasLayout.class, "forceAtlas.gravity.desc"), "getGravity", "setGravity")); properties.add(LayoutProperty.createProperty( this, Boolean.class, NbBundle.getMessage(ForceAtlasLayout.class, "forceAtlas.outboundAttractionDistribution.name"), FORCE_ATLAS, NbBundle.getMessage(ForceAtlasLayout.class, "forceAtlas.outboundAttractionDistribution.desc"), "isOutboundAttractionDistribution", "setOutboundAttractionDistribution")); properties.add(LayoutProperty.createProperty( this, Boolean.class, NbBundle.getMessage(ForceAtlasLayout.class, "forceAtlas.adjustSizes.name"), FORCE_ATLAS, NbBundle.getMessage(ForceAtlasLayout.class, "forceAtlas.adjustSizes.desc"), "isAdjustSizes", "setAdjustSizes")); properties.add(LayoutProperty.createProperty( this, Double.class, NbBundle.getMessage(ForceAtlasLayout.class, "forceAtlas.speed.name"), FORCE_ATLAS, NbBundle.getMessage(ForceAtlasLayout.class, "forceAtlas.speed.desc"), "getSpeed", "setSpeed")); } catch (Exception e) { e.printStackTrace(); } return properties.toArray(new LayoutProperty[0]); } public void setInertia(Double inertia) { this.inertia = inertia; } public Double getInertia() { return inertia; } /** * @return the repulsionStrength */ public Double getRepulsionStrength() { return repulsionStrength; } /** * @param repulsionStrength the repulsionStrength to set */ public void setRepulsionStrength(Double repulsionStrength) { this.repulsionStrength = repulsionStrength; } /** * @return the attractionStrength */ public Double getAttractionStrength() { return attractionStrength; } /** * @param attractionStrength the attractionStrength to set */ public void setAttractionStrength(Double attractionStrength) { this.attractionStrength = attractionStrength; } /** * @return the maxDisplacement */ public Double getMaxDisplacement() { return maxDisplacement; } /** * @param maxDisplacement the maxDisplacement to set */ public void setMaxDisplacement(Double maxDisplacement) { this.maxDisplacement = maxDisplacement; } /** * @return the freezeBalance */ public Boolean isFreezeBalance() { return freezeBalance; } /** * @param freezeBalance the freezeBalance to set */ public void setFreezeBalance(Boolean freezeBalance) { this.freezeBalance = freezeBalance; } /** * @return the freezeStrength */ public Double getFreezeStrength() { return freezeStrength; } /** * @param freezeStrength the freezeStrength to set */ public void setFreezeStrength(Double freezeStrength) { this.freezeStrength = freezeStrength; } /** * @return the freezeInertia */ public Double getFreezeInertia() { return freezeInertia; } /** * @param freezeInertia the freezeInertia to set */ public void setFreezeInertia(Double freezeInertia) { this.freezeInertia = freezeInertia; } /** * @return the gravity */ public Double getGravity() { return gravity; } /** * @param gravity the gravity to set */ public void setGravity(Double gravity) { this.gravity = gravity; } /** * @return the speed */ public Double getSpeed() { return speed; } /** * @param speed the speed to set */ public void setSpeed(Double speed) { this.speed = speed; } /** * @return the cooling */ public Double getCooling() { return cooling; } /** * @param cooling the cooling to set */ public void setCooling(Double cooling) { this.cooling = cooling; } /** * @return the outboundAttractionDistribution */ public Boolean isOutboundAttractionDistribution() { return outboundAttractionDistribution; } /** * @param outboundAttractionDistribution the outboundAttractionDistribution to set */ public void setOutboundAttractionDistribution(Boolean outboundAttractionDistribution) { this.outboundAttractionDistribution = outboundAttractionDistribution; } /** * @return the adjustSizes */ public Boolean isAdjustSizes() { return adjustSizes; } /** * @param adjustSizes the adjustSizes to set */ public void setAdjustSizes(Boolean adjustSizes) { this.adjustSizes = adjustSizes; } }