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.draw;
024:
025: import edu.hws.jcm.data.*;
026: import edu.hws.jcm.awt.*;
027: import java.awt.*;
028: import java.util.Vector;
029:
030: /**
031: * A VectorField displays lines or arrows on a grid of points where the direction
032: * and/or lengths are given by two functions (f1(x,y),f2(x,y)). This is probably
033: * more useful as a "direction field" than as a "vector field."
034: *
035: */
036:
037: public class VectorField extends Drawable implements Computable {
038:
039: /**
040: * One of the possible styles for displaying a VectorField: as a direction field shown as
041: * arrows of equal length. The point where the vector is computed is the tail of the arrow.
042: */
043: public static final int ARROWS = 0;
044: /**
045: * One of the possible styles for displaying a VectorField: as a direction field shown as
046: * tangent lines. The point where the vector is computed is the center of the line.
047: */
048: public static final int LINES = 1;
049: /**
050: * One of the possible styles for displaying a VectorField: as a vector field where a vector is shown as
051: * an arrow from (x,y) to (x+xFunc(x,y),y+xFunc(x,y)), except that a maximum length is imposed.
052: */
053: public static final int CLAMPED_VECTORS = 2;
054: /**
055: * One of the possible styles for displaying a VectorField: as a field of tangent lines where the length
056: * of the line is proportional to the length of the vector.
057: */
058: public static final int SCALED_LINES = 3;
059: /**
060: * One of the possible styles for displaying a VectorField: as a vector field where a vector is shown as
061: * an arrow with length proportional to the length of the vector. The lengths are scaled so that
062: * the longest arrow has length equal to the grid spacing.
063: */
064: public static final int SCALED_VECTORS = 4;
065:
066: private int style; // The style in which the vector field is drawn.
067:
068: private Function xFunc, yFunc; // The vector field is (xFunc(x,y),yfunc(x,y)).
069:
070: private Color graphColor = Color.lightGray; //Color of the vectors.
071:
072: private boolean changed; // Used internally to indicate that data has to be recomputed.
073:
074: private transient int[][] data; // Pre-computed data for the vectors.
075:
076: private int pixelSpacing = 30; // Desired number of pixels between grid points, clamped to the range 5 to 200.
077:
078: /**
079: * Create a VectorField object with nothing to graph. The functions and other values
080: * can be set later. The default display style is as a direction field of equal-length arrows.
081: */
082: public VectorField() {
083: this (null, null, ARROWS);
084: }
085:
086: /**
087: * Create a VectorField that will be displayed using the default style, as a direction field of
088: * equal-length arrows. If either of the functions is null, nothing will be displayed. If non-null,
089: * the functions must be functions of two variables.
090: */
091: public VectorField(Function xFunc, Function yFunc) {
092: this (xFunc, yFunc, ARROWS);
093: }
094:
095: /**
096: * Create a VectorField with the specified functions and style.
097: *
098: * @param xFunc A Function of two variables giving the x-component of the vector field. If this
099: * is null, then nothing will be drawn.
100: * @param yFunc A Function of two variables giving the y-component of the vector field. If this
101: * is null, then nothing will be drawn.
102: * @param style The style in which the direction field is drawn. This can be one of the
103: constants ARROWS (a direction field of equal-lenth arrows), LINES (equal length lines),
104: CLAMPED_VECTORS (vectors drawn at actual length, unless too long), SCALED_VECTORS (vectors scaled so longest has
105: length equal to the grid spacing), or SCALED_LINES (lines scaled so longest has length
106: equal to the grid spacing).
107: */
108: public VectorField(Function xFunc, Function yFunc, int style) {
109: if ((xFunc != null && xFunc.getArity() != 2)
110: || (yFunc != null && yFunc.getArity() != 2))
111: throw new IllegalArgumentException(
112: "Internal Error: The functions that define a vector must be functions of two variables.");
113: this .xFunc = xFunc;
114: this .yFunc = yFunc;
115: this .style = style;
116: changed = true;
117: }
118:
119: /**
120: * Set the color to be used for drawing the vector field. The default color is light gray.
121: */
122: public void setColor(Color c) {
123: if (c != null & !c.equals(graphColor)) {
124: graphColor = c;
125: needsRedraw();
126: }
127: }
128:
129: /**
130: * Get the color that is used to draw the vector field.
131: */
132: public Color getColor() {
133: return graphColor;
134: }
135:
136: /**
137: * Sets the functions that give the components of the vector field. If either function is
138: * null, then nothing is drawn. If non-null, each function must be a function of two variables.
139: */
140: synchronized public void setFunctions(Function dx, Function dy) {
141: setXFunction(dx);
142: setYFunction(dy);
143: }
144:
145: /**
146: * Set the function that gives the x-component of the vector field. If this is
147: * null, then nothing is drawn. If non-null, it must be a function of two variables.
148: */
149: synchronized public void setXFunction(Function dx) {
150: if (dx != null && dx.getArity() != 2)
151: throw new IllegalArgumentException(
152: "Internal Error: VectorField can only use functions of two variables.");
153: if (dx != xFunc) {
154: xFunc = dx;
155: changed = true;
156: needsRedraw();
157: }
158: }
159:
160: /**
161: * Set the function that gives the y-component of the vector field. If this is
162: * null, then nothing is drawn. If non-null, it must be a function of two variables.
163: */
164: synchronized public void setYFunction(Function dy) {
165: if (dy != null && dy.getArity() != 1)
166: throw new IllegalArgumentException(
167: "Internal Error: VectorField can only use functions of two variables.");
168: if (dy != yFunc) {
169: yFunc = dy;
170: changed = true;
171: needsRedraw();
172: }
173: }
174:
175: /**
176: * Get the (possibly null) function that gives the x-component of the vector field.
177: */
178: public Function getXFunction() {
179: return xFunc;
180: }
181:
182: /**
183: * Get the (possibly null) function that gives the y-component of the vector field.
184: */
185: public Function getYFunction() {
186: return yFunc;
187: }
188:
189: /**
190: * Get the style in which the vector field is displayed.
191: */
192: public int getStyle() {
193: return style;
194: }
195:
196: /**
197: * Set the style in which the vector field is displayed. This should be one of the
198: * constants ARROWS, LINES, CLAMPED_VECTORS, SCALED_LINES, or SCALED_VECTORS.
199: */
200: public void setStyle(int style) {
201: if (this .style != style) {
202: this .style = style;
203: changed = true;
204: needsRedraw();
205: }
206: }
207:
208: /**
209: * Get the value of the pixelSpacing property, which determines the grid spacing for the vector field.
210: */
211: public int getPixelSpacing() {
212: return pixelSpacing;
213: }
214:
215: /**
216: * Set the value of the pixelSpacing property, which determines the grid spacing for the vector field.
217: * The value will be clamped to the range from 5 to 200. The default value is 30.
218: */
219: public void setPixelSpacing(int spacing) {
220: if (spacing < 5)
221: spacing = 5;
222: else if (spacing > 200)
223: spacing = 200;
224: if (spacing != pixelSpacing) {
225: pixelSpacing = spacing;
226: changed = true;
227: needsRedraw();
228: }
229: }
230:
231: //------------------ Implementation details -----------------------------
232:
233: /**
234: * Recompute data for the vector field and make sure that the area of the display canvas
235: * that shows the vector field is redrawn. This method is ordinarily called by a
236: * Controller.
237: */
238: synchronized public void compute() {
239: setup();
240: needsRedraw();
241: changed = false;
242: }
243:
244: /**
245: * Draw the vector field (possibly recomputing the data if the CoordinateRect has changed).
246: *
247: */
248: synchronized public void draw(Graphics g, boolean coordsChanged) {
249: if (changed || coordsChanged || data == null) {
250: setup();
251: changed = false;
252: }
253: if (data == null)
254: return;
255: g.setColor(graphColor);
256: boolean arrows = style == ARROWS || style == CLAMPED_VECTORS
257: || style == SCALED_VECTORS;
258: for (int i = 0; i < data.length; i++) {
259: int[] c = data[i];
260: if (c[0] != Integer.MIN_VALUE) { // Otherwise, vector is undefined
261: g.drawLine(c[0], c[1], c[2], c[3]);
262: if (arrows && c[4] != Integer.MIN_VALUE) { // Otherwise, there is no arrowhead
263: g.drawLine(c[2], c[3], c[4], c[5]);
264: g.drawLine(c[2], c[3], c[6], c[7]);
265: }
266: }
267: }
268: }
269:
270: // ------------------------- Computing the data for the vector field -----------------------
271:
272: private void setup() {
273: if (xFunc == null || yFunc == null || coords == null) {
274: data = null; // Nothing will be drawn
275: return;
276: }
277: boolean arrows = style == ARROWS || style == CLAMPED_VECTORS
278: || style == SCALED_VECTORS;
279: int xCt, yCt; // number of points in x and y directions.
280: double xStart, yStart; // Starting values for x,y, at lower left corner of grid.
281: double dx, dy; // Change in x and y between grid points.
282: double[] params = new double[2];
283:
284: xCt = (coords.getWidth()) / pixelSpacing + 2;
285: yCt = (coords.getHeight()) / pixelSpacing + 2;
286: dx = pixelSpacing * coords.getPixelWidth();
287: dy = pixelSpacing * coords.getPixelHeight();
288: xStart = (coords.getXmax() + coords.getXmin() - xCt * dx) / 2;
289: yStart = (coords.getYmax() + coords.getYmin() - yCt * dy) / 2;
290:
291: data = new int[xCt * yCt][arrows ? 8 : 4];
292: double[][] xVec = new double[xCt][yCt]; // Vector field scaled so pixelsize is one unit.
293: double[][] yVec = new double[xCt][yCt];
294: double pixelWidth = coords.getPixelWidth();
295: double pixelHeight = coords.getPixelHeight();
296: double maxLength = 0;
297: for (int i = 0; i < xCt; i++) {
298: double x = xStart + i * dx;
299: params[0] = x;
300: for (int j = 0; j < yCt; j++) {
301: double y = yStart + j * dy;
302: params[1] = y;
303: xVec[i][j] = xFunc.getVal(params);
304: yVec[i][j] = yFunc.getVal(params);
305: if (!(Double.isNaN(xVec[i][j])
306: || Double.isNaN(yVec[i][j])
307: || Double.isInfinite(xVec[i][j]) || Double
308: .isInfinite(yVec[i][j]))) {
309: xVec[i][j] = xVec[i][j] / pixelWidth; // size in terms of pixels
310: yVec[i][j] = -yVec[i][j] / pixelHeight; // sign change because pixels are numbered from top down
311: double length = xVec[i][j] * xVec[i][j]
312: + yVec[i][j] * yVec[i][j];
313: if (length > maxLength)
314: maxLength = length;
315: }
316: }
317: }
318: maxLength = Math.sqrt(maxLength);
319:
320: int ct = 0; // which item of data are we working on?
321: for (int i = 0; i < xCt; i++) {
322: double x = xStart + i * dx;
323: int xInt = coords.xToPixel(x);
324: for (int j = 0; j < yCt; j++) {
325: double y = yStart + j * dy;
326: int yInt = coords.yToPixel(y);
327: int[] d = data[ct];
328: ct++;
329: if (Double.isNaN(xVec[i][j])
330: || Double.isNaN(yVec[i][j])
331: || Double.isInfinite(xVec[i][j])
332: || Double.isInfinite(yVec[i][j])) {
333: d[i] = Integer.MIN_VALUE; // signal that vector is undefined at this point
334: } else {
335: double length = Math.sqrt(xVec[i][j] * xVec[i][j]
336: + yVec[i][j] * yVec[i][j]);
337: if (length < 1e-15
338: || (maxLength == 0 && (style == SCALED_LINES || style == SCALED_VECTORS))) { // no arrow.
339: d[0] = d[2] = xInt;
340: d[1] = d[3] = yInt;
341: if (arrows)
342: d[4] = Integer.MIN_VALUE;
343: } else {
344: double sdx, sdy; // dx and dy scaled to a vector of right length
345: double alength; // length of arrow or line
346: boolean clamped = false;
347: switch (style) {
348: case ARROWS:
349: sdx = 0.8 * pixelSpacing * xVec[i][j]
350: / length;
351: sdy = 0.8 * pixelSpacing * yVec[i][j]
352: / length;
353: d[0] = xInt;
354: d[1] = yInt;
355: d[2] = (int) (xInt + sdx);
356: d[3] = (int) (yInt + sdy);
357: break;
358: case LINES:
359: sdx = 0.8 * pixelSpacing * xVec[i][j]
360: / length / 2;
361: sdy = 0.8 * pixelSpacing * yVec[i][j]
362: / length / 2;
363: d[0] = (int) (xInt - sdx);
364: d[1] = (int) (yInt - sdy);
365: d[2] = (int) (xInt + sdx);
366: d[3] = (int) (yInt + sdy);
367: break;
368: case CLAMPED_VECTORS:
369: alength = length;
370: if (alength > 0.9 * pixelSpacing) {
371: alength = 0.9 * pixelSpacing;
372: clamped = true;
373: }
374: sdx = xVec[i][j] / length * alength;
375: sdy = yVec[i][j] / length * alength;
376: d[0] = xInt;
377: d[1] = yInt;
378: d[2] = (int) (xInt + sdx);
379: d[3] = (int) (yInt + sdy);
380: break;
381: case SCALED_LINES:
382: alength = (length / maxLength)
383: * pixelSpacing;
384: sdx = xVec[i][j] / length * alength / 2;
385: sdy = yVec[i][j] / length * alength / 2;
386: d[0] = (int) (xInt - sdx);
387: d[1] = (int) (yInt - sdy);
388: d[2] = (int) (xInt + sdx);
389: d[3] = (int) (yInt + sdy);
390: break;
391: case SCALED_VECTORS:
392: alength = (length / maxLength)
393: * pixelSpacing;
394: sdx = xVec[i][j] / length * alength;
395: sdy = yVec[i][j] / length * alength;
396: d[0] = xInt;
397: d[1] = yInt;
398: d[2] = (int) (xInt + sdx);
399: d[3] = (int) (yInt + sdy);
400: break;
401: }
402: if (arrows) { // add an arrowhead
403: int d1 = (d[2] - d[0]) / 5;
404: int d2 = (d[3] - d[1]) / 5;
405: if (clamped || d1 == 0 && d2 == 0)
406: d[4] = Integer.MIN_VALUE; // no arrowhead
407: else {
408: d[4] = d[2] + d2 - d1;
409: d[5] = d[3] - d1 - d2;
410: d[6] = d[2] - d1 - d2;
411: d[7] = d[3] + d1 - d2;
412: }
413: }
414: }
415: }
416: }
417: }
418:
419: } // end setup()
420:
421: } // end class VectorField
|