KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > sape > carbon > services > swing > graph > GraphCanvas


1 /*
2  * The contents of this file are subject to the Sapient Public License
3  * Version 1.0 (the "License"); you may not use this file except in compliance
4  * with the License. You may obtain a copy of the License at
5  * http://carbon.sf.net/License.html.
6  *
7  * Software distributed under the License is distributed on an "AS IS" basis,
8  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
9  * the specific language governing rights and limitations under the License.
10  *
11  * The Original Code is The Carbon Component Framework.
12  *
13  * The Initial Developer of the Original Code is Sapient Corporation
14  *
15  * Copyright (C) 2003 Sapient Corporation. All Rights Reserved.
16  */

17
18 package org.sape.carbon.services.swing.graph;
19
20
21 import java.awt.BasicStroke JavaDoc;
22 import java.awt.Color JavaDoc;
23 import java.awt.Graphics JavaDoc;
24 import java.awt.Graphics2D JavaDoc;
25 import java.awt.Rectangle JavaDoc;
26 import java.awt.RenderingHints JavaDoc;
27 import java.awt.geom.AffineTransform JavaDoc;
28 import java.awt.geom.GeneralPath JavaDoc;
29 import java.awt.geom.Line2D JavaDoc;
30 import java.awt.geom.PathIterator JavaDoc;
31 import java.awt.geom.Point2D JavaDoc;
32 import java.awt.geom.Rectangle2D JavaDoc;
33 import java.text.NumberFormat JavaDoc;
34 import java.util.HashMap JavaDoc;
35 import java.util.Iterator JavaDoc;
36 import java.util.Map JavaDoc;
37
38 import javax.swing.JFrame JavaDoc;
39 import javax.swing.JPanel JavaDoc;
40
41
42
43 /** <p>This class provides a consistent space within to graph real
44  * numbers. It provides features such as auto-centering and
45  * real-time scaling. A user of this graph provides data by creating
46  * one or more tracks and then adding real points to those. Calling
47  * translate periodically allows you to create a scrolling graph as
48  * well.</p>
49  *
50  * <p>This graph will also maintain tick marks that resize and
51  * can be stuck to the sides of the screen so they are always
52  * visible even if the origin is off-screen.</p>
53  *
54  * Copyright 2001 Sapient
55  * @author Greg Hinkle
56  * @version $Revision: 1.4 $ ($Author: dvoet $ / $Date: 2003/05/05 21:21:26 $)
57  */

