-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathPositions.java
More file actions
300 lines (267 loc) · 10.9 KB
/
Copy pathPositions.java
File metadata and controls
300 lines (267 loc) · 10.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
/**
* Purpose of this file is to catalog all parts of the code where node
* positions play a role.
*/
/**
* In the original version of Galant, life was simple.
* (1) Algorithms did not change node positions.
* (2) There were no state changes during editing.
*
* I needed to rethink (1) because I wanted to animate sorting
* algorithms and layered graph algorithms. This led to the creation
* of two types of positions.
* - fixed positions: modified by the user during editing only
* - positions (state sensitive): able to be modified during an algorithm
* the non-fixed positions used the mechanism of the original
* positions; like other attributes, they were state sensitive, but,
* because of (1) and (2), they only resulted in one new state, the
* initial one.
*
* Because it was easy to accidentally create and delete nodes during
* editing, I introduced an undo/redo mechanism, which forced me to
* abandon (2). Now life is more complicated.
*
* In addition to scaling the desired behavior (functional
* requirement) with respect to positions is as follows.
* A node has both an edit position and an animation position.
* A user may move a node at any time by pointing to it and dragging
* the mouse.
* Ultimately, there should also be a keyboard shortcut for moving a
* node, but that gets complicated. The possible scenarios are
*
* + user moves a node during editing - its edit position changes
* + user moves a node during an animation that does not move nodes -
* its edit position changes
* + user moves a node during an animation that moves nodes - its
* animation position changes temporarily; the node snaps back
* into place when the display state changes (user presses a
* forward or backward arrow)
* [note: movement of nodes by user in this last case should be
* related to screen position only, not related to logical position]
*/
/**
* getNodeCenter() is used for
* drawing nodes, determining endpoints of edges, positions of labels
* and weights for both nodes and edges.
*
* Two important points.
* (i) the special case of layered graphs has details that can be used
* as a model for the scaling
* (ii) the decision of whether to use getPosition(state) or
* getFixedPosition() may need to be hidden underneath the method that
* does the scaling; the proposed enhancements will add complexity to
* this decision and one of these two methods may go away
*
* The position we want may always be state-based: animation state if
* the algorithm moves nodes; edit state otherwise;
* getDisplayState() returns animation state if the algorithm is
* running, regardless of whether it moves nodes.
*/
private Point getNodeCenter( Node n ) throws GalantException{
int state = dispatch.getDisplayState();
Point nodeCenter = null;
if ( dispatch.isAnimationMode()
&& GraphDispatch.getInstance().algorithmMovesNodes() ) {
nodeCenter = n.getPosition(state);
}
else {
nodeCenter = n.getFixedPosition();
}
// if graph is layered and node has layer and position in layer
// information, base its location on that
if ( dispatch.getWorkingGraph().isLayered() ) {
int x = 0;
int y = 0;
int layer = n.getLayer(); // should not change during an
// animation of a layered graph algorithm
int position = n.getPositionInLayer(state);
int layerSize = 1;
// vertical layered graphs have gaps in positions on some layers,
// i.e., positions on some layers are not contiguous; in that
// case, positions should be taken "literally", i.e., position p
// means the same thing on every layer
if ( dispatch.getWorkingGraph().isVertical() )
layerSize = dispatch.getWorkingGraph().maxPositionInAnyLayer() + 1;
else
layerSize = dispatch.getWorkingGraph().numberOfNodesOnLayer(layer);
int width = dispatch.getWindowWidth();
// center node in layer if it's unique; else do the usual
if (layerSize == 1) {
x = width / 2;
}
else {
int positionGap
= (width - 2 * HORIZONTAL_PADDING) / (layerSize - 1);
x = HORIZONTAL_PADDING + position * positionGap;
}
int numberOfLayers = dispatch.getWorkingGraph().numberOfLayers();
int height = dispatch.getWindowHeight();
// center layer in window if it's unique; else do the usual
if (numberOfLayers == 1) {
y = height / 2;
}
else {
int layerGap
= (height - 2 * VERTICAL_PADDING) / (numberOfLayers - 1);
y = VERTICAL_PADDING
+ n.getLayer() * layerGap;
// + (numberOfLayers - n.getLayer() - 1) * layerGap;
}
nodeCenter = new Point( x, y );
}
if ( nodeCenter == null )
throw new GalantException("Unable to compute center for node " + n);
return nodeCenter;
}
/**
* There are some important exceptions where getNodeCenter() is not
* used, both related to user interaction with the panel.
*/
/**
* One is determining what node or edge a user is selecting with the
* mouse.
* getFixedPosition() will need to be replaced by the scaled method
*/
public Node selectTopClickedNode(Point p) {
LogHelper.enterMethod(getClass(), "selectTopClickedNode");
Graph g = dispatch.getWorkingGraph();
int stateNumber = g.getEditState();
Node top = null;
for (Node n : g.getNodes(stateNumber)) {
/** *** !!! *** */
if ( p.distance(n.getFixedPosition()) < NODE_SELECTION_RADIUS ) {
top = n;
}
}
previousNode = selectedNode;
selectedNode = top;
selectedEdge = null;
LogHelper.exitMethod( getClass(), "selectTopClickedNode, node = "
+ (selectedNode == null ? "null"
: selectedNode.getId() ) );
return top;
}
public Edge selectTopClickedEdge(Point p) {
LogHelper.enterMethod(getClass(), "selectTopClickedEdge");
Graph g = dispatch.getWorkingGraph();
int stateNumber = g.getEditState();
Edge top = null;
for (int i=1; i <= EDGE_SELECTION_WIDTH; i++) {
double width = i;
double centerVal = width/2;
LogHelper.logDebug( "centerVal = " + centerVal );
Rectangle2D clickArea
= new Rectangle2D.Double(p.getX() - centerVal,
p.getY() - centerVal - 1, i, i);
for (Edge e : g.getEdges(stateNumber)) {
/** *** !!! *** */
Point p1 = e.getSourceNode().getFixedPosition();
Point p2 = e.getTargetNode().getFixedPosition();
Line2D l = new Line2D.Double(p1, p2);
if (l.intersects(clickArea)) {
top = e;
}
}
if (top != null) break;
}
this.selectedEdge = top;
this.selectedNode = null;
this.previousNode = null;
LogHelper.exitMethod(getClass(), "selectTopClickedEdge");
return top;
}
/**
* Another is when the user is drawing an edge.
* Here, getFixedPosition() needs to be replaced by the scaling method
*/
public void paintComponent(Graphics g) {
try {
// ...
// If you're drawing an edge, draw a line between the first node and
// the cursor
if ( ! dispatch.isAnimationMode()
&& this.selectedNode != null
&& this.edgeTracker != null ) {
Point p1 = edgeTracker;
Point p2 = selectedNode.getFixedPosition();
g2d.drawLine(p1.x, p1.y, p2.x, p2.y);
}
}
/**
* Some methods in Algorithm.java query the current (logical) position of a node.
*/
public Integer getX(Node v) throws GalantException {
checkGraphElement(v);
return v.getX();
}
public Integer getY(Node v) throws GalantException {
checkGraphElement(v);
return v.getY();
}
public Point getPosition(Node v) throws GalantException {
checkGraphElement(v);
return v.getPosition();
}
/**
* Also, there is a method that was incorporated into Algorithm.java
* as a convenience for several algorithms that use distance between
* nodes to make decisions, e.g., shortest paths and minimum spanning
* trees. We should probably use screen position here.
*/
/**
* @return the distance between two nodes
*/
public double distance(Node nodeOne, Node nodeTwo) {
return nodeOne.getPosition().distance(nodeTwo.getPosition());
}
/**
* There are a few places where node positions are changed.
*/
/**
* One is during editing, in GraphWindow.java.
* Here, the screen position is known but not the logical one. Some
* "snapping into place" may need to happen here if logical positions
* involve small integers.
*/
graphPanel.addMouseMotionListener(new MouseMotionListener() {
@Override
public void mouseDragged(MouseEvent arg0) {
// If you start dragging, set dragging mode so you don't
// perform any other operations on the Node until after
// releasing it
Node sel = graphPanel.getSelectedNode();
if (sel != null) {
graphPanel.setDragging(true);
graphPanel.setEdgeTracker(null);
if ( ! dispatch.isAnimationMode()
|| ! dispatch.algorithmMovesNodes() ) {
try {
/** *** !!! *** */
sel.setFixedPosition(arg0.getPoint());
} catch (Exception e) {
e.printStackTrace();
}
}
}
frame.repaint();
}
/**
* The other is when an algorithm changes the position of a node
*/
public void setX(Node v, int x) throws Terminate, GalantException {
checkGraphElement(v);
v.setX(x);
}
public void setY(Node v, int y) throws Terminate, GalantException {
checkGraphElement(v);
v.setY(y);
}
public void setPosition(Node v, int x, int y) throws Terminate, GalantException {
checkGraphElement(v);
v.setPosition(x, y);
}
public void setPosition(Node v, Point pt) throws Terminate, GalantException {
checkGraphElement(v);
v.setPosition(pt);
}
// [Last modified: 2021 02 12 at 21:59:13 GMT]