//CircleOverlap.java //2 overlapping circles import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.event.*; import javax.swing.border.*; import java.util.*; import java.text.*; public class CircleOverlap extends JApplet implements ActionListener { double r1, r2, d, c, alpha1, alpha2, segment1Area, segment2Area, overlapArea; JPanel controlPanel; JLabel r1Label, r2Label, dLabel, cLabel, segment1AreaLabel, segment2AreaLabel, overlapAreaLabel; JTextField r1TextField, r2TextField, dTextField, cTextField, segment1AreaTextField, segment2AreaTextField, overlapAreaTextField; DecimalFormat d4, d2; //needs to be global so mouseDrag can access them :( int thirdX; int midY; int rLen; double radiiProportion; //smaller/bigger <=1 double dProportion; int r2rLen; int dLen; public void init() { String input; d4 = new DecimalFormat ("0.0000"); d2 = new DecimalFormat ("0.00"); Container container = getContentPane(); container.setLayout( new BorderLayout() ); controlPanel = new JPanel(); controlPanel.setLayout(new FlowLayout()); controlPanel.setBackground(Color.MAGENTA); controlPanel.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.RAISED)); container.add(controlPanel,BorderLayout.NORTH); r1Label = new JLabel( "r1" ); controlPanel.add( r1Label ); r1TextField = new JTextField("1.0",4); r1 = Double.parseDouble(r1TextField.getText()); //rTextField.setBackground(Color.BLUE); //rTextField.setForeground(Color.WHITE); r1TextField.setHorizontalAlignment(JTextField.RIGHT); r1TextField.addActionListener( this ); controlPanel.add( r1TextField ); r2Label = new JLabel( "r2" ); controlPanel.add( r2Label ); r2TextField = new JTextField("0.5",4); r2 = Double.parseDouble(r2TextField.getText()); r2TextField.setHorizontalAlignment(JTextField.RIGHT); r2TextField.addActionListener( this ); controlPanel.add( r2TextField ); dLabel = new JLabel( " C1 C2 distance" ); controlPanel.add( dLabel ); dTextField = new JTextField("1",4); d = Double.parseDouble(dTextField.getText()); dTextField.setBackground(Color.CYAN); dTextField.setHorizontalAlignment(JTextField.RIGHT); dTextField.addActionListener( this ); controlPanel.add( dTextField ); cLabel = new JLabel( "chord" ); controlPanel.add( cLabel ); c = chordLength(r1,r2,d); cTextField = new JTextField(""+d4.format(c),4); cTextField.setBackground(Color.BLUE); cTextField.setForeground(Color.WHITE); cTextField.setHorizontalAlignment(JTextField.RIGHT); cTextField.setEditable(false); cTextField.addActionListener( this ); controlPanel.add( cTextField ); segment1AreaLabel = new JLabel( " AREA: segment1" ); controlPanel.add( segment1AreaLabel ); alpha1 = alphaAngle(commonChordX(r1,r2,d), 0.5*chordLength(r1,r2,d)); segment1Area = sectorArea(r1, alpha1) - triangleArea(r1, alpha1); segment1AreaTextField = new JTextField(""+d4.format(segment1Area),4); segment1AreaTextField.setBackground(Color.PINK); segment1AreaTextField.setHorizontalAlignment(JTextField.RIGHT); segment1AreaTextField.setEditable(false); segment1AreaTextField.addActionListener( this ); controlPanel.add( segment1AreaTextField ); segment2AreaLabel = new JLabel( "+ segment2" ); controlPanel.add( segment2AreaLabel ); alpha2 = alphaAngle(d-commonChordX(r1,r2,d), 0.5*chordLength(r1,r2,d)); segment2Area = sectorArea(r2, alpha2) - triangleArea(r2, alpha2); segment2AreaTextField = new JTextField(""+d4.format(segment2Area),4); segment2AreaTextField.setBackground(Color.YELLOW); segment2AreaTextField.setHorizontalAlignment(JTextField.RIGHT); segment2AreaTextField.setEditable(false); segment2AreaTextField.addActionListener( this ); controlPanel.add( segment2AreaTextField ); overlapAreaLabel = new JLabel( "= overlap" ); controlPanel.add( overlapAreaLabel ); overlapArea = segment1Area + segment2Area; overlapAreaTextField = new JTextField(""+d4.format(overlapArea),4); //overlapAreaTextField.setBackground(Color.YELLOW); overlapAreaTextField.setHorizontalAlignment(JTextField.RIGHT); overlapAreaTextField.setEditable(false); overlapAreaTextField.addActionListener( this ); controlPanel.add( overlapAreaTextField ); //need to put these here so mouseDrag has access to them //w = getWidth(); //h = getHeight(); thirdX = getWidth()/3; midY = getHeight()/2; rLen = getHeight()/3; radiiProportion = r2/r1; //smaller/bigger <=1 dProportion = d/r1; //midOverlapProportion = (r1+d-r2) / 2 /r1; //midway between overlap?? r2rLen = (int)(rLen*radiiProportion); dLen = (int)(rLen*dProportion); //midLen = (int)(rLen*midOverlapProportion); r1TextField.setText("1.0"); r1TextField.postActionEvent(); //fires listener //incantation that responds to mouse move events: addMouseMotionListener( new MouseMotionAdapter() { public void mouseDragged( MouseEvent event) { int c2midX = thirdX+dLen; int c2midY = midY; int c1midX = thirdX; int px = event.getX(); int py = event.getY(); double mouseDistance = distanceFormula(px,py, c2midX,c2midY); if (mouseDistance < r2rLen/2) { //within half-radius of center //showStatus("C ("+px+","+(getHeight()-py)+")"); if (px >= c1midX) { //can't go to the left of r1 circle's center d = (px-c1midX)*r1 / rLen; //dLen=px-c1midX dTextField.setText(""+d4.format(d)); if (dr1-r2) //neither disjoint nor C2 completely inside C1 c = chordLength(r1,r2,d); else c = 0; cTextField.setText(""+d4.format(c)); repaint(); } } else if (mouseDistance>5*r2rLen/6 && mouseDistance<7*r2rLen/6) { //within 1/6 radius of edge //showStatus("E ("+px+","+(getHeight()-py)+")"); double testr2 = mouseDistance*r1 / rLen; //mouseDistance=r2rLen if (testr2 <= r1) { //can't get larger than r1 circle r2 = testr2; r2TextField.setText(""+d4.format(r2)); if (dr1-r2) //neither disjoint nor C2 completely inside C1 c = chordLength(r1,r2,d); else c = 0; cTextField.setText(""+d4.format(c)); repaint(); } } } } ); } /* public void setTextFields(double a, double rad, double r, double c, double h, double arc, double sectorArea, double triangleArea, double segmentArea) { r1TextField.setText(""+d4.format(r)); rTextField.setText(""+d4.format(r)); cTextField.setText(""+d4.format(c)); } */ public void actionPerformed( ActionEvent event ) { try { double r1Try = Double.parseDouble(r1TextField.getText()); if (r1Try<0 ||r1Tryr1) { JOptionPane.showMessageDialog(null, "r2 radius must be greater than 0 and be the smaller circle", "CircleOverlap: Invalid RADIUS input", JOptionPane.ERROR_MESSAGE); return; } double dTry = Double.parseDouble(dTextField.getText()); if (dTry<0) { JOptionPane.showMessageDialog(null, "distance between centers must be >= 0", "CircleOverlap: Invalid DISTANCE input", JOptionPane.ERROR_MESSAGE); return; } double cTry = Double.parseDouble(cTextField.getText()); if (cTry<0 || cTry>2*r2) { JOptionPane.showMessageDialog(null, "chord length must be >0 and <=C2 diameter", "CircleOverlap: Invalid CHORD LENGTH input", JOptionPane.ERROR_MESSAGE); return; } } catch (NumberFormatException e) { JOptionPane.showMessageDialog(null, "all values must be numbers", "CircleOverlap: Invalid input", JOptionPane.ERROR_MESSAGE); return; } if ( event.getSource() == r1TextField ) { r1 = Double.parseDouble(r1TextField.getText()); //setTextFields(a,rad,r,c,h,arc,sectorArea,triangleArea,segmentArea); if (dr1-r2) //neither disjoint nor C2 completely inside C1 c = chordLength(r1,r2,d); else c = 0; cTextField.setText(""+d4.format(c)); repaint(); } else if ( event.getSource() == r2TextField ) { r2 = Double.parseDouble(r2TextField.getText()); if (dr1-r2) //neither disjoint nor C2 completely inside C1 c = chordLength(r1,r2,d); else c = 0; cTextField.setText(""+d4.format(c)); repaint(); } else if ( event.getSource() == dTextField ) { d = Double.parseDouble(dTextField.getText()); if (dr1-r2) //neither disjoint nor C2 completely inside C1 c = chordLength(r1,r2,d); else c = 0; cTextField.setText(""+d4.format(c)); repaint(); } else if ( event.getSource() == cTextField ) { c = Double.parseDouble(cTextField.getText()); //need to change d...if this field to be editable repaint(); } } public void paint (Graphics g) { super.paint( g ); //erase g.drawString("Set the radii or distance fields OR",5,50); g.drawString("Drag center of small right circle left/right to decrease/increase distance",5,70); g.drawString("Drag its edge to decrease/increase its size",5,90); //these are globals: radiiProportion = r2/r1; //smaller/bigger <=1 dProportion = d/r1; r2rLen = (int)(rLen*radiiProportion); dLen = (int)(rLen*dProportion); double commonChordLineX = commonChordX(r1,r2,d); double commonChordProportion = commonChordLineX / r1; int commonChord = (int)(rLen*commonChordProportion); double y1 = Math.sqrt(r1*r1 - commonChordLineX*commonChordLineX); double y2 = -y1; double y1Proportion = y1 / r1; //messy? double y2Proportion = y2 / r1; int y1Point = (int)(rLen*y1Proportion); int y2Point = (int)(rLen*y2Proportion); int c2midX = thirdX+dLen; int c2midY = midY; int c1midX = thirdX; int c1midY = midY; double centroid = d/((Math.PI*r1*r1/(Math.PI*r2*r2))+1); int centroidX = (int)(centroid/r1 * rLen) ; if (d < r1+r2) { //not disjoint g.setColor(Color.YELLOW); for (int py=-r2rLen; py<=r2rLen; py++) //bounding square of r2 circle for (int px=-r2rLen; px<=r2rLen; px++) if (distanceFormula(px+c2midX,py+c2midY, c2midX,c2midY) <= r2rLen) //in the r2 circle if (distanceFormula(px+c2midX,py+c2midY, c1midX,c1midY) <= rLen) { //within r1 of r1 circle center if (d>r1-r2 && px+c2midX>thirdX+commonChord) //not complete overlap AND point is to right of chord g.setColor(Color.PINK); else g.setColor(Color.YELLOW); g.drawLine(px+c2midX,py+c2midY,px+c2midX,py+c2midY); } if (d > r1-r2) { //r2 circle not completely inside r1 g.setColor(Color.BLUE); g.drawLine(thirdX+commonChord,midY-y1Point, thirdX+commonChord,midY-y2Point); g.drawString("("+d4.format(commonChordLineX)+","+d4.format(y1)+")", thirdX+commonChord,midY-y1Point); g.drawString("("+d4.format(commonChordLineX)+","+d4.format(y2)+")", thirdX+commonChord,midY-y2Point+10); alpha1 = alphaAngle(commonChordX(r1,r2,d), 0.5*chordLength(r1,r2,d)); segment1Area = sectorArea(r1, alpha1) - triangleArea(r1, alpha1); alpha2 = alphaAngle(d-commonChordX(r1,r2,d), 0.5*chordLength(r1,r2,d)); if (alpha2 < 0) //flipped negative because >=180 alpha2 = 2*Math.PI + alpha2; segment2Area = sectorArea(r2, alpha2) - triangleArea(r2, alpha2); } else { //r2 circle completely inside r1 alpha1 = 0; alpha2 = 2*Math.PI; segment1Area = 0; segment2Area = Math.PI*r2*r2; //entire C2 } } else { //disjoint alpha1 = 0; alpha2 = 0; segment1Area = 0; segment2Area = 0; //concave lens between the disjoint circles g.setColor(Color.GRAY); for (int py=-r2rLen; py<=r2rLen; py++) // rectangle: width is d, height is r2 circle for (int px=-dLen; px<0; px++) //not the most efficient... if (distanceFormula(px+c2midX,py+c2midY, c2midX,c2midY) > r2rLen && distanceFormula(px+c2midX,py+c2midY, c1midX,c1midY) > rLen) //outside both circles g.drawLine(px+c2midX,py+c2midY, px+c2midX,py+c2midY); } overlapArea = segment1Area + segment2Area; segment1AreaTextField.setText(""+d4.format(segment1Area)); //would rather not do GUI here segment2AreaTextField.setText(""+d4.format(segment2Area)); overlapAreaTextField.setText(""+d4.format(overlapArea)); //showStatus("overlap area check: "+lensArea(r2,r1,d)); //good //showStatus("height check: "+lensHeight(r2,r1,d)); //good g.setColor(Color.BLACK); g.drawString("\u03B11= "+d2.format(Math.toDegrees(alpha1))+ "\u00B0 \u03B12= "+d2.format(Math.toDegrees(alpha2))+"\u00B0", 2*thirdX ,50); g.drawString("C1 area= "+d4.format(Math.PI*r1*r1)+" C2 area= "+d4.format(Math.PI*r2*r2), 2*thirdX+150,50); g.drawString("C1 overlap= "+d2.format(overlapArea/(Math.PI*r1*r1)*100)+ "% C2 overlap= "+d2.format(overlapArea/(Math.PI*r2*r2)*100)+"%", 2*thirdX,70); if (alpha1 > 0) { //neither disjoint nor C2 completely inside g.drawString("C2 to chord= "+d4.format(distanceFormula(d,0,commonChordLineX,0)), 2*thirdX ,90); //showStatus("lune area check: "+luneArea(r2,r1,d)); //doesn't work?? g.drawString("Lune area= "+d4.format(Math.PI*r2*r2-overlapArea), 2*thirdX+180 ,90); g.drawString((r1!=r2?"a":"")+"symmetric lens Width="+d4.format(r1+r2-d)+ " Height="+d4.format(lensHeight(r2,r1,d)), 2*thirdX ,110); if (r1==r2 && d==r1) g.drawString("Vesica piscis", 2*thirdX+250, 110); g.drawString("Focal distance="+d2.format(focalDistance(r1,r2,r1+r2-d,1.52))+ " "+d2.format(focalDistance(r2,r1,r1+r2-d,1.52)), 2*thirdX ,120); } if (d == 0) //annulus g.drawString("Annulus area= "+d2.format(Math.PI*r1*r1-Math.PI*r2*r2), 2*thirdX ,90); g.setColor(Color.RED); g.drawString("Center of mutual mass: ("+d2.format(centroid) +",0)" , 2*thirdX ,140); g.fillRect(thirdX+centroidX,midY, 2,2); //r1 circle on leftside. its radius is always rLen (1/3 of width) g.setColor(Color.RED); g.drawOval(thirdX-rLen, midY-rLen, 2*rLen, 2*rLen); g.fillRect(thirdX,midY, 2,2); g.drawString("(0,0)",thirdX,midY); //r2 circle is proportional to r1's screen size g.setColor(Color.BLACK); g.drawOval(thirdX+dLen-r2rLen,midY-r2rLen, 2*r2rLen,2*r2rLen); g.fillRect(thirdX+dLen,midY,2,2); g.setColor(Color.CYAN); g.drawString("("+d4.format(d)+",0)",thirdX+dLen,midY+10); } public double distanceFormula (double x1, double y1, double x2, double y2){ return Math.sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1)); } public double commonChordX (double r1, double r2, double d) { return -(r2*r2 - r1*r1 - d*d) / (2*d); } public double chordLength(double r1, double r2, double d) { double x = commonChordX(r1,r2,d); //-(r2*r2 - r1*r1 - d*d) / (2*d); return 2 * Math.sqrt(r1*r1 - x*x); } public double alphaAngle(double x, double y) { //x is adj, y is opp return 2 * Math.atan(y/x); } public double triangleArea(double r, double a) { //a in radians return r*r/2 * Math.sin(a); } public double sectorArea(double r, double a) { //a in radians return Math.PI*r*r * (a/(2*Math.PI)); } //formula from Wolfram. doesn't work??? public double luneArea(double a, double b, double c) { // b>a !! c is distance return 2*(1.0/4*Math.sqrt((a+b+c)*(b+c-a)*(c+a-b)*(a+b-c))) + a*a * Math.acos(1/(2*a*c/(b+b-a*a-c*c))) - b*b * (2*b*c/(b*b+c*c-a*a)); } //asec x = acos 1/x //formula from Wolfram public double lensArea(double r, double R, double d) { return r*r*Math.acos((d*d+r*r-R*R)/(2*d*r)) + R*R*Math.acos((d*d+R*R-r*r)/(2*d*R)) - 0.5*Math.sqrt((-d+r+R)*(d+r-R)*(d-r+R)*(d+r+R)); } public double lensHeight(double r, double R, double d) { return 1/d*Math.sqrt((-d+r-R)*(-d-r+R)*(-d+r+R)*(d+r+R)); } //w width of lens, n refractive index public double focalDistance(double r1, double r2, double w, double n) { return 1 / ((n-1)*(1/r1 - 1/r2 + (n-1)*w/(n*r1*r2))); } }