58 public class GraphCanvas extends JPanel JavaDoc {
59
60     /** A list of Track's that are a part of this graph */
61     private Map JavaDoc tracks = new HashMap JavaDoc(11);
62
63     /** The current graph bounds that are visible */
64     protected Rectangle2D JavaDoc graphBounds;
65
66     /** The portion of the entire height that should be researved as
67      * a border, above and below the highest and lowest track points */

68     private static final double BORDER_PERCENT = 0.1d;
69
70     /** The background color for this graph */
71     protected Color JavaDoc backgroundColor = new Color JavaDoc(204,204,204);
72
73     protected static NumberFormat JavaDoc labelFormat = null;
74     protected static NumberFormat JavaDoc bigNumberLabelFormat = null;
75
76     /**
77      * Instantiates a graph canvas
78      */

79     public GraphCanvas() {
80         super();
81
82         setBackground(Color.blue);
83
84         this.graphBounds = new Rectangle2D.Double JavaDoc(-5,0,150,2);
85
86
87         this.labelFormat = NumberFormat.getNumberInstance();
88         this.labelFormat.setMaximumFractionDigits(2);
89
90         this.bigNumberLabelFormat = NumberFormat.getNumberInstance();
91         this.bigNumberLabelFormat.setMaximumFractionDigits(0);
92
93
94         System.out.println("GraphCanvas::<init> - New GraphCanvas created.");
95     }
96
97     /**
98      * <p>Sets the background color of this graph
99      *
100      * @param color the Color to set the background to
101      */

102     public void setBackgroundColor(Color JavaDoc color) {
103         this.backgroundColor = color;
104     }
105
106     /** Gets the bounds of the graphing space that are currently showing
107      * on the screen.
108      * @return Rectangle2D The bounds of the currently visible graph
109      */

110     public Rectangle2D JavaDoc getGraphBounds() {
111         return this.graphBounds;
112     }
113
114     /**
115      * Sets the bounds that this graph is displaying
116      *
117      * @param rect the Rectangle2D of the desired graph points
118      */

119     public void setGraphBounds(Rectangle2D JavaDoc rect) {
120         this.graphBounds = rect;
121     }
122
123
124     public AffineTransform JavaDoc getTransform() {
125
126         AffineTransform JavaDoc affineT =
127         new AffineTransform JavaDoc(1d,0d,0d,-1d,0d,super.getParent().getHeight());
128
129
130
131         // scale to current scale
132
affineT.concatenate(
133         AffineTransform.getScaleInstance(
134         this.getBounds().getWidth() / this.graphBounds.getWidth(),
135         this.getBounds().getHeight() / this.graphBounds.getHeight()));
136
137         // translate to the current origin
138
affineT.concatenate(
139         AffineTransform.getTranslateInstance(
140         -this.graphBounds.getX(),
141         -this.graphBounds.getY()));
142
143         return affineT;
144     }
145
146
147
148     // CLEAR ALL CURVES FROM PLOT
149
public void clear() {
150
151     }
152
153     public void addTrack(String JavaDoc trackName) {
154         this.tracks.put(trackName, new Track(trackName));
155     }
156     public void addTrack(String JavaDoc trackName,Color JavaDoc color) {
157         this.tracks.put(trackName, new Track(trackName,color));
158     }
159
160
161     // ADD CURVE TO STORAGE (DOESN'T GRAPH UNTIL REPAINT()).
162
public void addPoint(String JavaDoc track, Point2D JavaDoc point) {
163         ((Track)this.tracks.get(track)).addPoint(point);
164     }
165
166     public Track getTrack(String JavaDoc trackName) {
167         return (Track) this.tracks.get(trackName);
168     }
169
170
171
172
173     public void clearAll() {
174         this.getGraphics().clearRect(
175         (int)getBounds().getX(),
176         (int)getBounds().getY(),
177         (int)getBounds().getWidth(),
178         (int)getBounds().getHeight());
179     }
180
181     public void paint(Graphics JavaDoc gg) {
182         Graphics2D JavaDoc g = (Graphics2D JavaDoc) gg;
183         g.setBackground(this.backgroundColor);
184         // What is the current graph to panel transform
185
AffineTransform JavaDoc newTrans = getTransform();
186
187         g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_OFF);
188
189
190         // Erase this entire graph so that we can redraw it
191
g.clearRect(
192             (int)getBounds().getX(),
193             (int)getBounds().getY(),
194             (int)getBounds().getWidth(),
195             (int)getBounds().getHeight());
196
197
198         // This draws the tick marks and the tick values
199
drawLines(g,newTrans);
200
201         // This drawes the axeses
202
drawAxes(g,newTrans);
203
204         // This draws each of tracks in this graph
205
drawTracks(g,newTrans);
206
207         // This draws the keys for each graph
208
drawKey(g,newTrans);
209     }
210
211     /**
212      * <p>Draw the key to the tracks by calling thier toString</p>
213      *
214      * @param graphics2D the Graphics2D to draw to
215      * @param transform the Affine Transform to use to determine how to draw
216      */

217     protected void drawKey(Graphics2D JavaDoc g, AffineTransform JavaDoc transform) {
218
219         int start = 20;
220
221         Iterator JavaDoc trackIterator = this.tracks.values().iterator();
222         while(trackIterator.hasNext()) {
223
224             Track track = (Track) trackIterator.next();
225
226             String JavaDoc info = track.toString();
227             // Will draw the key in the same color as it's line
228
g.setColor(track.getColor());
229             g.drawString(info,50, start+=25);
230         }
231     }
232
233     protected void drawTracks(Graphics2D JavaDoc g, AffineTransform JavaDoc transform) {
234
235         // Store original transform to restore it later
236
// until I figure out how to track differences only
237
AffineTransform JavaDoc origTrans = g.getTransform();
238
239
240         // Transform for local drawing
241
g.transform(transform);
242
243         g.setColor(Color.orange);
244
245         // Using a small stroke will minimize the width to a single output
246
// level pixel up to a reasonable scaling size
247
g.setStroke(new BasicStroke JavaDoc(0.001f));
248
249         // Draw the tracks
250
Iterator JavaDoc trackIterator = this.tracks.values().iterator();
251         while (trackIterator.hasNext()) {
252             Track track = (Track) trackIterator.next();
253             g.setColor(track.getColor());
254             GeneralPath JavaDoc path = track.getPath();
255
256             g.draw(path);
257         }
258
259         // Reset transformation
260
g.setTransform(origTrans);
261     }
262
263     /**
264      * This draws the axes
265      */

