azkaban-aplcache

Preliminary project page.

7/16/2012 9:38:59 PM

Details

diff --git a/src/java/azkaban/flow/Edge.java b/src/java/azkaban/flow/Edge.java
index 3ac653b..06d8820 100644
--- a/src/java/azkaban/flow/Edge.java
+++ b/src/java/azkaban/flow/Edge.java
@@ -1,6 +1,10 @@
 package azkaban.flow;
 
+import java.awt.geom.Point2D;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 public class Edge {
 	private final String sourceId;
@@ -9,6 +13,9 @@ public class Edge {
 	private Node target;
 	private String error;
 	
+	// Useful in rendering.
+	private String guideType;
+	private List<Point2D> guideValues;
 
 	public Edge(String fromId, String toId) {
 		this.sourceId = fromId;
@@ -60,6 +67,19 @@ public class Edge {
 	public void setTarget(Node target) {
 		this.target = target;
 	}
+
+	public String getGuideType() {
+		return guideType;
+	}
+	
+	public List<Point2D> getGuideValues() {
+		return guideValues;
+	}
+
+	public void setGuides(String type, List<Point2D> values) {
+		this.guideType = type;
+		this.guideValues = values;
+	}
 	
 	public Object toObject() {
 		HashMap<String, Object> obj = new HashMap<String, Object>();
@@ -68,12 +88,27 @@ public class Edge {
 		if (error != null) {
 			obj.put("error", error);
 		}
+		if (guideValues != null) {
+			HashMap<String, Object> lineGuidesObj = new HashMap<String, Object>();
+			lineGuidesObj.put("type", guideType);
+			
+			ArrayList<Object> guides = new ArrayList<Object>();
+			for (Point2D point: this.guideValues) {
+				HashMap<String, Double> pointObj = new HashMap<String, Double>();
+				pointObj.put("x", point.getX());
+				pointObj.put("y", point.getY());
+				guides.add(pointObj);
+			}
+			lineGuidesObj.put("values", guides);
+			
+			obj.put("guides",lineGuidesObj);
+		}
 		
 		return obj;
 	}
 	
+	@SuppressWarnings("unchecked")
 	public static Edge fromObject(Object obj) {
-		@SuppressWarnings("unchecked")
 		HashMap<String, Object> edgeObj = (HashMap<String,Object>)obj;
 		
 		String source = (String)edgeObj.get("source");
@@ -84,6 +119,24 @@ public class Edge {
 		Edge edge = new Edge(source, target);
 		edge.setError(error);
 		
+		if (edgeObj.containsKey("guides")) {
+			Map<String, Object> guideMap = (Map<String,Object>)edgeObj.get("guides");
+			List<Object> values = (List<Object>)guideMap.get("values");
+			String type = (String)guideMap.get("type");
+			
+			ArrayList<Point2D> valuePoints = new ArrayList<Point2D>();
+			for (Object pointObj: values) {
+				Map<String, Double> point = (Map<String,Double>)pointObj;
+				
+				Double x = point.get("x");
+				Double y = point.get("y");
+				
+				valuePoints.add(new Point2D.Double(x, y));
+			}
+			
+			edge.setGuides(type, valuePoints);
+		}
+		
 		return edge;
 	}
 
diff --git a/src/java/azkaban/flow/Flow.java b/src/java/azkaban/flow/Flow.java
index 4b657d9..a4ce3ba 100644
--- a/src/java/azkaban/flow/Flow.java
+++ b/src/java/azkaban/flow/Flow.java
@@ -285,15 +285,15 @@ public class Flow {
 		this.isLayedOut = layedOut;
 	}
 
-	/*package*/ Map<String, Node> getNodeMap() {
+	public Map<String, Node> getNodeMap() {
 		return nodes;
 	}
 	
-	/*package*/ Map<String, Set<Edge>> getOutEdgeMap() {
+	public Map<String, Set<Edge>> getOutEdgeMap() {
 		return outEdges;
 	}
 	
-	/*package*/ Map<String, Set<Edge>> getInEdgeMap() {
+	public Map<String, Set<Edge>> getInEdgeMap() {
 		return inEdges;
 	}
 }
\ No newline at end of file
diff --git a/src/java/azkaban/flow/layout/BlockFlowLayout.java b/src/java/azkaban/flow/layout/BlockFlowLayout.java
new file mode 100644
index 0000000..3cd4cd4
--- /dev/null
+++ b/src/java/azkaban/flow/layout/BlockFlowLayout.java
@@ -0,0 +1,78 @@
+package azkaban.flow.layout;
+
+import java.util.HashMap;
+import java.util.Set;
+
+import azkaban.flow.Edge;
+import azkaban.flow.Flow;
+import azkaban.flow.Node;
+
+public class BlockFlowLayout implements FlowLayout {
+	
+	@Override
+	public void layoutFlow(Flow flow) {
+//		createLayeredGraph(flow);
+	}
+
+//	private void createLayeredGraph(Flow flow) {
+//		HashMap<String, LayeredNode> layeredNode = new HashMap<String, LayeredNode>();
+//		
+//		for(Node node: flow.getStartNodes()) {
+//			WrappedNode wnode = new WrappedNode(node);
+//			layeredNode.put(wnode.getId(), wnode);
+//			wnode.setLevel(0);
+//			traverseNodes(layeredNode, flow, wnode);
+//		}
+//	}
+//	
+//	private void traverseNodes(HashMap<String, LayeredNode> layeredNode, Flow flow, LayeredNode wnode) {
+//		Set<Edge> outEdges = flow.getOutEdges(wnode.getId());
+//		Set<Edge> inEdges = flow.getInEdges(wnode.getId());
+//		if (outEdges == null) {
+//			return;
+//		}
+//		
+//		if (outEdges.size() == 1 && inEdges != null && inEdges.size() == 1) {
+//			System.out.println("Starting chain for " + wnode.getId());
+//			ChainedNode cnode = new ChainedNode();
+//			wnode.addOutNode(cnode);
+//			cnode.addInNode(wnode);
+//			
+//			while(outEdges.size() == 1 && inEdges.size() == 1) {
+//				Edge edge = outEdges.iterator().next();
+//				if (layeredNode.containsKey(edge.getTargetId())) {
+//					return;
+//				}
+//				
+//				Node target = edge.getTarget();
+//				cnode.addNode(new WrappedNode(target));
+//				outEdges = flow.getOutEdges(target.getId());
+//				layeredNode.put(target.getId(), cnode);
+//				
+//				System.out.println("Chain " + target.getId());
+//
+//				if (outEdges == null) {
+//					return;
+//				}
+//				inEdges = flow.getInEdges(target.getId());
+//			}
+//			
+//			traverseNodes(layeredNode, flow, cnode);
+//		}
+//		else {
+//			for (Edge edge: outEdges) {
+//				if (layeredNode.containsKey(edge.getTargetId())) {
+//					continue;
+//				}
+//				
+//				WrappedNode child = new WrappedNode(edge.getTarget());
+//				layeredNode.put(child.getId(), child);
+//				wnode.addOutNode(child);
+//				child.addInNode(wnode);
+//				
+//				traverseNodes(layeredNode, flow, child);
+//			}
+//		}
+//		
+//	}
+}
diff --git a/src/java/azkaban/flow/layout/ChainedEdge.java b/src/java/azkaban/flow/layout/ChainedEdge.java
new file mode 100644
index 0000000..5e41b5f
--- /dev/null
+++ b/src/java/azkaban/flow/layout/ChainedEdge.java
@@ -0,0 +1,5 @@
+package azkaban.flow.layout;
+
+public class ChainedEdge {
+
+}
diff --git a/src/java/azkaban/flow/layout/DummyNode.java b/src/java/azkaban/flow/layout/DummyNode.java
new file mode 100644
index 0000000..b97ea6f
--- /dev/null
+++ b/src/java/azkaban/flow/layout/DummyNode.java
@@ -0,0 +1,17 @@
+package azkaban.flow.layout;
+
+import azkaban.flow.Edge;
+
+public class DummyNode extends LayeredNode {
+	private Edge edge;
+	public DummyNode(Edge edge) {
+		super();
+		this.setEdge(edge);
+	}
+	public Edge getEdge() {
+		return edge;
+	}
+	public void setEdge(Edge edge) {
+		this.edge = edge;
+	}
+}
\ No newline at end of file
diff --git a/src/java/azkaban/flow/layout/LayeredNode.java b/src/java/azkaban/flow/layout/LayeredNode.java
new file mode 100644
index 0000000..e75aeb1
--- /dev/null
+++ b/src/java/azkaban/flow/layout/LayeredNode.java
@@ -0,0 +1,70 @@
+package azkaban.flow.layout;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public abstract class LayeredNode {
+	private int level;
+	private ArrayList<LayeredNode> inNodes;
+	private ArrayList<LayeredNode> outNodes;
+	private double minX;
+	private double maxX;
+	private double x;
+	private double y;
+	
+	public LayeredNode() {
+		inNodes = new ArrayList<LayeredNode>();
+		outNodes = new ArrayList<LayeredNode>();
+	}
+	
+	public String getId() {
+		return "dummy";
+	}
+	
+	public int getLevel() {
+		return level;
+	}
+	public void setLevel(int level) {
+		this.level = level;
+	}
+	public double getX() {
+		return x;
+	}
+	public void setX(double x) {
+		this.x = x;
+	}
+	public double getMinX() {
+		return minX;
+	}
+	public void setMinX(double min) {
+		minX = min;
+	}
+	public double getMaxX() {
+		return maxX;
+	}
+	public void setMaxX(double max) {
+		this.maxX = max;
+	}
+	public void setY(double y) {
+		this.y = y;
+	}
+	public double getY() {
+		return y;
+	}
+	
+	public void addInNode(LayeredNode node) {
+		inNodes.add(node);
+	}
+	
+	public void addOutNode(LayeredNode node) {
+		outNodes.add(node);
+	}
+	
+	public List<LayeredNode> getInNode() {
+		return inNodes;
+	}
+	
+	public List<LayeredNode> getOutNode() {
+		return outNodes;
+	}
+}
\ No newline at end of file
diff --git a/src/java/azkaban/flow/layout/WrappedNode.java b/src/java/azkaban/flow/layout/WrappedNode.java
new file mode 100644
index 0000000..1dd6d67
--- /dev/null
+++ b/src/java/azkaban/flow/layout/WrappedNode.java
@@ -0,0 +1,18 @@
+package azkaban.flow.layout;
+
+import azkaban.flow.Node;
+
+public class WrappedNode extends LayeredNode {
+	private Node node;
+	public WrappedNode(Node node) {
+		this.node = node;
+		setLevel(node.getLevel());
+	}
+	public Node getNode() {
+		return node;
+	}
+	@Override
+	public String getId() {
+		return node.getId();
+	}
+}
diff --git a/src/java/azkaban/flow/layout2/BlockFlowLayout.java b/src/java/azkaban/flow/layout2/BlockFlowLayout.java
new file mode 100644
index 0000000..8447d66
--- /dev/null
+++ b/src/java/azkaban/flow/layout2/BlockFlowLayout.java
@@ -0,0 +1,54 @@
+package azkaban.flow.layout2;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Set;
+
+import azkaban.flow.Edge;
+import azkaban.flow.Flow;
+import azkaban.flow.Node;
+import azkaban.flow.layout.FlowLayout;
+
+public class BlockFlowLayout implements FlowLayout {
+	
+	@Override
+	public void layoutFlow(Flow flow) {
+		HashMap<String, LayeredNode> layoutNodes = new HashMap<String, LayeredNode>();
+		
+		for (Node node: flow.getNodes()) {
+			layoutNodes.put(node.getId(), new WrappedNode(node));
+		}
+		
+		for(Node node: flow.getStartNodes()) {
+			WrappedNode wnode = new WrappedNode(node);
+			traverseAndChain(layoutNodes, flow, wnode);
+		}
+	}
+	
+	public void traverseAndChain(HashMap<String, LayeredNode> layoutNodes, Flow flow, LayeredNode node) {
+		if (layoutNodes.containsKey(node.getId())) {
+			return;
+		}
+		
+		layoutNodes.put(node.getId(), node);
+		
+		HashMap<String, GroupedEdge> groupEdge = new HashMap<String, GroupedEdge>();
+		for(Edge edge: flow.getOutEdges(node.getId())) {
+			Node dest = edge.getTarget();
+			Set<Edge> destOutEdge = flow.getOutEdges(dest.getId());
+			Set<Edge> destInEdge = flow.getInEdges(dest.getId());
+
+			ArrayList<Node> chainDest = new ArrayList<Node>();
+			while(destOutEdge != null && destOutEdge.size()==1 && destInEdge.size()==1) {
+				chainDest.add(dest);
+				dest = edge.getTarget();
+				destOutEdge = flow.getOutEdges(dest.getId());
+				destInEdge = flow.getInEdges(dest.getId());
+			}
+
+			
+		}
+		
+	}
+
+}
diff --git a/src/java/azkaban/flow/layout2/ChainedEdge.java b/src/java/azkaban/flow/layout2/ChainedEdge.java
new file mode 100644
index 0000000..bca120f
--- /dev/null
+++ b/src/java/azkaban/flow/layout2/ChainedEdge.java
@@ -0,0 +1,37 @@
+package azkaban.flow.layout2;
+
+import java.util.ArrayList;
+
+public class ChainedEdge {
+	private LayeredNode start;
+	private LayeredNode end;
+	private ArrayList<LayeredNode> chain;
+	private String chainString = null;
+	
+	public ChainedEdge(LayeredNode start, LayeredNode end) {
+		this.start = start;
+		this.end = end;
+		chain = new ArrayList<LayeredNode>();
+	}
+	
+	public LayeredNode getStart() {
+		return start;
+	}
+	
+	public LayeredNode getEnd() {
+		return end;
+	}
+	
+	public void addChain(ArrayList<LayeredNode> chain) {
+		this.chain = chain;
+		StringBuffer chainString = new StringBuffer();
+		chainString.append(start.getId());
+		
+		
+		chainString.append(end.getId());
+	}
+	
+	public String chainString() {
+		return chainString;
+	}
+}
diff --git a/src/java/azkaban/flow/layout2/GroupedEdge.java b/src/java/azkaban/flow/layout2/GroupedEdge.java
new file mode 100644
index 0000000..20d8222
--- /dev/null
+++ b/src/java/azkaban/flow/layout2/GroupedEdge.java
@@ -0,0 +1,35 @@
+package azkaban.flow.layout2;
+
+import java.util.ArrayList;
+
+public class GroupedEdge {
+	private WrappedNode source;
+	private WrappedNode dest;
+	private ArrayList<ChainedEdge> edges;
+	
+	public GroupedEdge(WrappedNode source, WrappedNode dest) {
+		this.setSource(source);
+		this.setDest(dest);
+		this.edges = new ArrayList<ChainedEdge>();
+	}
+	
+	public WrappedNode getSource() {
+		return source;
+	}
+	
+	public void setSource(WrappedNode source) {
+		this.source = source;
+	}
+	
+	public WrappedNode getDest() {
+		return dest;
+	}
+	
+	public void setDest(WrappedNode dest) {
+		this.dest = dest;
+	}
+	
+	public void addNestedEdge(ChainedEdge edge) {
+		edges.add(edge);
+	}
+}
diff --git a/src/java/azkaban/flow/layout2/LayeredEdge.java b/src/java/azkaban/flow/layout2/LayeredEdge.java
new file mode 100644
index 0000000..c1ee127
--- /dev/null
+++ b/src/java/azkaban/flow/layout2/LayeredEdge.java
@@ -0,0 +1,11 @@
+package azkaban.flow.layout2;
+
+public class LayeredEdge {
+	private LayeredNode source;
+	private LayeredNode dest;
+	
+	public LayeredEdge(LayeredNode source, LayeredNode dest) {
+		this.source = source;
+		this.dest = dest;
+	}
+}
diff --git a/src/java/azkaban/flow/layout2/LayeredNode.java b/src/java/azkaban/flow/layout2/LayeredNode.java
new file mode 100644
index 0000000..732cf77
--- /dev/null
+++ b/src/java/azkaban/flow/layout2/LayeredNode.java
@@ -0,0 +1,72 @@
+package azkaban.flow.layout2;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import azkaban.flow.Edge;
+
+public abstract class LayeredNode {
+	private int level = -1;
+	private ArrayList<ChainedEdge> inEdges;
+	private ArrayList<ChainedEdge> outEdges;
+	private double minX;
+	private double maxX;
+	private double x;
+	private double y;
+	
+	public LayeredNode() {
+		inEdges = new ArrayList<ChainedEdge>();
+		outEdges = new ArrayList<ChainedEdge>();
+	}
+	
+	public String getId() {
+		return "dummy";
+	}
+	
+	public int getLevel() {
+		return level;
+	}
+	public void setLevel(int level) {
+		this.level = level;
+	}
+	public double getX() {
+		return x;
+	}
+	public void setX(double x) {
+		this.x = x;
+	}
+	public double getMinX() {
+		return minX;
+	}
+	public void setMinX(double min) {
+		minX = min;
+	}
+	public double getMaxX() {
+		return maxX;
+	}
+	public void setMaxX(double max) {
+		this.maxX = max;
+	}
+	public void setY(double y) {
+		this.y = y;
+	}
+	public double getY() {
+		return y;
+	}
+	
+	public void addInEdge(ChainedEdge edge) {
+		inEdges.add(edge);
+	}
+	
+	public void addOutNode(ChainedEdge edge) {
+		outEdges.add(edge);
+	}
+	
+	public List<ChainedEdge> getInEdges() {
+		return inEdges;
+	}
+	
+	public List<ChainedEdge> getOutNode() {
+		return outEdges;
+	}
+}
\ No newline at end of file
diff --git a/src/java/azkaban/flow/layout2/NestedEdge.java b/src/java/azkaban/flow/layout2/NestedEdge.java
new file mode 100644
index 0000000..7a96b32
--- /dev/null
+++ b/src/java/azkaban/flow/layout2/NestedEdge.java
@@ -0,0 +1,5 @@
+package azkaban.flow.layout2;
+
+public class NestedEdge {
+
+}
diff --git a/src/java/azkaban/flow/layout2/NestedLayoutGraph.java b/src/java/azkaban/flow/layout2/NestedLayoutGraph.java
new file mode 100644
index 0000000..628915b
--- /dev/null
+++ b/src/java/azkaban/flow/layout2/NestedLayoutGraph.java
@@ -0,0 +1,34 @@
+package azkaban.flow.layout2;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import azkaban.flow.Edge;
+import azkaban.flow.Flow;
+import azkaban.flow.Node;
+
+public class NestedLayoutGraph {
+	private Flow flow;
+	private HashMap<String, LayeredNode> nodes = new HashMap<String, LayeredNode>();
+	private HashMap<String, LayeredEdge> edges = new HashMap<String, LayeredEdge>();
+	private HashMap<String, ArrayList<LayeredEdge>> inEdges;
+	private HashMap<String, ArrayList<LayeredEdge>> outEdges;
+	
+	public NestedLayoutGraph(Flow flow) {
+		this.flow = flow;
+	}
+	
+	private void setupGraph() {
+		for (Node node: flow.getNodes()) {
+			nodes.put(node.getId(), new WrappedNode(node));
+		}
+		
+		for (Edge edge: flow.getEdges()) {
+			LayeredEdge ledge = new LayeredEdge(nodes.get(edge.getSourceId()), nodes.get(edge.getTargetId()));
+			edges.put(edge.getId(), ledge);
+			
+			//inEdges.put(edge.getSourceId(), ledge);
+			//outEdges.put(edge.getTargetId(), ledge);
+		}
+	}
+}
diff --git a/src/java/azkaban/flow/layout2/NestedNode.java b/src/java/azkaban/flow/layout2/NestedNode.java
new file mode 100644
index 0000000..25c9600
--- /dev/null
+++ b/src/java/azkaban/flow/layout2/NestedNode.java
@@ -0,0 +1,5 @@
+package azkaban.flow.layout2;
+
+public class NestedNode extends LayeredNode {
+	
+}
diff --git a/src/java/azkaban/flow/layout2/WrappedNode.java b/src/java/azkaban/flow/layout2/WrappedNode.java
new file mode 100644
index 0000000..afc3cc9
--- /dev/null
+++ b/src/java/azkaban/flow/layout2/WrappedNode.java
@@ -0,0 +1,25 @@
+package azkaban.flow.layout2;
+
+import azkaban.flow.Node;
+
+public class WrappedNode extends LayeredNode {
+	private Node node;
+	private boolean visited=false;
+	public WrappedNode(Node node) {
+		this.node = node;
+	}
+	public Node getNode() {
+		return node;
+	}
+	@Override
+	public String getId() {
+		return node.getId();
+	}
+	public boolean isVisited() {
+		return visited;
+	}
+	public void setVisited(boolean visited) {
+		this.visited = visited;
+	}
+
+}
diff --git a/src/java/azkaban/project/FileProjectManager.java b/src/java/azkaban/project/FileProjectManager.java
index 22b7efc..bbbbc38 100644
--- a/src/java/azkaban/project/FileProjectManager.java
+++ b/src/java/azkaban/project/FileProjectManager.java
@@ -16,7 +16,8 @@ import org.joda.time.format.DateTimeFormat;
 import org.joda.time.format.DateTimeFormatter;
 
 import azkaban.flow.Flow;
-import azkaban.flow.LayeredFlowLayout;
+import azkaban.flow.layout.BlockFlowLayout;
+import azkaban.flow.layout.LayeredFlowLayout;
 import azkaban.user.Permission;
 import azkaban.user.Permission.Type;
 import azkaban.user.User;
@@ -129,6 +130,9 @@ public class FileProjectManager implements ProjectManager {
 								LayeredFlowLayout layout = new LayeredFlowLayout();
 								layout.layoutFlow(flow);
 								
+								BlockFlowLayout bfl = new BlockFlowLayout();
+								bfl.layoutFlow(flow);
+								
 								try {
 									writeFlowFile(flowFile.getParentFile(), flow);
 								} catch (IOException e) {
diff --git a/src/java/azkaban/webapp/servlet/IndexServlet.java b/src/java/azkaban/webapp/servlet/IndexServlet.java
index 0e3a676..0af2f2f 100644
--- a/src/java/azkaban/webapp/servlet/IndexServlet.java
+++ b/src/java/azkaban/webapp/servlet/IndexServlet.java
@@ -56,7 +56,7 @@ public class IndexServlet extends LoginAbstractAzkabanServlet {
         if(hasParam(req, "action")) {
         	String action = getParam(req, "action");
         	if (action.equals("create")) {
-
+        		
         	}
         }
         else {
diff --git a/src/java/azkaban/webapp/servlet/ProjectManagerServlet.java b/src/java/azkaban/webapp/servlet/ProjectManagerServlet.java
index 986a8e7..b389b08 100644
--- a/src/java/azkaban/webapp/servlet/ProjectManagerServlet.java
+++ b/src/java/azkaban/webapp/servlet/ProjectManagerServlet.java
@@ -1,5 +1,6 @@
 package azkaban.webapp.servlet;
 
+import java.awt.geom.Point2D;
 import java.io.BufferedOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
@@ -157,6 +158,20 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
 			if (edge.hasError()) {
 				edgeObj.put("error", edge.getError());
 			}
+			if (edge.getGuideValues() != null) {
+				List<Point2D> guides = edge.getGuideValues();
+				ArrayList<Object> guideOutput = new ArrayList<Object>();
+				for (Point2D guide: guides) {
+					double x = guide.getX();
+					double y = guide.getY();
+					HashMap<String, Double> point = new HashMap<String, Double>();
+					point.put("x", x);
+					point.put("y", y);
+					guideOutput.add(point);
+				}
+				
+				edgeObj.put("guides", guideOutput);
+			}
 			
 			edgeList.add(edgeObj);
 		}
diff --git a/src/java/azkaban/webapp/servlet/velocity/flowpage.vm b/src/java/azkaban/webapp/servlet/velocity/flowpage.vm
index 6d3f7bd..853549f 100644
--- a/src/java/azkaban/webapp/servlet/velocity/flowpage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/flowpage.vm
@@ -53,10 +53,14 @@
 					<div id="graphView">
 						<div class="relative">
 							<div id="jobList">
-								Loading Flow ${flowid} 
+								<div id="filterList">
+									<input id="filter" placeholder="  Job Filter" />
+								</div>
+								<div id="list">
+								</div>
 							</div>
 							<div id="svgDiv" >
-								<svg id="svgGraph" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 100% 100%" > 
+								<svg id="svgGraph" xmlns="http://www.w3.org/2000/svg" version="1.1" shape-rendering="optimize-speed" text-rendering="optimize-speed" >
 								</svg>
 							</div>
 						</div>
diff --git a/src/web/css/azkaban.css b/src/web/css/azkaban.css
index 0b640e8..2959b80 100644
--- a/src/web/css/azkaban.css
+++ b/src/web/css/azkaban.css
@@ -989,7 +989,47 @@ tr:hover td {
 	top: 0px;
 	left: 0px;
 	height: 100%;
-	width: 260px;
+	width: 250px;
+}
+
+#list {
+	position: absolute;
+	background-color: #fff;
+	margin-right: 10px;
+	border: solid;
+	border-color: #CCC;
+	border-width: 1px;
+	overflow: auto;
+	top: 26px;
+	width: 100%;
+	bottom: 120px;
+}
+
+#filter {
+	width: 100%;
+}
+
+#list ul {
+	white-space: nowrap
+}
+
+#list ul li {
+	margin: 4px 5px;
+	border-bottom: 1px solid #EEE;
+}
+
+#list ul li a {
+	font-size: 10pt;
+	margin-left: 5px;
+	cursor: pointer;
+}
+
+#list ul li a span {
+	background-color: #FF0;
+}
+
+#list ul li a:hover {
+	color: #009FC9;
 }
 
 /* old styles */
diff --git a/src/web/js/azkaban.flow.view.js b/src/web/js/azkaban.flow.view.js
index 5129e40..c2e4d2f 100644
--- a/src/web/js/azkaban.flow.view.js
+++ b/src/web/js/azkaban.flow.view.js
@@ -35,6 +35,104 @@ azkaban.FlowTabView= Backbone.View.extend({
   }
 });
 
+var jobListView;
+azkaban.JobListView = Backbone.View.extend({
+	events: {
+		"keyup input": "filterJobs"
+	},
+	initialize: function(settings) {
+		this.model.bind('change:selected', this.changeSelected, this);
+		this.model.bind('change:graph', this.render, this);
+	},
+	filterJobs: function(self) {
+		var filter = $("#filter").val();
+		
+		if (filter && filter.trim() != "") {
+			filter = filter.trim();
+			
+			if (filter == "") {
+				if (this.filter) {
+					$("#jobs").children().each(
+						function(){
+							var a = $(this).find("a");
+        					$(a).html(this.jobid);
+						}
+					);
+				}
+				
+				this.filter = null;
+				return;
+			}
+		}
+		else {
+			if (this.filter) {
+				$("#jobs").children().each(
+					function(){
+						var a = $(this).find("a");
+    					$(a).html(this.jobid);
+					}
+				);
+			}
+				
+			this.filter = null;
+			return;
+		}
+		
+		$("#jobs").children().each(
+			function(){
+        		var jobid = this.jobid;
+        		var index = jobid.indexOf(filter);
+        		if (index != -1) {
+        			var a = $(this).find("a");
+        			
+        			var endIndex = index + filter.length;
+        			var newHTML = jobid.substring(0, index) + "<span>" + jobid.substring(index, endIndex) + "</span>" + jobid.substring(endIndex, jobid.length);
+        			
+        			$(a).html(newHTML);
+        			$(this).show();
+        		}
+        		else {
+        			$(this).hide();
+        		}
+    	});
+    	
+    	this.filter = filter;
+	},
+	render: function(self) {
+		var data = this.model.get("data");
+		var nodes = data.nodes;
+		var edges = data.edges;
+		if (nodes.length == 0) {
+			console.log("No results");
+			return;
+		};
+	
+		var nodeArray = nodes.slice(0);
+		nodeArray.sort(function(a,b){ 
+			var diff = a.y - b.y;
+			if (diff == 0) {
+				return a.x - b.x;
+			}
+			else {
+				return diff;
+			}
+		});
+		
+		var ul = document.createElement("ul");
+		$(ul).attr("id", "jobs");
+		for (var i = 0; i < nodeArray.length; ++i) {
+			var li = document.createElement("li");
+			var a = document.createElement("a");
+			$(a).text(nodeArray[i].id);
+			li.appendChild(a);
+			ul.appendChild(li);
+			li.jobid=nodeArray[i].id;
+		}
+		
+		$("#list").append(ul);
+	}
+});
+
 var svgGraphView;
 azkaban.SvgGraphView = Backbone.View.extend({
 	events: {
@@ -57,38 +155,142 @@ azkaban.SvgGraphView = Backbone.View.extend({
 
 		$(svg).svgNavigate();
 	},
+	initializeDefs: function(self) {
+		var def = document.createElementNS(svgns, 'defs');
+		def.setAttributeNS(null, "id", "buttonDefs");
+
+		// ArrowHead
+		var arrowHeadMarker = document.createElementNS(svgns, 'marker');
+		arrowHeadMarker.setAttribute("id", "triangle");
+		arrowHeadMarker.setAttribute("viewBox", "0 0 10 10");
+		arrowHeadMarker.setAttribute("refX", "5");
+		arrowHeadMarker.setAttribute("refY", "5");
+		arrowHeadMarker.setAttribute("markerUnits", "strokeWidth");
+		arrowHeadMarker.setAttribute("markerWidth", "4");
+		arrowHeadMarker.setAttribute("markerHeight", "3");
+		arrowHeadMarker.setAttribute("orient", "auto");
+		var path = document.createElementNS(svgns, 'polyline');
+		arrowHeadMarker.appendChild(path);
+		path.setAttribute("points", "0,0 10,5 0,10 1,5");
+
+		def.appendChild(arrowHeadMarker);
+		
+		this.svgGraph.appendChild(def);
+	},
 	render: function(self) {
 		console.log("graph render");
 
 		var data = this.model.get("data");
 		var nodes = data.nodes;
+		var edges = data.edges;
+		if (nodes.length == 0) {
+			console.log("No results");
+			return;
+		};
+	
+		var bounds = {};
+		this.nodes = {};
 		for (var i = 0; i < nodes.length; ++i) {
-			this.drawNode(this, nodes[i]);
+			this.drawNode(this, nodes[i], bounds);
 		}
+		
+		for (var i = 0; i < edges.length; ++i) {
+			this.drawEdge(this, edges[i]);
+		}
+		
+		this.graphBounds = bounds;
+			
+		$("#svgGraph").svgNavigate("transformToBox", {x: bounds.minX, y: bounds.minY, width: (bounds.maxX - bounds.minX), height: (bounds.maxY - bounds.minY) });
 	},
 	changeSelected: function(self) {
 		console.log("change selected");
 	},
-	drawNode: function(self, node) {
+	drawEdge: function(self, edge) {
+		var svg = self.svgGraph;
+		var svgns = self.svgns;
+		
+		var startNode = this.nodes[edge.from];
+		var endNode = this.nodes[edge.target];
+		
+		if (edge.guides) {
+			var pointString = "" + startNode.sx + "," + startNode.sy + " ";
+
+			for (var i = 0; i < edge.guides.length; ++i ) {
+				edgeGuidePoint = edge.guides[i];
+				this.calcScalePoint(edgeGuidePoint);
+				pointString += edgeGuidePoint.sx + "," + edgeGuidePoint.sy + " ";
+			}
+			
+			pointString += endNode.sx + "," + endNode.sy;
+			var polyLine = document.createElementNS(svgns, "polyline");
+			polyLine.setAttributeNS(null, "points", pointString);
+			polyLine.setAttributeNS(null, "style", "fill:none;stroke:black;stroke-width:3");
+			self.mainG.appendChild(polyLine);
+		}
+		else {
+			var line = document.createElementNS(svgns, 'line');
+			line.setAttributeNS(null, "x1", startNode.sx);
+			line.setAttributeNS(null, "y1", startNode.sy);
+			
+			line.setAttributeNS(null, "x2", endNode.sx);
+			line.setAttributeNS(null, "y2", endNode.sy);
+			line.setAttributeNS(null, "style", "stroke:rgb(255,0,0);stroke-width:2");
+					
+			self.mainG.appendChild(line);
+		}
+	},
+	drawNode: function(self, node, bounds) {
 		var svg = self.svgGraph;
 		var svgns = self.svgns;
 
+		this.calcScalePoint(node);
+		var xOffset = 10;
+		var yOffset = 10;
+
 		var nodeG = document.createElementNS(svgns, "g");
 		nodeG.setAttributeNS(null, "id", node.id);
 		nodeG.setAttributeNS(null, "font-family", "helvetica");
-		nodeG.setAttributeNS(null, "transform", "translate(" + (node.x * 100) + "," + (node.y*100)+ ")");
+		nodeG.setAttributeNS(null, "transform", "translate(" + node.sx + "," + node.sy + ")");
+		
+		var innerG = document.createElementNS(svgns, "g");
+		innerG.setAttributeNS(null, "transform", "translate(-10,-10)");
 		
 		var rect1 = document.createElementNS(svgns, 'rect');
-		rect1.setAttributeNS(null, "y", 2);
-		rect1.setAttributeNS(null, "x", 2);
+		rect1.setAttributeNS(null, "y", 0);
+		rect1.setAttributeNS(null, "x", 0);
 		rect1.setAttributeNS(null, "ry", 12);
 		rect1.setAttributeNS(null, "width", 20);
-		rect1.setAttributeNS(null, "height", 30);
-		rect1.setAttributeNS(null, "style", "width:inherit;fill-opacity:1.0;stroke-opacity:1");
+		rect1.setAttributeNS(null, "height", 20);
+		rect1.setAttributeNS(null, "style", "width:inherit;stroke-opacity:1");
+		
 		
-		nodeG.appendChild(rect1);
+		var text = document.createElementNS(svgns, 'text');
+		var textLabel = document.createTextNode(node.id);
+		text.appendChild(textLabel);
+		text.setAttributeNS(null, "x", 4);
+		text.setAttributeNS(null, "y", 0);
+		text.setAttributeNS(null, "height", 10); 
+		
+		this.addBounds(bounds, {minX:node.sx - xOffset, minY: node.sy - yOffset, maxX: node.sx + xOffset, maxY: node.sy + yOffset});
+		
+		innerG.appendChild(rect1);
+		innerG.appendChild(text);
+		nodeG.appendChild(innerG);
 		self.mainG.appendChild(nodeG);
+		
+		this.nodes[node.id] = node;
+	},
+	addBounds: function(toBounds, addBounds) {
+		toBounds.minX = toBounds.minX ? Math.min(toBounds.minX, addBounds.minX) : addBounds.minX;
+		toBounds.minY = toBounds.minY ? Math.min(toBounds.minY, addBounds.minY) : addBounds.minY;
+		toBounds.maxX = toBounds.maxX ? Math.max(toBounds.maxX, addBounds.maxX) : addBounds.maxX;
+		toBounds.maxY = toBounds.maxY ? Math.max(toBounds.maxY, addBounds.maxY) : addBounds.maxY;
+	},
+	calcScalePoint : function(pointObj) {
+		pointObj.sx = 50*pointObj.x;
+		pointObj.sy = 50*pointObj.y;
 	}
+	
 });
 
 var graphModel;
@@ -113,6 +315,7 @@ $(function() {
 
 	graphModel = new azkaban.GraphModel();
 	svgGraphView = new azkaban.SvgGraphView({el:$('#svgDiv'), model: graphModel});
+	jobsListView = new azkaban.JobListView({el:$('#jobList'), model: graphModel});
 	
 	var requestURL = contextURL + "/manager";
 
diff --git a/src/web/js/svgNavigate.js b/src/web/js/svgNavigate.js
index 71f1915..35426ea 100644
--- a/src/web/js/svgNavigate.js
+++ b/src/web/js/svgNavigate.js
@@ -48,6 +48,8 @@
 			evt = window.event;
 		}
 		var target = evt.target;
+		
+		
 		var leftOffset = 0;
 		var topOffset = 0;
 		if (!target.marker) {
@@ -58,13 +60,6 @@
 			target = target.farthestViewportElement;
 		}
 		
-		if (target.parentNode.offsetLeft) {
-			leftOffset = target.parentNode.offsetLeft;
-		}
-		if (target.parentNode.offsetTop) {
-			topOffset = target.parentNode.offsetTop;
-		}
-		
 		// Trackball/trackpad vs wheel. Need to accommodate
 		var delta = 0;
 		if (evt.wheelDelta) {
@@ -78,9 +73,9 @@
 		target.zoomIndex = zoomLevel;
 		var scale = target.zoomLevels[zoomLevel];
 		
-		var x = evt.clientX - leftOffset;
-		var y = evt.clientY - topOffset;
-		
+		var y = evt.layerY;
+		var x = evt.layerX;
+
 		scaleGraph(target, scale, x, y);
 	}