// ComPhys File: FieldLine.java
// Chapter 9: Draw electric field lines in two dimensions

import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.lang.*;
import java.util.*;
 
public class FieldLine extends Applet implements ActionListener {

    double L = 10;              // box has length, width 2*L = 20 cm
    double a = 0.2;             // radius of visual image of charges

    class Charge {

        Charge (double q, double x, double y) {

            this.q = q;
            this.x = x;
            this.y = y;

        }

        double q;
        double x;
        double y;

    }

    Vector chargeVector = new Vector();
    boolean doAddCharge;
    double qtotal;
    int N;

    Graphics draw_lines_g;
    double draw_lines_scale;
    boolean doDrawLines;
    
    void draw_lines () {

        int lpc = 8;            // lines per charge
        double Emin = 0.01;     // if E < Emin stop drawing field lines
        int sign = 1;
        if (qtotal < 0)
            sign = -1;
        double ds_small = 0.01 * sign;
        double ds_big = sign;

        Enumeration i = chargeVector.elements();
        while (i.hasMoreElements()) {

            Charge qi = (Charge) i.nextElement();
            if (qi.q * sign > 0) {

                double dtheta = 2 * Math.PI / (lpc * Math.abs(qi.q));
                for (double theta = 0; theta < 2 * Math.PI; theta += dtheta) {

                    boolean stop_plot = false;
                    double xline = qi.x + a * Math.cos(theta);
                    double yline = qi.y + a * Math.sin(theta);
                    double r = 0;

                    do {

                        double Ex = 0;
                        double Ey = 0;
                        Enumeration j = chargeVector.elements();
                        while (j.hasMoreElements()) {

                            Charge qj = (Charge) j.nextElement();
                            double dx = xline - qj.x;
                            double dy = yline - qj.y;
                            r = Math.sqrt(dx * dx + dy * dy);
                            if (r > 0.9 * a) {

                                double E0 = qj.q / (r * r * r);
                                Ex += E0 * dx;
                                Ey += E0 * dy;

                            } else {

                                stop_plot = true;
                                                            
                            }
                            
                        }

                        double E = Math.sqrt(Ex * Ex + Ey * Ey);
                        if (E > Emin || r < 20) {

                            xline += ds_small * Ex / E;
                            yline += ds_small * Ey / E;

                        } else {

                            xline += ds_big * Ex / E;
                            yline += ds_big * Ey / E;

                        }

                        if (xline >= 0 && xline < 2 * L &&
                            yline >= 0 && yline < 2 * L) {
                            
                            int ix = (int) (xline * draw_lines_scale);
                            int iy = (int) (yline * draw_lines_scale);
                            draw_lines_g.drawLine(ix, iy, ix, iy);

                        }

                        if (Math.abs(xline) > 100 * L ||
                            Math.abs(yline) > 100 * L)
                            break;      // line is probably headed to infinity

                    } while (!stop_plot);
                    
                }

            }
            
        }

    }

    boolean doClearPicture;

    class Picture extends Canvas implements MouseListener {

        int plotSize = 400;
        Color ivory = new Color(255, 255, 240);

        Picture () {

            setSize(plotSize, plotSize);
            setBackground(ivory);
            addMouseListener(this);

        }

        public void mouseClicked (MouseEvent me) {

            double scale = 2 * L / plotSize;

            if (doAddCharge) {

                double q = 0;
                try {

                    q = new Double(chargeTextField.getText()).doubleValue();

                } catch (NumberFormatException nfe) { }

                double x = me.getX() * scale;
                double y = me.getY() * scale;
                chargeVector.addElement(new Charge(q, x, y));
                qtotal += q;
                ++N;
                NLabel.setText("N = " + N);
                totalChargeLabel.setText("Total Q = " + qtotal);

                repaint();

            }

        }

        public void mouseEntered (MouseEvent me) { }
        public void mouseExited (MouseEvent me) { }
        public void mousePressed (MouseEvent me) { }
        public void mouseReleased (MouseEvent me) { }

        public void paint (Graphics g) {

            update(g);

        }

        public void update (Graphics g) {

            double scale = plotSize / (2 * L);

            if (doClearPicture) {

                g.clearRect(0, 0, plotSize, plotSize);
                doClearPicture = false;

            }

            // draw 1 cm grid
            g.setColor(Color.lightGray);
            for (int ix = (int) scale; ix <= plotSize; ix += (int) scale)
                g.drawLine(ix, 0, ix, plotSize);
            for (int iy = (int) scale; iy <= plotSize; iy += (int) scale)
                g.drawLine(0, iy, plotSize, iy);

            // draw charges
            int r = (int) (a * scale);
            Enumeration e = chargeVector.elements();
            while (e.hasMoreElements()) {

                Charge q = (Charge) e.nextElement();
                if (q.q != 0) {
                    
                    int ix = (int) Math.round(q.x * scale);
                    int iy = (int) Math.round(q.y * scale);
                    if (q.q > 0)
                        g.setColor(Color.blue);
                    else g.setColor(Color.red);
                    g.fillOval(ix - r, iy - r, 2 * r, 2 * r);
                    
                }

            }

            if (doDrawLines) {

                g.setColor(Color.black);
                draw_lines_g = g;
                draw_lines_scale = scale;
                draw_lines();
                doDrawLines = false;

            }
            
        }

    }

    Picture picture;
    Label NLabel;
    Label totalChargeLabel;
    TextField chargeTextField;
    Button addChargeButton;
    Button drawLinesButton;
    Button resetButton;

    public void init () {

        add(picture = new Picture());

        Panel p = new Panel();
        p.setLayout(new GridLayout(3, 2));

        p.add(NLabel = new Label("N = 0"));
        p.add(totalChargeLabel = new Label("Total Q = 0"));

        p.add(addChargeButton = new Button("Add charge q ="));
        addChargeButton.addActionListener(this);

        p.add(chargeTextField = new TextField("+1"));
        chargeTextField.addActionListener(this);

        p.add(drawLinesButton = new Button("Plot Field Lines"));
        drawLinesButton.addActionListener(this);
        
        p.add(resetButton = new Button("Reset"));
        resetButton.addActionListener(this);

        add(p);
        
    }

    public void actionPerformed (ActionEvent event) {

        if (event.getSource() == addChargeButton) {

            doDrawLines = false;
            doAddCharge = true;

        } else if (event.getSource() == chargeTextField) {

            doDrawLines = false;
            doAddCharge = true;

        } else if (event.getSource() == drawLinesButton) {

            doAddCharge = false;
            doDrawLines = true;
            picture.repaint();

        } else if (event.getSource() == resetButton) {

            doAddCharge = doDrawLines = false;
            chargeVector.removeAllElements();
            N = 0;
            NLabel.setText("N = " + N);
            qtotal = 0;
            totalChargeLabel.setText("Total Q = " + qtotal);
            doClearPicture = true;
            picture.repaint();

        }

    }

    public static void main (String[] args) {

        FieldLine fieldLine = new FieldLine();
 
        Frame aFrame = new Frame("Electric Field Lines");
 
        // anonymous inner class to destroy window
        aFrame.addWindowListener(new WindowAdapter () {
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });
        
        aFrame.add(fieldLine);
        fieldLine.init();
        aFrame.setSize(700, 450);
        aFrame.setLocation(50, 50);
        aFrame.setVisible(true);
 
    }

}

