// TangentLine Applet // Concept by Bill Ziemer // Programming by Brendon Cheves & Bill Ziemer // e-mail: brendon@csulb.edu wziemer@csulb.edu // Copyright 1996 by California State University, Long Beach // expr package Copyright 1996 by Darius Bacon // Created 10/22/96 // Last update 10/22/96 // java packages import java.applet.*; import java.awt.*; import java.lang.Math; // our packages import expr.*; // f(x) parser by Darius Bacon import cool_utils.*; // cool utilities we made public class TangentLine extends Applet { static final boolean DEBUG = false; double first, last, xTangent, h, step, secantSlope, realSlope; Color current_color = Color.black; Button helpButton, aboutButton, plusdx, minusdx, okBtn; Label sliderLabel, secantSlopeLabel, realSlopeLabel, errorLabel, stepLabel; CardLayout theCards; Canvas theCanvas; Panel theDrawingPanel, theFeedbackPanel, okBtnPanel, mainCd, aboutCd, helpCd, theAboutPanel, theHelpPanel; Image offscreenImage; Graphics offscreenGraphics, offscreenInstance; MultiLineLabel aboutLabel; Dimension d; // EZGridLayout manager stuff EZGridSettings gridSettings; EZGridLayout EZGL; // Our function class, based on the Java Polygon class ZFunction theFunction; public void init() { resize(640,420); theFunction = new ZFunction(); // create a card layout theCards = new CardLayout(10,10); this.setLayout(theCards); // create the main card mainCd = new Panel(); this.add("main",mainCd); mainCd.setLayout(new BorderLayout(10,10)); // create the about card aboutCd = new Panel(); this.add("about",aboutCd); aboutCd.setLayout(new BorderLayout(10,10)); // create the help card helpCd = new Panel(); this.add("help",helpCd); helpCd.setLayout(new BorderLayout(10,10)); // create the drawing panel and add it to the main cd theDrawingPanel = new Panel(); mainCd.add("Center",theDrawingPanel); theDrawingPanel.setLayout(new BorderLayout(10,10)); // create the EZGridLayout EZGL = new EZGridLayout(12,2); // create the feedback panel and add it to the main cd theFeedbackPanel = new Panel(); mainCd.add("South",theFeedbackPanel); theFeedbackPanel.setLayout(EZGL); minusdx = new Button("-"); gridSettings = new EZGridSettings(minusdx,1,1,1,1); theFeedbackPanel.add(minusdx); EZGL.addLayoutInfo(gridSettings); sliderLabel = new Label(); sliderLabel.setText("h = " + String.valueOf(h - ((1E3 * h) % 1) / 1E3)); gridSettings = new EZGridSettings(sliderLabel,3,1,2,1); theFeedbackPanel.add(sliderLabel); EZGL.addLayoutInfo(gridSettings); plusdx = new Button("+"); gridSettings = new EZGridSettings(plusdx,2,1,1,1); theFeedbackPanel.add(plusdx); EZGL.addLayoutInfo(gridSettings); //stepLabel = new Label(); //gridSettings = new EZGridSettings(stepLabel,4,1,2,1); //theFeedbackPanel.add(stepLabel); //EZGL.addLayoutInfo(gridSettings); secantSlopeLabel = new Label(); gridSettings = new EZGridSettings(secantSlopeLabel,7,1,3,1); theFeedbackPanel.add(secantSlopeLabel); EZGL.addLayoutInfo(gridSettings); realSlopeLabel = new Label(); gridSettings = new EZGridSettings(realSlopeLabel,10,1,3,1); theFeedbackPanel.add(realSlopeLabel); EZGL.addLayoutInfo(gridSettings); errorLabel = new Label(); gridSettings = new EZGridSettings(errorLabel,7,2,3,1); theFeedbackPanel.add(errorLabel); EZGL.addLayoutInfo(gridSettings); helpButton = new Button("Help"); gridSettings = new EZGridSettings(helpButton,1,2,1,1); theFeedbackPanel.add(helpButton); EZGL.addLayoutInfo(gridSettings); aboutButton = new Button("About..."); gridSettings = new EZGridSettings(aboutButton,2,2,1,1); theFeedbackPanel.add(aboutButton); EZGL.addLayoutInfo(gridSettings); theFeedbackPanel.resize(preferredSize()); // create the about panel and add it to the about cd theAboutPanel = new Panel(); aboutCd.add("Center",theAboutPanel); theAboutPanel.setLayout(new FlowLayout()); // create the OK button panel and add it to the about cd okBtnPanel = new Panel(); aboutCd.add("South",okBtnPanel); // add the stuff to the about panel Font theFont = new Font("TimesRoman", Font.BOLD, 18); aboutLabel = new MultiLineLabel( "Riemann Sum Applet\nCopyright 1996 by California State University, Long Beach\nWritten by Bill Ziemer and Brendon Cheves\nFunction Parsing Copyright 1996 by Darius Bacon", 10, 10, MultiLineLabel.CENTER); aboutLabel.setFont(theFont); theAboutPanel.add(aboutLabel); // add the OK button okBtn = new Button("OK"); okBtnPanel.add(okBtn); // initialize the offscreen stuff d = theDrawingPanel.size(); offscreenImage = this.createImage(600,500); offscreenGraphics = offscreenImage.getGraphics(); if (offscreenGraphics == null) System.out.println("Error: Null offscreen graphics"); // set up the initial screen drawFunction("(1-x^2) / 2", "0.2", "0.05"); // finally, show the main card theCards.show(this,"main"); } public void paint(Graphics g) { // who cares about g, we know where we want to draw offscreenGraphics.clearRect(0,0,600,500); offscreenGraphics.setColor(Color.white); offscreenGraphics.fillRect(0,0,600,500); theFunction.plot(offscreenGraphics); drawSecant(offscreenGraphics); drawAxis(offscreenGraphics); drawLimits(offscreenGraphics); drawLabels(offscreenGraphics); // force the offscreen buffer to draw into the drawing panel theDrawingPanel.getGraphics().drawImage(offscreenImage,0,0,theDrawingPanel); } // Override update() for double buffering public void update(Graphics g) { paint(g); } public Insets insets() { return new Insets(10,10,10,10); } // handle GUI events public boolean action(Event e, Object arg) { if(e.target == minusdx) { if( -20*step < h) { h -= step; paint(theDrawingPanel.getGraphics()); } return true; } else if(e.target == plusdx) { if( h < 20*step ) { h += step; paint(theDrawingPanel.getGraphics()); } return true; } else if(e.target == aboutButton) { theCards.show(this,"about"); return true; } else if(e.target == okBtn) { theCards.show(this,"main"); return true; } else return super.action(e,arg); } // draw the axis public void drawAxis(Graphics g) { g.setColor(Color.black); // x axis if( (theFunction.yMin < theFunction.yRealToPixel(0)) && (theFunction.yRealToPixel(0) < theFunction.yMax)) { g.drawLine(35,theFunction.yRealToPixel(theFunction.yRealToPixel(0)),580,theFunction.yRealToPixel(theFunction.yRealToPixel(0))); g.drawString("X",585,theFunction.yRealToPixel(theFunction.yRealToPixel(0)) + 4); } else { g.drawLine(0,theFunction.yRealToPixel(theFunction.yMin),580,theFunction.yRealToPixel(theFunction.yMin)); g.drawString("X",585,theFunction.yRealToPixel(theFunction.yMin) + 4); } // y axis g.drawLine(40,20,40,500); g.drawString("Y",38,17); } // draw all the necessary labels public void drawLabels(Graphics g) { g.setColor(Color.black); //stepLabel.setText("h step size =" + String.valueOf(step - ((1E3 * step) % 1) / 1E3)); sliderLabel.setText("h = " + String.valueOf(h - ((1E8 * h) % 1) / 1E8)); if( (h - ((1E8 * h) % 1) / 1E8) != 0.0) { secantSlopeLabel.setText("Secant Slope =" + String.valueOf( secantSlope - ((1E6 * secantSlope) % 1) / 1E6)); errorLabel.setText("Error = " + String.valueOf( realSlope - secantSlope) ); } else { secantSlopeLabel.setText("Secant Slope = UNDEFINED" ); errorLabel.setText("Error = UNDEFINED" ); } realSlopeLabel.setText("Tangent Slope = " + String.valueOf(realSlope - ((1E6 * realSlope) % 1) / 1E6)); } // draw limits in the proper places public void drawLimits(Graphics g) { g.setColor(Color.red); // x limit if( (theFunction.yMin < theFunction.yRealToPixel(0)) && (theFunction.yRealToPixel(0) < theFunction.yMax)) { g.drawString(String.valueOf(last),theFunction.xRealToPixel(last),theFunction.yRealToPixel(theFunction.yRealToPixel(0)) + 14); g.drawLine(theFunction.xRealToPixel(last),theFunction.yRealToPixel(theFunction.yRealToPixel(0)) - 2,theFunction.xRealToPixel(last),theFunction.yRealToPixel(theFunction.yRealToPixel(0)) + 2); g.drawString(String.valueOf(xTangent), theFunction.xRealToPixel(xTangent), theFunction.yRealToPixel(theFunction.yRealToPixel(0)) + 14); g.drawLine(theFunction.xRealToPixel(xTangent),theFunction.yRealToPixel(theFunction.yRealToPixel(0)) - 2,theFunction.xRealToPixel(xTangent),theFunction.yRealToPixel(theFunction.yRealToPixel(0)) + 2); g.drawString(String.valueOf(xTangent+h), theFunction.xRealToPixel(xTangent+h), theFunction.yRealToPixel(theFunction.yRealToPixel(0)) + 28); g.drawLine(theFunction.xRealToPixel(xTangent+h),theFunction.yRealToPixel(theFunction.yRealToPixel(0)) - 2,theFunction.xRealToPixel(xTangent+h),theFunction.yRealToPixel(theFunction.yRealToPixel(0)) + 2); } else { g.drawString(String.valueOf(last),theFunction.xRealToPixel(last),theFunction.yRealToPixel(theFunction.yMin) + 14); g.drawLine(theFunction.xRealToPixel(last),theFunction.yRealToPixel(theFunction.yMin) - 2,theFunction.xRealToPixel(last),theFunction.yRealToPixel(theFunction.yMin) + 2); g.drawString(String.valueOf(xTangent), theFunction.xRealToPixel(xTangent), theFunction.yRealToPixel(theFunction.yMin) + 14); g.drawLine(theFunction.xRealToPixel(xTangent),theFunction.yRealToPixel(theFunction.yMin) - 2,theFunction.xRealToPixel(xTangent),theFunction.yRealToPixel(theFunction.yMin) + 2); g.drawString(String.valueOf(xTangent+h), theFunction.xRealToPixel(xTangent+h), theFunction.yRealToPixel(theFunction.yMin) + 28); g.drawLine(theFunction.xRealToPixel(xTangent+h),theFunction.yRealToPixel(theFunction.yMin) - 2,theFunction.xRealToPixel(xTangent+h),theFunction.yRealToPixel(theFunction.yMin) + 2); } // y limit theFunction.xvar.set_value(last); g.drawString(String.valueOf(theFunction.expr.value() - ((1E2 * theFunction.expr.value()) %1) / 1E2),5,theFunction.yRealToPixel(theFunction.expr.value()) + 3); g.drawLine(38,theFunction.yRealToPixel(theFunction.expr.value()),42,theFunction.yRealToPixel(theFunction.expr.value())); theFunction.xvar.set_value(xTangent); g.drawString(String.valueOf(theFunction.expr.value() - ((1E2 * theFunction.expr.value()) %1) / 1E2),5,theFunction.yRealToPixel(theFunction.expr.value()) + 3); g.drawLine(38,theFunction.yRealToPixel(theFunction.expr.value()),42,theFunction.yRealToPixel(theFunction.expr.value())); theFunction.xvar.set_value(xTangent+h); g.drawString(String.valueOf(theFunction.expr.value() - ((1E2 * theFunction.expr.value()) %1) / 1E2),5,theFunction.yRealToPixel(theFunction.expr.value()) + 3); g.drawLine(38,theFunction.yRealToPixel(theFunction.expr.value()),42,theFunction.yRealToPixel(theFunction.expr.value())); } public void drawSecant(Graphics g) { int x1,x2,x3,x4,y1,y2,y3,y4; x1 = theFunction.xRealToPixel(xTangent); y1 = theFunction.yRealToPixel(theFunction.value(xTangent)); if( (h - ((1E8 * h) % 1) / 1E8) != 0.0) { g.setColor(Color.darkGray); secantSlope = (theFunction.value(xTangent+h)-theFunction.value(xTangent))/h; x2 = theFunction.xRealToPixel(xTangent+h); y2 = theFunction.yRealToPixel(theFunction.value(xTangent+h)); if(h > 0) { x3 = theFunction.xRealToPixel(last); y3 = theFunction.yRealToPixel( secantSlope*(last-xTangent)+theFunction.value(xTangent)); x4 = theFunction.xRealToPixel(first); y4 = theFunction.yRealToPixel( secantSlope*(first-xTangent)+theFunction.value(xTangent)); } else { x3 = theFunction.xRealToPixel(first); y3 = theFunction.yRealToPixel( secantSlope*(first-xTangent)+theFunction.value(xTangent)); x4 = theFunction.xRealToPixel(last); y4 = theFunction.yRealToPixel( secantSlope*(last-xTangent)+theFunction.value(xTangent)); } g.drawLine( x1, y1, x2, y2); g.drawLine( x2, y2, x3, y3); g.drawLine( x1, y1, x4, y4); } //draw the tangent line g.setColor(Color.red); drawDashedLine(x1, y1, theFunction.xRealToPixel(first), theFunction.yRealToPixel(realSlope*(first-xTangent)+theFunction.value(xTangent)),g); drawDashedLine(x1, y1, theFunction.xRealToPixel(last), theFunction.yRealToPixel(realSlope*(last-xTangent)+theFunction.value(xTangent)),g); } // Return info for an about box browser option public String getAppletInfo() { return "Fixed Point Java Applet by Bill Ziemer & Brendon Cheves"; } // Called from JavaScript only public void drawFunction(String f, String g, String s) { theFunction.parse(f); Double gWrap = new Double(g); Double sWrap = new Double(s); xTangent = gWrap.doubleValue(); step = sWrap.doubleValue(); theFunction.establishScale(xTangent-20*step,xTangent+20*step); first = theFunction.xMin; last = theFunction.xMax; h = 12*step; realSlope = (theFunction.value(xTangent+1E-5)-theFunction.value(xTangent-1E-5))/2E-5; //paint(theDrawingPanel.getGraphics()); repaint(); } // Unitl we can get CoolGraphics.class working, got to put these cool utilities here public void drawArrowheadLine(int x1, int y1, int x2, int y2, Graphics g) { int a = 0; int arrowW = 26, arrowH = 26; int arrowX, arrowY; // draw the line first g.drawLine(x1, y1, x2, y2); // calculate arrowhead locations arrowX = x2 - (arrowW / 2); arrowY = y2 - (arrowH / 2); if(x1 == x2) { if (y2 > y1) { a = 270; } else { a = 90; } } else { a = (int)Math.atan((double)(y2 - y1) / (double)(x2 - x1)); // -90 to 90 if(x2 < x1) { a = 180 - a; } } // draw the arrowhead g.fillArc(arrowX, arrowY, arrowW, arrowH, a + 165, 30); } public void drawDashedLine(int x1, int y1, int x2, int y2, int dashLength, Graphics g) { dashedLine(x1, y1, x2, y2, dashLength,g); } public void drawDashedLine(int x1, int y1, int x2, int y2, Graphics g) { dashedLine(x1, y1, x2, y2, 4,g); } private void dashedLine(int x1, int y1, int x2, int y2, int dashLength, Graphics g) { float m; int i, tmp; if(x2 < x1) { tmp = x1; x1 = x2; x2 = tmp; tmp = y1; y1 = y2; y2 = tmp; } if(x2 != x1) { m = (float)(y2 - y1) / (float)(x2 - x1); for(i = x1; i < x2; i += (2 * dashLength)) { g.drawLine( i, (int)((m * (i - x1)) + y1), (int)(i + Math.sqrt(dashLength / (1 + (m*m)))), (int)((m * ((i + Math.sqrt(dashLength / (1 + (m*m)))) - x1)) + y1)); } // guarantee last dash if( (i * 2 * dashLength + x1 < x2)) { g.drawLine( i * 2 * dashLength + x1, (int)((m * (i * 2 * dashLength)) + y1), x2, y2); } } else // vertical { if(y2 < y1) { tmp = y1; y1 = y2; y2 = tmp; } for(i = y1; i < y2; i += (2 * dashLength)) { g.drawLine( x1, i, x1, i + dashLength); } // guarantee last dash if( (i * 2 * dashLength + y1 < y2)) { g.drawLine( x1, i * 2 * dashLength + y1, x1, y2); } } } }