View Javadoc
1   package de.tud.plt.r43ples.visualisation;
2   
3   import java.awt.BasicStroke;
4   import java.awt.Color;
5   import java.awt.Dimension;
6   import java.awt.Graphics2D;
7   import java.awt.geom.Dimension2D;
8   import java.awt.geom.GeneralPath;
9   import java.util.HashMap;
10  import java.util.LinkedList;
11  import java.util.List;
12  import java.util.Map;
13  import java.util.Queue;
14  
15  import de.tud.plt.r43ples.revisionTree.Commit;
16  
17  public class CommitGraphView {
18  	
19  	/**
20  	 * Diameter of the circles representing commits
21  	 */
22  	private static final int CIRCLE_DIAMETER = 10;
23  	
24  	
25  	/**
26  	 * Horizontal distance between commits respective branches
27  	 */
28  	private static final int COLUMN_WIDTH = 15;
29  	
30  	// dimensions of this graph, is null until drawn
31  	private Dimension2D dimension;
32  	// saves terminal commits of columns
33  	private List<Commit> terminalCommits = new LinkedList<Commit>();
34  	// saves on which column a commit was drawn
35  	private Map<Commit, Integer> commit_column = new HashMap<Commit, Integer>();
36  	// holds colors of branches, is cycled
37  	private Queue<Color> colorQueue;
38  	// list of commits
39  	private List<Commit> commits;
40  
41  	public CommitGraphView(List<Commit> commits) {
42  		this.commits = commits;
43  		colorQueue = new LinkedList<Color>();
44  		colorQueue.add(new Color(0.5f, 0.1f, 0.1f));
45  		colorQueue.add(new Color(0.1f, 0.5f, 0.1f));
46  		colorQueue.add(new Color(0.1f, 0.1f, 0.5f));
47  		colorQueue.add(new Color(0.5f, 0.5f, 0.1f));
48  		colorQueue.add(new Color(0.5f, 0.1f, 0.5f));
49  		colorQueue.add(new Color(0.1f, 0.5f, 0.5f));
50  	}
51  	
52  	public void drawGraph(Graphics2D g) {
53  
54  		int currentLine = 0;
55  		int maxColumn = 0;
56  		g.translate(0, VisualisationTable.LINE_HEIGHT/2);
57  		g.setStroke(new BasicStroke(2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND));
58  		
59  		Map<Commit, Color> commit_color = new HashMap<Commit, Color>();
60  
61  		for (Commit c : commits) {
62  
63  			// get column for this commit
64  			int currentColumn = getColumnForCommit(c);
65  
66  			// find color
67  			Color currentColor = null;
68  			for (Commit suc : c.successors) {
69  				if (suc.getBranch().equals(c.getBranch()))
70  					currentColor = commit_color.get(suc);
71  			}
72  			if (currentColor == null)
73  				currentColor = getNextColor();
74  
75  			// connect with any succeeding commits
76  			for (Commit suc : c.successors) {
77  
78  				// calculate and set starting point
79  				GeneralPath path = new GeneralPath();
80  				int x = COLUMN_WIDTH * currentColumn + CIRCLE_DIAMETER / 2;
81  				int y = VisualisationTable.LINE_HEIGHT * currentLine + CIRCLE_DIAMETER / 2;
82  				path.moveTo(x, y);
83  
84  				// set color
85  				if (!suc.getBranch().equals(c.getBranch()) && suc.predecessor.size() == 1)
86  					g.setColor(commit_color.get(suc));
87  				else
88  					g.setColor(currentColor);
89  
90  				// get line and column of successor
91  				int endLine = commits.indexOf(suc);
92  				int endColumn = commit_column.get(suc);
93  
94  				// find column for connection line
95  				int column = currentColumn; // fallback
96  				if (terminalCommits.contains(suc)) {
97  					// if successor terminates a column, use his column
98  					column = terminalCommits.indexOf(suc);
99  				} else if (!terminalCommits.contains(c)) {
100 					// if successor is not terminating a column and current
101 					// commit is not the first commit of his column, use new
102 					// column
103 					column = terminalCommits.size();
104 					terminalCommits.add(null);
105 				}
106 				int line;
107 				for (line = currentLine; line > endLine + 1; line--) {
108 					// drawConnection
109 					drawConnectionTo(path, line - 1, column);
110 					maxColumn = Math.max(maxColumn, column);
111 				}
112 				maxColumn = Math.max(maxColumn, column);
113 				drawConnectionTo(path, endLine, endColumn);
114 				g.draw(path);
115 			}
116 			
117 			// update memory
118 			terminalCommits.set(currentColumn, c);
119 			commit_column.put(c, currentColumn);
120 			commit_color.put(c, currentColor);
121 
122 			// advance to next line
123 			currentLine++;
124 		}
125 
126 		// draw circles
127 		for (int l = 0; l < commits.size(); l++) {
128 			Commit c = commits.get(l);
129 			g.setColor(commit_color.get(c));
130 			int column = commit_column.get(c);
131 			drawCircle(g, l, column);
132 		}
133 		
134 		g.translate(0, -VisualisationTable.LINE_HEIGHT/2);
135 
136 		// calculate dimensions
137 		dimension = new Dimension();
138 		dimension.setSize((maxColumn+1) * COLUMN_WIDTH, commits.size()
139 				* VisualisationTable.LINE_HEIGHT);
140 
141 	}
142 	
143 	/**
144 	 * @return Dimension of drawn graph or null if nothing has been drawn yet
145 	 */
146 	public Dimension2D getDimension() {
147 		return dimension;
148 	}
149 
150 	private void drawConnectionTo(GeneralPath path, int line,
151 			int column) {
152 		
153 		int x1 = (int) path.getCurrentPoint().getX();
154 		int y1 = (int) path.getCurrentPoint().getY();
155 		int x2 = COLUMN_WIDTH * column + CIRCLE_DIAMETER / 2;
156 		int y2 = VisualisationTable.LINE_HEIGHT * line + CIRCLE_DIAMETER / 2;
157 
158 		if (x1 == x2) {
159 			// draw straight line
160 			path.lineTo(x2, y2);
161 		} else {
162 			// draw curved line
163 			int curvedLineBase = (int) (VisualisationTable.LINE_HEIGHT * 0.7);
164 			path.curveTo(x1, y1 - curvedLineBase, x2, y2 + curvedLineBase,
165 					x2, y2);
166 		}
167 	}
168 
169 	private void drawCircle(Graphics2D g, int line, int column) {
170 		g.fillOval(COLUMN_WIDTH * column, VisualisationTable.LINE_HEIGHT * line, CIRCLE_DIAMETER,
171 				CIRCLE_DIAMETER);
172 	}
173 
174 	private int getColumnForCommit(Commit c) {
175 
176 		int column = -1;
177 
178 		// test if there is a successor of same branch terminating a column
179 		for (Commit suc : c.successors) {
180 			if (terminalCommits.contains(suc) && suc.getBranch().equals(c.getBranch())) {
181 				return terminalCommits.indexOf(suc);
182 			}
183 		}
184 
185 		// test for reusable columns
186 		for (Commit term : terminalCommits) {
187 			boolean isReusable = true;
188 
189 			// test whether terminal commit has predecessors not yet drawn
190 			for (Commit term_pre : term.predecessor) {
191 				if (commits.indexOf(term_pre) > commits.indexOf(c)) {
192 					isReusable = false;
193 					break;
194 				}
195 				// test whether current commit has successors that would lead to
196 				// overlapping
197 				for (Commit suc : c.successors) {
198 					if (!terminalCommits.contains(suc) && (commits.indexOf(suc) < commits.indexOf(term_pre))) {
199 						isReusable = false;
200 						break;
201 					}
202 				}
203 				if (!isReusable)
204 					break;
205 			}
206 
207 			if (isReusable)
208 			{
209 				return terminalCommits.indexOf(term);
210 			}
211 		}
212 
213 		// no column found, add new column
214 		if (column == -1) {
215 			terminalCommits.add(c);
216 			column = terminalCommits.indexOf(c);
217 		}
218 
219 		return column;
220 	}
221 
222 	private Color getNextColor() {
223 		Color c = colorQueue.poll();
224 		colorQueue.add(c);
225 		return c;
226 	}
227 }