/*
Copyright 2008-2010 Gephi
Authors : Mathieu Bastian <mathieu.bastian@gephi.org>
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;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.gephi.graph.api.GraphModel;
import org.gephi.layout.spi.Layout;
import org.gephi.layout.spi.LayoutProperty;
import org.openide.nodes.Node.Property;
/**
* Class to build layout scenario that runs for a certain duration. Multiple
* layout can be chained and their duration ratio set. Moreover layout
* property can be mananaged automatically and set in advance.
* <p>
* <b>Example:</b>
* <p>
* This will execute ForceAtlas for the first 80%, and LabelAdjust for remaining 20%
* <pre>
* AutoLayout autoLayout = new AutoLayout(10, TimeUnit.SECONDS);
* ForceAtlasLayout forceAtlasLayout = new ForceAtlasLayout(null);
* AutoLayout.DynamicProperty gravity = AutoLayout.createDynamicProperty("Gravity", new Double[]{80., 400.0}, new float[]{0f, 1f}, AutoLayout.Interpolation.LINEAR);
* AutoLayout.DynamicProperty speed = AutoLayout.createDynamicProperty("Speed", new Double[]{1.2, 0.3}, new float[]{0f, 1f}, AutoLayout.Interpolation.LINEAR);
* AutoLayout.DynamicProperty repulsion = AutoLayout.createDynamicProperty("Repulsion strength", new Double[]{3000.0, 6000.}, new float[]{0f, 1f}, AutoLayout.Interpolation.LINEAR);
* AutoLayout.DynamicProperty freeze = AutoLayout.createDynamicProperty("Autostab Strength", new Double(100.0), 0f);
* autoLayout.addLayout(forceAtlasLayout, 0.8f, new AutoLayout.DynamicProperty[]{gravity, speed, repulsion, freeze});
*
* //LabelAdjust
* LabelAdjust labelAdjust = new LabelAdjust(null);
* AutoLayout.DynamicProperty speed2 = AutoLayout.createDynamicProperty("Speed", new Double[]{0.5, 0.2}, new float[]{0f, 1f}, AutoLayout.Interpolation.LINEAR);
* autoLayout.addLayout(labelAdjust, 0.2f, new AutoLayout.DynamicProperty[]{speed2});
* </pre>
* Work in Progress
*
* @author Mathieu Bastian
*/
public class AutoLayout {
private final float duration;
private final List<LayoutScenario> layouts;
private GraphModel graphModel;
//Flags
private long startTime = 0;
private long lastExecutionTime;
private float currentRatio;
private int innerIteration;
private float innerStart;
private float innerRatio;
private LayoutScenario currentLayout;
private boolean cancel;
public AutoLayout(long duration, TimeUnit timeUnit) {
this.duration = TimeUnit.MILLISECONDS.convert(duration, timeUnit);
this.layouts = new ArrayList<LayoutScenario>();
}
public void addLayout(Layout layout, float ratio) {
layouts.add(new LayoutScenario(layout, ratio));
}
public void addLayout(Layout layout, float ratio, DynamicProperty[] properties) {
for (int i = 0; i < properties.length; i++) {
AbstractDynamicProperty property = (AbstractDynamicProperty) properties[i];
for (LayoutProperty lp : layout.getProperties()) {
if (lp.getProperty().getName().equalsIgnoreCase(property.getName())) {
property.setProperty(lp.getProperty());
break;
}
}
if (property.getProperty() == null) {
throw new IllegalArgumentException(property.getName() + " property cannot be found in layout");
}
}
layouts.add(new LayoutScenario(layout, ratio, properties));
}
public void execute() {
//System.out.println("execute");
cancel = false;
verifiy();
LayoutScenario layout;
while (!cancel && (layout = setLayout()) != null) {
setProperties();
layout.layout.goAlgo();
}
//System.out.println("finished");
}
public void cancel() {
cancel = true;
}
private void setProperties() {
//String log = currentLayout.layout.toString() + ": ";
for (int i = 0; i < currentLayout.properties.length; i++) {
DynamicProperty d = currentLayout.properties[i];
Object val = d.getValue(innerRatio);
if (val != null) {
try {
if (val != d.getProperty().getValue()) {
//log += d.getProperty().getDisplayName() + "=" + val+" ";
d.getProperty().setValue(val);
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
//System.out.println(log);
}
private LayoutScenario setLayout() {
long elapsedTime = getElapsedTime();
long diff = elapsedTime - lastExecutionTime;
//System.out.println(diff);
lastExecutionTime = elapsedTime;
currentRatio = elapsedTime / duration;
float ratio = currentRatio + (diff / duration); //Don't start a layout that will overcome ratio
if (ratio >= 1f) {
currentLayout = null;
return currentLayout;
}
LayoutScenario layout = null;
float sumRatio = 0;
float sum = 0;
for (int i = 0; i < layouts.size(); i++) {
LayoutScenario l = layouts.get(i);
if (sum <= ratio) {
layout = l;
sumRatio = sum;
}
sum += l.ratio;
}
if (currentLayout != layout) {
innerStart = currentRatio;
innerIteration = 0;
layout.layout.setGraphModel(graphModel);
layout.layout.resetPropertiesValues();
layout.layout.initAlgo();
} else {
innerIteration++;
}
currentLayout = layout;
float start = innerStart;
float end = sumRatio + layout.ratio;
float averageIteration = innerIteration == 0 ? 0 : (currentRatio - start) / innerIteration;
int totalIteration = averageIteration == 0 ? 0 : (int) ((end - start) / averageIteration) - 1;
innerRatio = totalIteration == 0 ? 0 : innerIteration / (float) totalIteration;
return currentLayout;
}
private long getElapsedTime() {
if (startTime == 0) {
startTime = System.currentTimeMillis();
return 0;
}
return System.currentTimeMillis() - startTime;
}
public void setGraphModel(GraphModel graphModel) {
this.graphModel = graphModel;
}
private void verifiy() {
float sum = 0;
for (LayoutScenario l : layouts) {
sum += l.ratio;
}
if (sum != 1) {
throw new RuntimeException("Ratio sum is not 1");
}
}
public static DynamicProperty createDynamicProperty(String propertyName, Object value, float ratio) {
return new SingleDynamicProperty(propertyName, value, ratio);
}
public static DynamicProperty createDynamicProperty(String propertyName, Object[] value, float[] ratio) {
return new MultiDynamicProperty(propertyName, value, ratio);
}
public static DynamicProperty createDynamicProperty(String propertyName, Number[] value, float[] ratio, Interpolation interpolation) {
return new InterpolateDynamicProperty(propertyName, value, ratio, interpolation);
}
public static interface DynamicProperty {
public Object getValue(float ratio);
public Property getProperty();
public String getName();
}
public enum Interpolation {
LINEAR, LOG
};
private static abstract class AbstractDynamicProperty implements DynamicProperty {
private final String propertyName;
protected Property property;
public AbstractDynamicProperty(String propertyName) {
this.propertyName = propertyName;
}
@Override
public Property getProperty() {
return property;
}
void setProperty(Property property) {
this.property = property;
}
@Override
public String getName() {
return propertyName;
}
}
private static class SingleDynamicProperty extends AbstractDynamicProperty {
private final Object value;
private final float threshold;
SingleDynamicProperty(String propertyName, Object value, float ratio) {
super(propertyName);
this.value = value;
this.threshold = ratio;
}
@Override
public Object getValue(float ratio) {
try {
if (ratio >= threshold) {
return value;
}
return property.getValue();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
private static class MultiDynamicProperty extends AbstractDynamicProperty {
private final Object[] value;
private final float[] thresholds;
private int currentIndex = 0;
MultiDynamicProperty(String propertyName, Object[] value, float[] ratio) {
super(propertyName);
this.value = value;
this.thresholds = ratio;
if (value.length != ratio.length) {
throw new IllegalArgumentException("Value and ratio arrays must have same length");
}
}
@Override
public Object getValue(float ratio) {
while (thresholds[currentIndex] < ratio && currentIndex < thresholds.length) {
currentIndex++;
}
return value[currentIndex];
}
}
private static class InterpolateDynamicProperty extends AbstractDynamicProperty {
private final Number[] value;
private final float[] thresholds;
private final Interpolation interpolation;
private int currentIndex = 0;
InterpolateDynamicProperty(String propertyName, Number[] value, float[] ratio, Interpolation interpolation) {
super(propertyName);
this.value = value;
this.thresholds = ratio;
this.interpolation = interpolation;
if (value.length != ratio.length) {
throw new IllegalArgumentException("Value and ratio arrays must have same length");
}
}
@Override
public Object getValue(float ratio) {
while (thresholds[currentIndex] < ratio && currentIndex < thresholds.length) {
currentIndex++;
}
if (currentIndex > 0) {
float r = 1 / (thresholds[currentIndex] - thresholds[currentIndex - 1]);
ratio = ((ratio - thresholds[currentIndex - 1]) * r);
return new Double(value[currentIndex - 1].doubleValue() + (value[currentIndex].doubleValue() - value[currentIndex - 1].doubleValue()) * ratio);
}
return value[currentIndex];
}
}
private static class LayoutScenario {
private final Layout layout;
private final float ratio;
private final DynamicProperty[] properties;
public LayoutScenario(Layout layout, float ratio, DynamicProperty[] properties) {
this.layout = layout;
this.ratio = ratio;
this.properties = properties;
}
public LayoutScenario(Layout layout, float ratio) {
this(layout, ratio, new DynamicProperty[0]);
}
}
}