266     protected void drawAxes(Graphics2D JavaDoc g,AffineTransform JavaDoc transform) {
267         g.setColor(Color.white);
268
269         Point2D JavaDoc origin = transform.transform(new Point2D.Double JavaDoc(0,0),null);
270
271         // If you want to have rubber banding axes (always visible)
272
Rectangle2D JavaDoc axesRect = new Rectangle2D.Double JavaDoc(5,5,this.bounds().getWidth()-10,this.bounds().getHeight());
273         origin = floorPoint(origin,axesRect);
274
275         Line2D JavaDoc x = new Line2D.Double JavaDoc(
276         getBounds().getMinX(), origin.getY(),
277         getBounds().getMaxX(), origin.getY());
278
279         Line2D JavaDoc y = new Line2D.Double JavaDoc(
280         origin.getX(), getBounds().getMinY(),
281         origin.getX(), getBounds().getMaxY());
282
283         g.draw(x);
284         g.draw(y);
285     }
286
287
288     /**
289      * <p>This finds the closest point on a rectangle's edge to a point outside
290      * the rectangle or if that point is within the rectangle it is returned.
291      * </p>
292      *
293      * @param point The point to rectangularly floor
294      * @param rect The rectangle to floor within
295      */

296     public static Point2D JavaDoc floorPoint(Point2D JavaDoc point, Rectangle2D JavaDoc rect) {
297         double x = point.getX();
298         double y = point.getY();
299
300         if (x < rect.getMinX())
301             x = rect.getMinX();
302         if (x > rect.getMaxX())
303             x = rect.getMaxX();
304         if (y < rect.getMinY())
305             y = rect.getMinY();
306         if (y > rect.getMaxY())
307             y = rect.getMaxY();
308
309         return new Point2D.Double JavaDoc(x,y);
310     }
311
312
313     /**
314      * <p>This draws the tick marks in the graph
315      *
316      */

