1 /******************************************************************************
2 * Copyright (C) 2003 Jean-Daniel Fekete and INRIA, France *
3 * ------------------------------------------------------------------------- *
4 * This software is published under the terms of the X11 Software License *
5 * a copy of which has been included with this distribution in the *
6 * license-infovis.txt file. *
7 *****************************************************************************/
8 package edu.psu.geovista.ui;
9
10 import java.awt.*;
11 import java.awt.event.*;
12 import java.awt.geom.*;
13 import java.util.*;
14
15 import javax.swing.*;
16 import javax.swing.Timer;
17
18
19
20 /***
21 * Display excentric labels around items in a visualization.
22 *
23 * @author Jean-Daniel Fekete
24 * @version $Revision: 1.2 $
25 */
26 public class ExcentricLabels extends MouseAdapter implements Comparator,
27 MouseMotionListener {
28 Timer insideTimer;
29 int[] hits;//changed from IntColumn to int[] by fah
30 Rectangle2D.Double cursorBounds;
31 int centerX;
32 int centerY;
33 int focusSize = 50;
34 Point2D.Double[] itemPosition;
35 Point2D.Double[] linkPosition;
36 Point2D.Double[] labelPosition;
37 Point2D.Double[] left;
38 int leftCount;
39 Point2D.Double[] right;
40 int rightCount;
41 boolean xStable;
42 boolean yStable;
43 JComponent vpanel;
44 ExcentricLabelClient client;
45 boolean visible;
46 int gap = 5;
47 int maxLabels;
48 int labelCount;
49 int threshold = 20;
50 boolean opaque;
51 Color backgroundColor = Color.WHITE;
52
53 /***
54 * Constructor for ExcentricLabels.
55 */
56 public ExcentricLabels() {
57 cursorBounds = new Rectangle2D.Double(0, 0, focusSize, focusSize);
58
59 insideTimer = new Timer(2000,
60 new ActionListener() {
61 public void actionPerformed(ActionEvent e) {
62 setVisible(true);
63 }
64 });
65 insideTimer.setRepeats(false);
66 setMaxLabels(20);
67 }
68
69 /***
70 * @see javax.swing.JToolTip#setComponent(JComponent)
71 */
72 public void setComponent(JComponent c) {
73 if (c instanceof ExcentricLabelClient) {
74 if (vpanel == c)
75 return;
76 if (vpanel != null) {
77 vpanel.removeMouseListener(this);
78 vpanel.removeMouseMotionListener(this);
79 //visualization = null;
80 client = null;
81
82 }
83 vpanel = c;
84 if (vpanel != null) {
85 //visualization = vpanel.getVisualization();
86 client = (ExcentricLabelClient)c;
87 vpanel.addMouseListener(this);
88 vpanel.addMouseMotionListener(this);
89 }
90 } else
91 throw new RuntimeException("Invalid component type for ExcentricLabels");
92 }
93
94 public void paint(Graphics2D graphics, Rectangle2D bounds) {
95 //if (visualization == null || !visible)
96 if (client == null || !visible) { // added fah 18 march 2003
97 return;
98 }
99 FontMetrics fm = graphics.getFontMetrics();
100 computeExcentricLabels(graphics, bounds);
101
102 Line2D.Double line = new Line2D.Double();
103 for (int i = 0; i < labelCount; i++) {
104 int hit = hits[i];
105 String lab = client.getObservationLabel(hit);// added fah 18 march 2003
106 //visualization.getLabel(hits.get(i));
107 if (lab == null) {
108 lab = "item" + hits[i];
109 }
110
111 Point2D.Double pos = labelPosition[i];
112 if (opaque) {
113 graphics.setColor(backgroundColor);
114 Rectangle2D sb = fm.getStringBounds(lab, graphics);
115 graphics.fillRect((int)(pos.x+sb.getX()-2), (int)(pos.y+sb.getY()-2),
116 (int)sb.getWidth()+4, (int)sb.getHeight()+4);
117 }
118 graphics.setColor(Color.BLACK);
119 graphics.drawString(lab, (int)(pos.x), (int)(pos.y));
120 line.setLine(itemPosition[i], linkPosition[i]);
121 graphics.setXORMode(Color.white);
122 graphics.draw(line);
123 graphics.setPaintMode();
124 }
125 graphics.setColor(Color.RED);
126 graphics.draw(cursorBounds);
127 }
128
129 protected void computeExcentricLabels(Graphics2D graphics,
130 Rectangle2D bounds) {
131 if (client == null)
132 return;
133
134 cursorBounds.x = centerX - focusSize / 2;
135 cursorBounds.y = centerY - focusSize / 2;
136
137 //if (hits == null) hits = new IntColumn("pickAll");
138
139 hits = client.pickAll(cursorBounds);
140 //hits = visualization.pickAll(cursorBounds, bounds, hits);
141
142 labelCount = Math.min(maxLabels, hits.length);
143 if (labelCount != 0) {
144 computeItemPositions(graphics, bounds);
145 projectLeftRight(graphics, bounds);
146 }
147 }
148
149 protected void computeItemPositions(Graphics2D graphics,
150 Rectangle2D bounds) {
151 Rectangle2D.Double inter = new Rectangle2D.Double();
152
153 for (int i = 0; i < labelCount; i++) {
154 //Rectangle2D rect = visualization.getShapeAt(hits.get(i)).getBounds2D();
155 int hit = hits[i];
156 Rectangle2D rect = client.getShapeAt(hit).getBounds2D();
157 rect.intersect(cursorBounds, rect, inter);
158 itemPosition[i].setLocation(inter.getCenterX(), inter.getCenterY());
159 }
160 }
161
162 protected double comparableValueLeft(Point2D.Double pos) {
163 if (yStable)
164 return pos.y;
165 else
166 return Math.atan2(pos.y - centerY, centerX - pos.x);
167 }
168
169 protected double comparableValueRight(Point2D.Double pos) {
170 if (yStable)
171 return pos.getY();
172 else
173 return Math.atan2(pos.y - centerY, pos.x - centerX);
174 }
175
176 protected void projectLeftRight(Graphics2D graphics, Rectangle2D bounds) {
177 int radius = focusSize / 2;
178 int i;
179
180 leftCount = 0;
181 rightCount = 0;
182 double maxHeight = 0;
183 FontMetrics fm = graphics.getFontMetrics();
184
185 for (i = 0; i < labelCount; i++) {
186 Point2D.Double itemPos = itemPosition[i];
187 int hit = hits[i];
188 String lab = client.getObservationLabel(hit);
189 if (lab == null)
190 lab = "item" + hits[i];
191 Rectangle2D sb = fm.getStringBounds(lab, graphics);
192 Point2D.Double linkPos = linkPosition[i];
193 Point2D.Double labelPos = labelPosition[i];
194
195 maxHeight = Math.max(sb.getHeight(), maxHeight);
196 if (itemPosition[i].getX() < centerX) {
197 linkPos.y = comparableValueLeft(itemPos);
198 if (xStable)
199 linkPos.x = itemPos.x - radius - gap;
200 else
201 linkPos.x = centerX - radius - gap;
202 labelPos.x = linkPos.x - sb.getWidth();
203 left[leftCount++] = linkPos;
204 } else {
205 linkPos.y = comparableValueRight(itemPos);
206 if (xStable)
207 linkPos.x = itemPos.x + radius + gap;
208 else
209 linkPos.x = centerX + radius + gap;
210 labelPos.x = linkPos.x;
211 right[rightCount++] = linkPos;
212 }
213 }
214
215 Arrays.sort(left, 0, leftCount, this);
216 Arrays.sort(right, 0, rightCount, this);
217 double yMidLeft = leftCount * maxHeight / 2;
218 double yMidRight = rightCount * maxHeight / 2;
219 int ascent = fm.getAscent();
220
221 for (i = 0; i < leftCount; i++) {
222 Point2D.Double pos = left[i];
223 pos.y = i * maxHeight + centerY - yMidLeft + ascent;
224 }
225 for (i = 0; i < rightCount; i++) {
226 Point2D.Double pos = right[i];
227 pos.y = i * maxHeight + centerY - yMidRight + ascent;
228 }
229 for (i = 0; i < linkPosition.length; i++) {
230 labelPosition[i].y = linkPosition[i].y;
231 }
232 }
233
234 /***
235 * Returns the visible.
236 * @return boolean
237 */
238 public boolean isVisible() {
239 return visible;
240 }
241
242 /***
243 * Sets the visible.
244 * @param visible The visible to set
245 */
246 public void setVisible(boolean visible) {
247 if (this.visible != visible) {
248 this.visible = visible;
249 client.repaint();
250 //visualization.repaint();
251 }
252 }
253
254 /***
255 * For sorting points vertically.
256 *
257 * @see java.util.Comparator#compare(Object, Object)
258 */
259 public int compare(Object o1, Object o2) {
260 double d = ((Point2D.Double)o1).getY() - ((Point2D.Double)o2).getY();
261 if (d < 0)
262 return -1;
263 else if (d == 0)
264 return 0;
265 else
266 return 1;
267 }
268
269 /***
270 * @see java.awt.event.MouseAdapter#mouseEntered(MouseEvent)
271 */
272 public void mouseEntered(MouseEvent e) {
273 insideTimer.restart();
274 }
275
276 /***
277 * @see java.awt.event.MouseAdapter#mouseExited(MouseEvent)
278 */
279 public void mouseExited(MouseEvent e) {
280 insideTimer.stop();
281 setVisible(false);
282 }
283
284 /***
285 * @see java.awt.event.MouseAdapter#mousePressed(MouseEvent)
286 */
287 public void mousePressed(MouseEvent e) {
288 setVisible(false);
289 }
290
291 /***
292 * @see java.awt.event.MouseMotionListener#mouseDragged(MouseEvent)
293 */
294 public void mouseDragged(MouseEvent e) {
295 }
296
297 int dist2(int dx, int dy) {
298 return dx * dx + dy * dy;
299 }
300
301 /***
302 * @see java.awt.event.MouseMotionListener#mouseMoved(MouseEvent)
303 */
304 public void mouseMoved(MouseEvent e) {
305 if (isVisible()) {
306 if (dist2(centerX - e.getX(), centerY - e.getY()) > threshold * threshold) {
307 setVisible(false);
308 insideTimer.restart();
309 }
310 client.repaint();
311 //visualization.repaint();
312 }
313 centerX = e.getX();
314 centerY = e.getY();
315 }
316
317 /***
318 * Returns the gap.
319 * @return int
320 */
321 public int getGap() {
322 return gap;
323 }
324
325 /***
326 * Sets the gap.
327 * @param gap The gap to set
328 */
329 public void setGap(int gap) {
330 this.gap = gap;
331 }
332
333 /***
334 * Returns the maxLabels.
335 * @return int
336 */
337 public int getMaxLabels() {
338 return maxLabels;
339 }
340
341 void allocatePoints(Point2D.Double[] array) {
342 for (int i = 0; i < array.length; i++)
343 array[i] = new Point2D.Double();
344 }
345
346 /***
347 * Sets the maxLabels.
348 * @param maxLabels The maxLabels to set
349 */
350 public void setMaxLabels(int maxLabels) {
351 this.maxLabels = maxLabels;
352 itemPosition = new Point2D.Double[maxLabels];
353 allocatePoints(itemPosition);
354 linkPosition = new Point2D.Double[maxLabels];
355 allocatePoints(linkPosition);
356 labelPosition = new Point2D.Double[maxLabels];
357 allocatePoints(labelPosition);
358 left = new Point2D.Double[maxLabels];
359 right = new Point2D.Double[maxLabels];
360 }
361
362 /***
363 * Returns the threshold.
364 *
365 * When the mouse moves a distance larger than this
366 * threshold since the last event, excentric labels
367 * are disabled.
368 *
369 * @return int
370 */
371 public int getThreshold() {
372 return threshold;
373 }
374
375 /***
376 * Sets the threshold.
377 *
378 * When the mouse moves a distance larger than the
379 * specified threshold since the last event, excentric
380 * labels are disabled.
381 *
382 * @param threshold The threshold to set
383 */
384 public void setThreshold(int threshold) {
385 this.threshold = threshold;
386 }
387 /***
388 * Returns the focusSize.
389 * @return int
390 */
391 public int getFocusSize() {
392 return focusSize;
393 }
394
395 /***
396 * Sets the focusSize.
397 * @param focusSize The focusSize to set
398 */
399 public void setFocusSize(int focusSize) {
400 this.focusSize = focusSize;
401 cursorBounds = new Rectangle2D.Double(0, 0, focusSize, focusSize);
402 }
403
404 /***
405 * Returns the backgroundColor.
406 * @return Color
407 */
408 public Color getBackgroundColor() {
409 return backgroundColor;
410 }
411
412 /***
413 * Returns the opaque.
414 * @return boolean
415 */
416 public boolean isOpaque() {
417 return opaque;
418 }
419
420 /***
421 * Sets the backgroundColor.
422 * @param backgroundColor The backgroundColor to set
423 */
424 public void setBackgroundColor(Color backgroundColor) {
425 this.backgroundColor = backgroundColor;
426 }
427
428 /***
429 * Sets the opaque.
430 * @param opaque The opaque to set
431 */
432 public void setOpaque(boolean opaque) {
433 this.opaque = opaque;
434 }
435
436 }
This page was automatically generated by Maven