001: /*************************************************************************
002: * *
003: * 1) This source code file, in unmodified form, and compiled classes *
004: * derived from it can be used and distributed without restriction, *
005: * including for commercial use. (Attribution is not required *
006: * but is appreciated.) *
007: * *
008: * 2) Modified versions of this file can be made and distributed *
009: * provided: the modified versions are put into a Java package *
010: * different from the original package, edu.hws; modified *
011: * versions are distributed under the same terms as the original; *
012: * and the modifications are documented in comments. (Modification *
013: * here does not include simply making subclasses that belong to *
014: * a package other than edu.hws, which can be done without any *
015: * restriction.) *
016: * *
017: * David J. Eck *
018: * Department of Mathematics and Computer Science *
019: * Hobart and William Smith Colleges *
020: * Geneva, New York 14456, USA *
021: * Email: eck@hws.edu WWW: http://math.hws.edu/eck/ *
022: * *
023: *************************************************************************/package edu.hws.jcm.functions;
024:
025: import java.awt.*;
026: import java.awt.event.*;
027: import edu.hws.jcm.data.*;
028: import edu.hws.jcm.awt.*;
029: import edu.hws.jcm.draw.*;
030:
031: /**
032: * A TableFunctionGraph is a Drawable object that can be added to a CoordinateRect (or DisplayCanvas).
033: * It draws the graph of a specified TableFunction.
034: * A TableFunction is a function and can also be graphed by an object of the class edu.hws.jcm.draw.Graph1D.
035: * However, a TableFunctionGraph offers the option of showing the points from the table that defines
036: * the function as small disks (true by default) and the option of making the graph "interactive" so
037: * that the user can drag the points (false by default).
038: */
039: public class TableFunctionGraph extends Drawable implements
040: MouseListener, MouseMotionListener {
041:
042: private TableFunction function; // The function that is drawn.
043: private boolean showPoints; // Are the points from the table shown as little disks?
044: private boolean interactive; // Can user drag points?
045: private Computable onDrag; // If interactive is true and onDrag is not null, then this
046: // Computable's compute() method is called each time a
047: // point moves as the user drags it.
048: private Computable onFinishDrag; // If interactive is true and onFinishDraw is not null, then this
049: // Computable's compute() method is called when the user
050: // finishes a drag operation (if the point is actually moved).
051: private Color color; // non-null color of the graph.
052:
053: /**
054: * Create a TableFunctionGraph that initially draws no function. A function can be set
055: * later with setFunction.
056: */
057: public TableFunctionGraph() {
058: this (null);
059: }
060:
061: /**
062: * Create a TableFunctionGraph to draw the specified TableFunction.
063: */
064: public TableFunctionGraph(TableFunction function) {
065: this .function = function;
066: this .color = Color.magenta;
067: showPoints = true;
068: }
069:
070: /**
071: * Set the function whose graph is drawn by this TableFunctionGraph. If the value is null,
072: * nothing is drawn
073: */
074: public void setFunction(TableFunction function) {
075: this .function = function;
076: needsRedraw();
077: }
078:
079: /**
080: * Get the TableFunction whose graph is drawn by this TableFunctionGraph. If the value is null,
081: * then no graph is drawn.
082: */
083: public TableFunction getFunction() {
084: return function;
085: }
086:
087: /**
088: * Specify a controller whose compute() method will be called repeatedly
089: * as the user drags one of the points from the table function. This only
090: * applies if the "interactive" property is true.
091: */
092: public void setOnDrag(Computable c) {
093: onDrag = c;
094: }
095:
096: /**
097: * Get the Computable that is notified as the user drags a point.
098: */
099: public Computable getOnDrag() {
100: return onDrag;
101: }
102:
103: /**
104: * Specify a controller whose compute() method will be called once
105: * when the user finishes dragging one of the points from the table function.
106: * This only applies if the "interactive" property is true.
107: */
108: public void setOnFinishDrag(Computable c) {
109: onFinishDrag = c;
110: }
111:
112: /**
113: * Get the Computable that is notified when the user finishes dragging a point.
114: */
115: public Computable getOnFinishDrag() {
116: return onFinishDrag;
117: }
118:
119: /**
120: * Set the value of the interactive property, which is true if the user can
121: * modify the function by dragging the points from the table. The default is false.
122: */
123: public void setInteractive(boolean interactive) {
124: if (this .interactive == interactive)
125: return;
126: if (this .interactive && canvas != null) {
127: canvas.removeMouseListener(this );
128: canvas.removeMouseMotionListener(this );
129: }
130: this .interactive = interactive;
131: if (this .interactive && canvas != null) {
132: canvas.addMouseListener(this );
133: canvas.addMouseMotionListener(this );
134: }
135: }
136:
137: /**
138: * Get the value of the interactive property, which is true if the user can
139: * modify the function by dragging the points from the table.
140: */
141: public boolean getInteractive() {
142: return interactive;
143: }
144:
145: /**
146: * Set the showPoints property, which determines whether the points
147: * from the table that defines the function are visible as little
148: * disks. The default is true;
149: */
150: public void setShowPoints(boolean show) {
151: showPoints = show;
152: needsRedraw();
153: }
154:
155: /**
156: * Get the showPoints property, which determines whether the points
157: * from the table that defines the function are visible as little
158: * disks.
159: */
160: public boolean getShowPoints() {
161: return showPoints;
162: }
163:
164: /**
165: * Set the color that is used for drawing the graph. The defalt is magenta.
166: * If the specified Color value is null, the call to setColor is ignored.
167: */
168: public void setColor(Color c) {
169: if (c != null) {
170: color = c;
171: needsRedraw();
172: }
173: }
174:
175: /**
176: * Get the non-null color that is used for drawing the graph.
177: */
178: public Color getColor() {
179: return color;
180: }
181:
182: /**
183: * Sets the values of member variables canvas and coords. This is
184: * designed to be called only by the CoordinateRect class. This overrides
185: * Drawable.setOwnerData();
186: */
187: protected void setOwnerData(DisplayCanvas canvas,
188: CoordinateRect coords) {
189: if (interactive && this .canvas != null) {
190: canvas.removeMouseListener(this );
191: canvas.removeMouseMotionListener(this );
192: }
193: super .setOwnerData(canvas, coords);
194: if (interactive && this .canvas != null) {
195: canvas.addMouseListener(this );
196: canvas.addMouseMotionListener(this );
197: }
198: }
199:
200: /**
201: * Provided as a convenience. If the function for this TableFunctionGraph is non-null,
202: * its style is set to the specified style, and the graph is redrawn. The parameter
203: * should be one of the constants TableFunction.SMOOTH, TableFunction.PIECEWISE_LINEAR,
204: * TableFunction.STEP, TableFunction.STEP_LEFT, or TableFunction.STEP_RIGHT.
205: */
206: public void setFunctionStyle(int style) {
207: if (function != null && function.getStyle() != style) {
208: function.setStyle(style);
209: needsRedraw();
210: }
211: }
212:
213: /**
214: * Override the draw() method from class Drawable to draw the function.
215: * This is not meant to be called directly.
216: */
217: public void draw(Graphics g, boolean coordsChanged) {
218: if (function == null || coords == null)
219: return;
220: int ct = function.getPointCount();
221: if (ct == 0)
222: return;
223: int startPt; // The index of first point from the table that we have to consider
224: int endPt; // The index of the last point from the table that we have to consider
225: double xmin = coords.pixelToX(coords.getLeft());
226: double xmax = coords.pixelToX(coords.getLeft()
227: + coords.getWidth());
228: if (function.getX(0) > xmax || function.getX(ct - 1) < xmin)
229: return;
230: startPt = 0;
231: while (startPt < ct - 1 && function.getX(startPt + 1) <= xmin)
232: startPt++;
233: endPt = ct - 1;
234: while (endPt > 1 && function.getX(endPt - 1) >= xmax)
235: endPt--;
236: double x, y, a, b; // usually, two consecutive points on curve
237: int xInt, yInt, aInt, bInt; // usually, pixel coordinates for the two points.
238: g.setColor(color);
239: switch (function.getStyle()) {
240: case TableFunction.SMOOTH: {
241: if (endPt > startPt) {
242: x = function.getX(startPt);
243: y = function.getVal(x);
244: xInt = coords.xToPixel(x);
245: yInt = coords.yToPixel(y);
246: double limit = xmax;
247: if (function.getX(endPt) < limit)
248: limit = function.getX(endPt);
249: coords.xToPixel(function.getX(ct - 1));
250: aInt = xInt;
251: while (x < limit) {
252: aInt += 3;
253: a = coords.pixelToX(aInt);
254: if (a > limit)
255: a = limit;
256: b = function.getVal(a);
257: bInt = coords.yToPixel(b);
258: g.drawLine(xInt, yInt, aInt, bInt);
259: x = a;
260: xInt = aInt;
261: yInt = bInt;
262: }
263: }
264: break;
265: }
266: case TableFunction.PIECEWISE_LINEAR: {
267: x = function.getX(startPt);
268: xInt = coords.xToPixel(x);
269: y = function.getY(startPt);
270: yInt = coords.yToPixel(y);
271: for (int i = startPt + 1; i <= endPt; i++) {
272: a = function.getX(i);
273: aInt = coords.xToPixel(a);
274: b = function.getY(i);
275: bInt = coords.yToPixel(b);
276: g.drawLine(xInt, yInt, aInt, bInt);
277: xInt = aInt;
278: yInt = bInt;
279: }
280: break;
281: }
282: case TableFunction.STEP: {
283: x = function.getX(startPt);
284: xInt = coords.xToPixel(x);
285: for (int i = startPt; i <= endPt; i++) {
286: if (i < endPt) {
287: double nextX = function.getX(i + 1);
288: a = (x + nextX) / 2;
289: x = nextX;
290: } else
291: a = x;
292: aInt = coords.xToPixel(a);
293: y = function.getY(i);
294: yInt = coords.yToPixel(y);
295: g.drawLine(xInt, yInt, aInt, yInt);
296: xInt = aInt;
297: }
298: break;
299: }
300: case TableFunction.STEP_LEFT: {
301: x = function.getX(startPt);
302: xInt = coords.xToPixel(x);
303: for (int i = startPt + 1; i <= endPt; i++) {
304: a = function.getX(i);
305: aInt = coords.xToPixel(a);
306: y = function.getY(i - 1);
307: yInt = coords.yToPixel(y);
308: g.drawLine(xInt, yInt, aInt, yInt);
309: xInt = aInt;
310: }
311: break;
312: }
313: case TableFunction.STEP_RIGHT: {
314: x = function.getX(startPt);
315: xInt = coords.xToPixel(x);
316: for (int i = startPt + 1; i <= endPt; i++) {
317: a = function.getX(i);
318: aInt = coords.xToPixel(a);
319: y = function.getY(i);
320: yInt = coords.yToPixel(y);
321: g.drawLine(xInt, yInt, aInt, yInt);
322: xInt = aInt;
323: }
324: break;
325: }
326: }
327: if (!showPoints)
328: return;
329: for (int i = startPt; i <= endPt; i++) {
330: x = function.getX(i);
331: y = function.getY(i);
332: xInt = coords.xToPixel(x);
333: yInt = coords.yToPixel(y);
334: g.fillOval(xInt - 2, yInt - 2, 5, 5);
335: }
336: } // end draw();
337:
338: //-------------------- Dragging --------------------------
339:
340: private int dragPoint = -1; // -1 if no point is being dragged;
341: // Otherwise, the index of the point being dragged.
342:
343: private int startX, startY; // Point where mouse was clicked at start of drag.
344:
345: private int prevY; // Previous position of mouse during dragging.
346:
347: private boolean moved; // Becomes true once the clicked point has actually
348:
349: // been dragged a bit. If the mouse is released before
350: // the point is moved at least 3 pixels, then the associated
351: // y-value is not changed.
352:
353: /**
354: * Method required by the MouseListener interface. Defined here to
355: * support dragging of points on the function's graph. Not meant to be called directly.
356: */
357: public void mousePressed(MouseEvent evt) {
358: dragPoint = -1;
359: if (function == null || getVisible() == false || canvas == null
360: || coords == null || evt.isConsumed())
361: return;
362: if (evt.isShiftDown() || evt.isMetaDown()
363: || evt.isControlDown() || evt.isAltDown())
364: return;
365: moved = false;
366: int ct = function.getPointCount();
367: for (int i = 0; i < ct; i++) {
368: int x = coords.xToPixel(function.getX(i));
369: int y = coords.yToPixel(function.getY(i));
370: if (evt.getX() >= x - 3 && evt.getX() <= x + 3
371: && evt.getY() >= y - 3 && evt.getY() <= y + 3) {
372: startX = evt.getX();
373: prevY = startY = evt.getY();
374: dragPoint = i;
375: evt.consume();
376: return;
377: }
378: }
379: }
380:
381: /**
382: * Method required by the MouseListener interface. Defined here to
383: * support dragging of points on the function's graph. Not meant to be called directly.
384: */
385: public void mouseReleased(MouseEvent evt) {
386: if (dragPoint == -1)
387: return;
388: evt.consume();
389: if (!moved) {
390: dragPoint = -1;
391: return;
392: }
393: mouseDragged(evt);
394: dragPoint = -1;
395: if (onFinishDrag != null)
396: onFinishDrag.compute();
397: }
398:
399: /**
400: * Method required by the MouseListener interface. Defined here to
401: * support dragging of points on the function's graph. Not meant to be called directly.
402: */
403: public void mouseDragged(MouseEvent evt) {
404: if (dragPoint == -1 || prevY == evt.getY())
405: return;
406: evt.consume();
407: if (!moved && Math.abs(evt.getY() - startY) < 3)
408: return;
409: moved = true;
410: int y = evt.getY();
411: if (y < coords.getTop() + 4)
412: y = coords.getTop() + 4;
413: else if (y > coords.getTop() + coords.getHeight() - 4)
414: y = coords.getTop() + coords.getHeight() - 4;
415: if (Math.abs(evt.getX() - startX) > 72)
416: y = startY;
417: if (y == prevY)
418: return;
419: prevY = y;
420: function.setY(dragPoint, coords.pixelToY(prevY));
421: needsRedraw();
422: if (onDrag != null)
423: onDrag.compute();
424: }
425:
426: /**
427: * Empty method, required by the MouseListener interface.
428: */
429: public void mouseClicked(MouseEvent evt) {
430: }
431:
432: /**
433: * Empty method, required by the MouseMotionListener interface.
434: */
435: public void mouseEntered(MouseEvent evt) {
436: }
437:
438: /**
439: * Empty method, required by the MouseMotionListener interface.
440: */
441: public void mouseExited(MouseEvent evt) {
442: }
443:
444: /**
445: * Empty method, required by the MouseMotionListener interface.
446: */
447: public void mouseMoved(MouseEvent evt) {
448: }
449:
450: } // end class TableFunctionGraph
|