317     protected void drawLines(Graphics2D JavaDoc g, AffineTransform JavaDoc transform) {
318
319         g.setColor(Color.white);
320         int REAL_TICK_SPACE = 40;
321         int REAL_TICK_HEIGHT = 10;
322
323         double graphTickSpaceX = (REAL_TICK_SPACE / transform.getScaleX());
324         double graphTickSpaceY = (REAL_TICK_SPACE / Math.abs(transform.getScaleY()));
325
326
327         Point2D JavaDoc origin = transform.transform(new Point2D.Float JavaDoc(0,0),null);
328
329         // If you want to have rubber banding axes (always visible)
330
Rectangle2D JavaDoc axesRect = new Rectangle2D.Double JavaDoc(5,5,this.bounds().getWidth()-10,this.bounds().getHeight());
331         Point2D JavaDoc falseOrigin = floorPoint(origin,axesRect);
332
333         double firstX = this.graphBounds.getMinX();
334
335         Point2D JavaDoc pt = new Point2D.Float JavaDoc();
336         for (double x = firstX; x <= (this.graphBounds.getMaxX()+graphTickSpaceX); x += graphTickSpaceX) {
337             double tx = (Math.floor(x/graphTickSpaceX)) * graphTickSpaceX;
338             pt.setLocation(tx,0);
339             transform.transform(pt,pt);
340             g.drawLine((int)pt.getX(),(int)falseOrigin.getY() - 5 ,(int)pt.getX(),(int)falseOrigin.getY() + 5);
341
342             String JavaDoc label;
343             if (tx > 10)
344                 label = this.bigNumberLabelFormat.format(tx);
345             else
346                 label = this.labelFormat.format(tx);
347
348             g.drawString(label,
349                 (float)pt.getX(),(float)falseOrigin.getY()-9);
350         }
351
352
353         double firstY = this.graphBounds.getMinY();
354         for (double y = firstY; y <= (this.graphBounds.getMaxY()+graphTickSpaceY); y += graphTickSpaceY) {
355             double ty = (Math.floor(y/graphTickSpaceY)) * graphTickSpaceY;
356             pt.setLocation(0,ty);
357             transform.transform(pt,pt);
358             g.drawLine((int)falseOrigin.getX() - 5,(int)pt.getY() ,(int)falseOrigin.getX() + 5,(int)pt.getY());
359
360             String JavaDoc label;
361             if (ty > 10)
362                 label = this.bigNumberLabelFormat.format(ty);
363             else
364                 label = this.labelFormat.format(ty);
365
366             g.drawString(label,
367                 (float)falseOrigin.getX()+7,(float)pt.getY());
368
369         }
370     }
371
372     public static class Track {
373         protected String JavaDoc name;
374         protected Color JavaDoc color = Color.black; //Default to black
375
protected GeneralPath JavaDoc path = new GeneralPath JavaDoc();
376         protected boolean started = false;
377         protected NumberFormat JavaDoc keyFormat;
378
379         public Track(String JavaDoc name) {
380             super();
381             this.name = name;
382
383             this.keyFormat = NumberFormat.getNumberInstance();
384             this.keyFormat.setMaximumFractionDigits(2);
385         }
386
387         public Track(String JavaDoc name, Color JavaDoc color) {
388             this(name);
389             this.color = color;
390         }
391
392         public void setPath(GeneralPath JavaDoc path) {
393             this.path = path;
394         }
395
396         public GeneralPath JavaDoc getPath() {
397             return this.path;
398         }
399
400         public void addPoint(Point2D JavaDoc point) {
401             if (path.getCurrentPoint() == null) {
402                 this.path.moveTo((float)point.getX(),(float)point.getY());
403                 this.started = true;
404             } else {
405                 this.path.lineTo((float)point.getX(),(float)point.getY());
406             }
407
408         }
409
410         public Color JavaDoc getColor() {
411             return this.color;
412         }
413         public void setColor(Color JavaDoc color) {
414             this.color = color;
415         }
416
417         public String JavaDoc toString() {
418             String JavaDoc value = null;
419             if (this.path.getCurrentPoint() != null) {
420                 double val = this.path.getCurrentPoint().getY();
421
422                 //NumberFormat nf = NumberFormat.getNumberInstance();
423
value = this.keyFormat.format(val);
424             }
425             return this.name + ": " + value;
426         }
427     }
428
429
430     /**
431      * <p>Bounds the graph to the limits of the tracks verticaly providing a
432      * useful scaling. A more intelligent implementation could have minimum
433      * bounds to limit the bouncyness to startup.</p>
434      */

435     public void verticalBound() {
436         Rectangle2D JavaDoc rect = null;
437         Rectangle2D JavaDoc orig = getGraphBounds();
438
439         Iterator JavaDoc trackIterator = this.tracks.values().iterator();
440         while(trackIterator.hasNext()) {
441             Track track = (Track) trackIterator.next();
442
443             GeneralPath JavaDoc path = track.getPath();
444
445             if (rect == null)
446                 rect = path.getBounds2D();
447             else
448                 Rectangle.union(rect,path.getBounds2D(),rect);
449         }
450         Rectangle.union(rect,new Rectangle2D.Double JavaDoc(orig.getX(),0,1,1),rect);
451
452         double border = rect.getHeight() * BORDER_PERCENT;
453
454         setGraphBounds(new Rectangle2D.Double JavaDoc(
455             orig.getMinX(),
456             rect.getMinY()-border,
457             orig.getWidth(),
458             rect.getHeight()+(2d*border)));
459     }
460
461     public void clipOld() {
462         Rectangle2D JavaDoc rect = getGraphBounds();
463
464         //Rectangle2D orig = AffineTransform.getScaleInstance(1.5,1.5).createTransformedShape(getGraphBounds()).getBounds();
465

466         Iterator JavaDoc trackIterator = this.tracks.values().iterator();
467         double[] cs = new double[6];
468         while(trackIterator.hasNext()) {
469             Track track = (Track) trackIterator.next();
470
471             GeneralPath JavaDoc path = track.getPath();
472
473             GeneralPath JavaDoc newPath = new GeneralPath JavaDoc();
474
475             PathIterator JavaDoc pIter = path.getPathIterator(new AffineTransform JavaDoc());
476             while (!pIter.isDone()) {
477
478                 pIter.currentSegment(cs);
479
480                 //Point2D pt = new Point2D.Double(cs[0],cs[1]);
481
if (cs[0] > rect.getMinX()) {
482                     if (newPath.getCurrentPoint() == null)
483                         newPath.moveTo((float)cs[0],(float)cs[1]);
484                     else
485                         newPath.lineTo((float)cs[0],(float)cs[1]);
486                 }
487
488                 /*
489                 System.out.println("Current Segment: " +
490                 cs[0] + ", " +
491                 cs[1] + ", " +
492                 cs[2] + ", " +
493                 cs[3] + ", " +
494                 cs[4] + ", " +
495                 cs[5]);
496                  **/

497                 pIter.next();
498
499             }
500             track.setPath(newPath);
501         }
502
503     }
504
505     /**
506      * <p>Translates the main graph rect by x and y, horizontally and vertically
507      * respectively.</p>
508      */

