Skip to content

Instantly share code, notes, and snippets.

@MoritzStefaner
Created December 5, 2011 16:11
Show Gist options
  • Save MoritzStefaner/1434108 to your computer and use it in GitHub Desktop.
Save MoritzStefaner/1434108 to your computer and use it in GitHub Desktop.
package eu.stefaner.researchdensity {
import eu.stefaner.flareextensions.Helpers;
import flare.animate.Transitioner;
import flare.util.Property;
import flare.vis.Visualization;
import flare.vis.data.Data;
import flare.vis.data.DataList;
import flare.vis.data.NodeSprite;
import com.gskinner.sprites.ProximityManager;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.Point;
/**
* @author mo
*/
[SWF(backgroundColor="#FFFFFF", frameRate="31", width="900", height="900")]
public class MapOptimizer extends Visualization {
private static const MODE_OPTIMIZE : String = "MODE_OPTIMIZE";
public static const STEP_EVENT : String = "STEP_EVENT";
public var SCALE_CONSTANT : Number = 1.5;
public var MIN_SIZE : Number = .1;
public var optimizationIterations : Number = 200.0;
public var scalePeriod : Number = .66;
public var spacing : Number = 1;
public var mode : String;
public var timerCounter : Number = 0;
private var sizeProp : Property;
private var latProp : Property;
private var longProp : Property;
private var sizeField : String;
private var normalizedWeightProp : Property;
private var proximityManager : ProximityManager;
public var normalize : Boolean = false;
private var defaultGridSize : uint;
public function MapOptimizer(sizeField : String = "props.weight", latField : String = "props.lat", longField : String = "props.long") {
super(new Data());
this.sizeField = sizeField;
sizeProp = new Property(sizeField);
latProp = new Property(latField);
longProp = new Property(longField);
mode = MODE_OPTIMIZE;
}
public function startUp() : void {
if (normalize) {
Helpers.normalizeMax(sizeField, "props.normalizedWeightForMap", data.nodes);
normalizedWeightProp = new Property("props.normalizedWeightForMap");
} else {
normalizedWeightProp = sizeProp;
}
if (mode == MODE_OPTIMIZE) {
doPositioning();
continuousUpdates = true;
// addEventListener(MouseEvent.CLICK, onLogCoords);
} else {
doPositioning(new Transitioner(1));
}
}
private function onLogCoords(event : MouseEvent) : void {
for each (var n:NodeSprite in data.nodes) {
trace([n.data.id, n.x, n.y].join("\t"));
}
}
override public function update(t : * = null, ...operators) : Transitioner {
t = super.update(t, operators);
timerCounter++;
// Logger.info("optimization step " + timerCounter);
var dl : DataList = data.nodes;
var n : NodeSprite;
var scaleFactor : Number = Math.min(1, (timerCounter / (optimizationIterations * scalePeriod)));
var firstPhase : Boolean = timerCounter <= optimizationIterations * scalePeriod;
if (timerCounter == optimizationIterations) {
continuousUpdates = false;
dispatchEvent(new Event(Event.COMPLETE));
return t;
}
dispatchEvent(new Event(STEP_EVENT));
if ((timerCounter % 10) == 1) {
if (firstPhase) {
proximityManager.gridSize = defaultGridSize * scaleFactor + 1;
} else {
proximityManager.gridSize = defaultGridSize * 1.5;
}
// Logger.info("refreshing prox manager", proximityManager.gridSize);
proximityManager.refresh();
}
var diff : Point = new Point();
for each (n in dl) {
n.scaleX = n.scaleY = scaleFactor ;
// move towards original position
var factor : Number;
if (firstPhase) {
factor = .15 * n.props.tension;
} else {
factor = n.props.tension * .10 * Math.max(0, (1.0 - (timerCounter - optimizationIterations * scalePeriod) / (optimizationIterations * (1 - scalePeriod))));
}
n.x += (n.props.screenX - n.x) * factor;
n.y += (n.props.screenY - n.y) * factor;
var r1 : Number = n.size * 6 * n.scaleX ;
for each (var n2:NodeSprite in proximityManager.getNeighbors(n)) {
if (n2 == n) continue;
diff.x = n2.x - n.x;
diff.y = n2.y - n.y;
if (!diff.length) {
diff.x = Math.random() - Math.random();
diff.y = Math.random() - Math.random();
}
// var overlap : Number = (n.width * .5 + n2.width * .5) + spacing - diff.length;
var r2 : Number = n2.size * 6 * n2.scaleX ;
var s : Number = ((n as CitySprite).country == (n2 as CitySprite).country) ? 0.5 : 4;
var overlap : Number = (r1 + r2) + s - diff.length;
if (overlap > 0) {
var sizeProportion : Number = (Math.pow(n.size, 2)) / (Math.pow(n.size, 2) + Math.pow(n2.size, 2));
sizeProportion = Math.max(sizeProportion, 0);
sizeProportion = Math.min(sizeProportion, 1);
diff.normalize(overlap);
n.x -= diff.x * (1 - sizeProportion);
n.y -= diff.y * (1 - sizeProportion);
n2.x += diff.x * sizeProportion;
n2.y += diff.y * sizeProportion;
}
}
}
return t;
}
private function doPositioning(transitioner : Transitioner = null) : void {
proximityManager = new ProximityManager(Math.floor(Math.max(bounds.width, bounds.height) / 30));
var maxSize : Number = 0;
for each (var n:NodeSprite in data.nodes) {
var p : Point = getPointForLoc(latProp.getValue(n), longProp.getValue(n));
n.props.screenX = p.x;
n.props.screenY = p.y;
n.x = p.x;
n.y = p.y;
n.size = getSizeForWeight(normalizedWeightProp.getValue(n));
n.props.tension = Math.pow(.25 + .75 * normalizedWeightProp.getValue(n), 2);
initNodeSprite(n);
if (n.props.label) n.props.label.visible = false;
maxSize = Math.max(maxSize, n.size);
n.scaleX = n.scaleY = 0;
proximityManager.addItem(n);
}
defaultGridSize = proximityManager.gridSize = Math.ceil(6 * maxSize);
// simpler
// defaultGridSize = bounds.width / 20;
proximityManager.refresh();
}
private function initNodeSprite(n : NodeSprite) : void {
// n.fillColor = Colors.gray(Math.floor(Math.max(0, 0x66 - 0x99 * normalizedWeightProp.getValue(n))));
// n.fillColor = Colors.gray(0);
// n.lineAlpha = 0;
}
private function getSizeForWeight(weight : Number) : Number {
return MIN_SIZE + Math.sqrt(weight) * SCALE_CONSTANT;
}
private function getPointForLoc(lat : Number, long : Number) : Point {
var x : Number = bounds.width * .5 + (long / 160.0) * bounds.width * .5;
var y : Number = bounds.height * .5 - (lat / 160.0) * bounds.width * .5;
return new Point(x, y);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment