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 java.awt.*;
026: import edu.hws.jcm.data.*;
027: import edu.hws.jcm.awt.*;
028:
029: /**
030: * A RiemannSumRects calculates a Riemann sum for a function. It implements
031: * Computable and InputObject. You can specify and change the number of
032: * intervals in the sum, as well as the method used to calculate the sum.
033: * Functions exist to return Value objects for the sum using different
034: * computations. This class was written by Gabriel Weinstock, with some
035: * modifications by David Eck
036: */
037: public class RiemannSumRects extends Drawable implements Computable {
038:
039: private double[] rectHeights;
040: private int method;
041: private Color color = new Color(255, 255, 180);
042: private Color outlineColor = new Color(180, 180, 0);
043: private double[] endpointVals, maxVals, minVals, midpointVals;
044: private Value intervalCount;
045: private Function func, deriv; // derivative is used in max/min computations
046:
047: // store sum data here:
048: private double[] sum;
049: private double[] param = new double[1];
050: private boolean changed = true;
051:
052: /**
053: * Summation method type.
054: */
055: public static final int LEFTENDPOINT = 0, RIGHTENDPOINT = 1,
056: MIDPOINT = 2, CIRCUMSCRIBED = 3, INSCRIBED = 4,
057: TRAPEZOID = 5;
058:
059: /**
060: * For use in getValueObject(), to indicate whatever summation method is currently set for drawing.
061: */
062: public static final int CURRENT_METHOD = -1;
063:
064: /**
065: * Get the current color used to draw the rectangles
066: */
067: public Color getColor() {
068: return color;
069: }
070:
071: /**
072: * Set the color used to draw the rectangles. The default color is a light yellow.
073: */
074: public void setColor(Color c) {
075: if (c != null) {
076: color = c;
077: needsRedraw();
078: }
079: }
080:
081: /**
082: * Set the color that will be used to draw outlines around the rects. If this is null,
083: * then no outlines are drawn. The default is a medium-dark red that looks brownish next to the default yellow fill color.
084: */
085: public void setOutlineColor(Color c) {
086: outlineColor = c;
087: needsRedraw();
088: }
089:
090: /**
091: * Get the color that is used to draw outlines around the rects. If this is null, then
092: * no outlines are drawn.
093: */
094: public Color getOutlineColor() {
095: return outlineColor;
096: }
097:
098: /**
099: * Set the function whose Riemann sums are to be computed. If null, nothing is drawn.
100: * The function, if non-null, must have arity 1, or an IllegalArgumentException is thrown.
101: */
102: public void setFunction(Function func) {
103: if (func != null && func.getArity() != 1)
104: throw new IllegalArgumentException(
105: "Function for Riemann sums must have arity 1.");
106: this .func = func;
107: deriv = (func == null) ? null : func.derivative(1);
108: changed = true;
109: needsRedraw();
110: }
111:
112: /**
113: * Returns the function whose Riemann sums are computed. Can be null.
114: */
115: public Function getFuction() {
116: return func;
117: }
118:
119: /**
120: * Set the method used to calculate the rectangles.
121: * @param m can be: LEFTENDPOINT, RIGHTENDPOINT, MIDPOINT, CIRCUMSCRIBED,
122: * INSCRIBED or TRAPEZOID (these are integers ranging from 0 to 5,
123: * respectively)
124: */
125: public void setMethod(int m) {
126: method = m;
127: changed = true;
128: needsRedraw();
129: }
130:
131: /**
132: * Return the current method used to find the rectangle sums
133: */
134: public int getMethod() {
135: return method;
136: }
137:
138: /**
139: * This is generally called by a Controller. Indicates that all data should be recomputed
140: * because input values that the data depends on might have changed.
141: */
142: public void compute() {
143: changed = true;
144: needsRedraw();
145: }
146:
147: /**
148: * Get the number of intervals used.
149: * @return a Value object representing the number of intervals
150: */
151: public Value getIntervalCount() {
152: return intervalCount;
153: }
154:
155: /**
156: * Set the interval count (the RiemannSumRects will be redrawn after this function
157: * is called). The value will be clamped to be a value between 1 and 5000.
158: * If the value is null, the default number of intervals, five, is used.
159: * @param c a Value object representing the interval count
160: */
161: public void setIntervalCount(Value c) {
162: changed = true;
163: intervalCount = c;
164: needsRedraw();
165: }
166:
167: /**
168: * Construct a RiemannSumRects object that initially has nothing to draw and that
169: * is set up to use the default number of intervals, 5.
170: */
171: public RiemannSumRects() {
172: this (null, null);
173: }
174:
175: /**
176: * Construct a new RiemannSumRects object.
177: * @param i a Value object representing the number of intervals. If null, five intervals are used.
178: * @param f a Function object used to derive the Riemann sum. If null, nothing is drawn.
179: */
180: public RiemannSumRects(Function f, Value i) {
181: intervalCount = i;
182: func = f;
183: if (f != null)
184: deriv = func.derivative(1);
185: sum = new double[6];
186: method = LEFTENDPOINT;
187: }
188:
189: /**
190: * Draw the Rieman sum rects. This is generally called by an object of class CoordinateRect
191: */
192: public void draw(Graphics g, boolean coordsChanged) {
193: if (func == null || coords == null)
194: return;
195: if (changed || rectHeights == null || coordsChanged)
196: setSumData();
197: int intervals = ((method == 5 || method == 0 || method == 1) ? (rectHeights.length - 1)
198: : rectHeights.length);
199: double x = coords.getXmin();
200: double dx = (coords.getXmax() - x) / intervals;
201: int zero = coords.yToPixel(0);
202: g.setColor(color);
203: if (method == 5) // trapezoids
204: {
205: int[] xp = new int[4];
206: int[] yp = new int[4];
207: xp[1] = coords.xToPixel(x);
208: yp[0] = yp[1] = zero;
209: yp[2] = coords.yToPixel(rectHeights[0]);
210: for (int i = 0; i < intervals; i++) {
211: x += dx;
212: xp[0] = xp[3] = xp[1];
213: xp[1] = xp[2] = coords.xToPixel(x);
214: yp[3] = yp[2];
215: yp[2] = coords.yToPixel(rectHeights[i + 1]);
216: g.fillPolygon(xp, yp, 4);
217: if (outlineColor != null) {
218: g.setColor(outlineColor);
219: g.drawPolygon(xp, yp, 4);
220: g.setColor(color);
221: }
222: }
223: } else {
224: int left = coords.xToPixel(x);
225: for (int i = 0; i < intervals; i++) {
226: int right = coords.xToPixel(x + dx);
227: int width = right - left + 1;
228: int top = coords
229: .yToPixel(rectHeights[(method == 1) ? i + 1 : i]);
230: int height = zero - top;
231: if (height > 0)
232: g.fillRect(left, top, width, height);
233: else if (height == 0)
234: g.drawLine(left, zero, left + width - 1, zero);
235: else
236: g.fillRect(left, zero, width, -height);
237: if (outlineColor != null) {
238: g.setColor(outlineColor);
239: if (height > 0)
240: g.drawRect(left, top, width, height);
241: else if (height == 0)
242: g.drawLine(left, zero, left + width - 1, zero);
243: else
244: g.drawRect(left, zero, width, -height);
245: g.setColor(color);
246: }
247: x += dx;
248: left = right;
249: }
250: }
251: }
252:
253: private void setSumData() {
254: // Recompute all data.
255: changed = false;
256: double intCtD = (intervalCount == null) ? 5 : (intervalCount
257: .getVal() + 0.5);
258: if (Double.isNaN(intCtD) || Double.isInfinite(intCtD))
259: intCtD = 5;
260: else if (intCtD < 0)
261: intCtD = 1;
262: else if (intCtD > 5000)
263: intCtD = 5000;
264: int intCt = (int) intCtD;
265: endpointVals = new double[intCt + 1];
266: maxVals = new double[intCt];
267: minVals = new double[intCt];
268: midpointVals = new double[intCt];
269: double x = coords.getXmin();
270: double dx = (coords.getXmax() - x) / intCt;
271: param[0] = x;
272: endpointVals[0] = func.getVal(param);
273:
274: int ptsPerInterval = 200 / intCt;
275: double smalldx;
276: if (ptsPerInterval < 1) {
277: ptsPerInterval = 1;
278: smalldx = dx;
279: } else
280: smalldx = dx / ptsPerInterval;
281:
282: boolean increasingleft;
283: boolean increasingright = deriv.getVal(param) > 0;
284:
285: for (int i = 1; i <= intCt; i++) {
286: x += dx;
287: param[0] = x;
288: endpointVals[i] = func.getVal(param);
289: param[0] = x - dx / 2;
290: midpointVals[i - 1] = func.getVal(param);
291:
292: // maxmin stuff
293: double max, min;
294: max = min = endpointVals[i - 1];
295: for (int j = 1; j <= ptsPerInterval; j++) // looking for turning points in the interval
296: {
297: increasingleft = increasingright;
298: double xright = (x - dx) + j * smalldx;
299: param[0] = xright;
300: increasingright = deriv.getVal(param) > 0;
301: if (increasingleft != increasingright) {
302: if (increasingleft) {
303: double z = searchMax(xright - smalldx, xright,
304: 1);
305: if (z > max)
306: max = z;
307: } else {
308: double z = searchMin(xright - smalldx, xright,
309: 1);
310: if (z < min)
311: min = z;
312: }
313: }
314: }
315: if (endpointVals[i] > max)
316: max = endpointVals[i];
317: else if (endpointVals[i] < min)
318: min = endpointVals[i];
319: minVals[i - 1] = min;
320: maxVals[i - 1] = max;
321: }
322:
323: double y = endpointVals[0];
324:
325: double leftsum = 0, midpointsum = 0, rightsum = 0, maxsum = 0, minsum = 0;
326: for (int i = 0; i < intCt; i++) {
327: leftsum += endpointVals[i];
328: midpointsum += midpointVals[i];
329: maxsum += maxVals[i];
330: minsum += minVals[i];
331: }
332: rightsum = leftsum - endpointVals[0] + endpointVals[intCt];
333:
334: // calculate sums
335: sum[LEFTENDPOINT] = leftsum * dx;
336: sum[RIGHTENDPOINT] = rightsum * dx;
337: sum[MIDPOINT] = midpointsum * dx;
338: sum[CIRCUMSCRIBED] = maxsum * dx;
339: sum[INSCRIBED] = minsum * dx;
340: sum[TRAPEZOID] = (leftsum + rightsum) / 2 * dx;
341:
342: setRectData();
343: }
344:
345: private void setRectData() {
346: if (method == 3)
347: setRectHeights(maxVals);
348: else if (method == 4)
349: setRectHeights(minVals);
350: else if (method == 2)
351: setRectHeights(midpointVals);
352: else
353: setRectHeights(endpointVals);
354: }
355:
356: private void setRectHeights(double[] e) {
357: rectHeights = e;
358: changed = true;
359: }
360:
361: private double searchMin(double x1, double x2, int depth) {
362: // find an approximate minimum of func in the interval (x1,x2)
363: double mid = (x1 + x2) / 2;
364: param[0] = mid;
365: if (depth >= 13)
366: return func.getVal(param);
367: double slope = deriv.getVal(param);
368: if (slope < 0)
369: return searchMin(mid, x2, depth + 1);
370: else
371: return searchMin(x1, mid, depth + 1);
372: }
373:
374: private double searchMax(double x1, double x2, int depth) {
375: // find an approximate maximum of func in the interval (x1,x2)
376: double mid = (x1 + x2) / 2;
377: param[0] = mid;
378: if (depth >= 13)
379: return func.getVal(param);
380: double slope = deriv.getVal(param);
381: if (slope > 0)
382: return searchMin(mid, x2, depth + 1);
383: else
384: return searchMin(x1, mid, depth + 1);
385: }
386:
387: /**
388: * Gets a Value object that gives the value of the Riemann sum for the specified method.
389: * @return a Value object representing the sum for the given method
390: * @param which integer stating the method used to derive the sum; one of the
391: * constants LEFTENDPOINT, RIGHTENDPOINT, MIDPOINT,
392: * CIRCUMSCRIBED, INSCRIBED, TRAPEZOID, or CURRENT_METHOD.
393: */
394: public Value getValueObject(final int which) {
395: return new Value() {
396: public double getVal() {
397: if (func == null || coords == null)
398: return Double.NaN;
399: if (changed)
400: setSumData();
401: if (which == CURRENT_METHOD)
402: return sum[method];
403: else
404: return sum[which];
405: }
406: };
407: }
408:
409: } // end class RiemannSumRects
|