509     public void translate(double x, double y) {
510
511
512
513         Rectangle2D JavaDoc rect = getGraphBounds();
514         setGraphBounds(
515         new Rectangle2D.Double JavaDoc(rect.getMinX()+x,
516         rect.getMinY()+y,rect.getWidth(),rect.getHeight()));
517     }
518
519     public static void main(String JavaDoc[] args) throws Exception JavaDoc {
520
521         GraphCanvas gc = new GraphCanvas();
522         gc.show();
523
524         JFrame JavaDoc frame = new JFrame JavaDoc("Memory Graph");
525         frame.getContentPane().add(gc);
526         frame.setSize(600,200);
527
528         // TODO: Add window exit listener
529

530         frame.show();
531         gc.repaint();
532         gc.paint((Graphics2D JavaDoc)gc.getGraphics());
533
534
535
536         long start = System.currentTimeMillis();
537
538         gc.addTrack("test", Color.cyan);
539         gc.addTrack("test2", Color.blue);
540         gc.addTrack("test3", Color.red);
541         gc.addTrack("test4", Color.yellow);
542         gc.addTrack("test5", Color.green);
543         gc.addTrack("test6", Color.orange);
544         gc.addTrack("test7", Color.pink);
545
546
547
548         int i=0;
549         while (true) {
550             i++;
551
552             Point2D JavaDoc pt = new Point2D.Float JavaDoc(i,((float)Math.cos(i/20f) + (float)Math.sin(i/40f)) * 3f);
553             gc.addPoint("test",pt);
554
555             Point2D JavaDoc pt2 = new Point2D.Float JavaDoc(i,(float)Math.cos(i/25.0f)*10f);
556             gc.addPoint("test2",pt2);
557
558             Point2D JavaDoc pt3 = new Point2D.Float JavaDoc(i,Math.min((float)Math.cos(Math.sin(i/4f))*13f - (float)Math.cos(i/80f)*20f,400f));
559             gc.addPoint("test3",pt3);
560
561             Point2D JavaDoc pt4 = new Point2D.Float JavaDoc(i,
562                 (float) Math.sin(.31*i)*2f +
563                 ((float)2f*(float)Math.cos(.07f*i))*8f);
564             gc.addPoint("test4",pt4);
565
566             Point2D JavaDoc pt5 = new Point2D.Float JavaDoc(i,
567                 (float) Math.cos(.66*i)*1f +
568                 ((float)2f*(float)Math.cos(.07f*i))*3f);
569             gc.addPoint("test5",pt5);
570
571             Point2D JavaDoc pt6 = new Point2D.Float JavaDoc(i,
572                 (float) Math.sin(.31*i)*2f +
573                 ((float)2f*(float)Math.cos(.07f*Math.tan(i)))*5f);
574             gc.addPoint("test6",pt6);
575
576             Point2D JavaDoc pt7 = new Point2D.Float JavaDoc(i,
577                 (float) Math.sin(i)*2f +
578                 ((float)2f*(float)Math.sin(.25f*i))*0.5f);
579             gc.addPoint("test7",pt7);
580
581
582             if (i > 150)
583                 gc.translate(1,0);
584
585             gc.verticalBound();
586
587             //if(i%100 == 0) {
588
gc.clipOld();
589             //}
590
gc.repaint();
591
592             Thread.sleep(10);
593             if (i % 100 == 0) {
594                 System.out.println("Framewrate: " +
595                     (100d / ((System.currentTimeMillis()-start)/1000d)));
596                 start = System.currentTimeMillis();
597             }
598         }
599
600     }
601 }
Popular Tags