import java.io.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.font.*;
import java.awt.geom.Arc2D;
import java.awt.geom.Line2D;
import java.net.URL;
import java.text.*;
import java.util.Vector;
import java.util.Map;
import java.util.Hashtable;
import java.util.jar.Attributes;

// Module 1 Exercise 4
public class Mod1Plot extends Panel {

    private static final long serialVersionUID = 352369;

    private static int NMAX = 5000;  //was (NMAX=10000)
    private static int xscale = 3;   // number of real pixels per "pixel"
    private static int yscale = 3;   // number of real pixels per "pixel"
    private static int numberPixelsPerGrid = 3;  // was 6
    private double nmPerPixel;  // nano meters per pixel
    private double pixelsPerNm;  // pixels per nano meter
    private double pixelsPerNmOver4piepsilon;
    private double gridsPerNmOver4piepsilon;

    private double pixelsPerNmMuOver4pi;
    private double gridsPerNmMuOver4pi;

    int ncharges;  // # of charges in 2D area
    int xpos[], ypos[];  // positions of charges, in nm
    double charge[];  // signed charge of each charge, nColoumbs
    double elemcharge[];  // signed charge of each charge as mult of elem charg
    Point pixelPt[];  // actual pixel point (NOT grid point)

    //Vector chargeVector = new Vector();
    Vector<Charge> chargeVector = new Vector<Charge>();

    int numEquiLines;
    private static final int EQUI_LINES_INIT = 15;

    // the chosen ranges for the plot: (1) is the min, (2) is the max
    //  and (3) is delta for large tics
    double xrange[] = new double[3];
    double yrange[] = new double[3];

    static int nxgrid , nygrid;
    double potential[][];
    double Emax;
    double potentialMax, potentialMin;

    int max_number_charges = NMAX;

    Mod1State state;
    Image im;
    Graphics imG;
    Graphics2D g2d;

    public int mainWidth;  // 619
    public int mainHeight;
    //plotPanel.setBounds(12,58,635,581);

    int yMin, yMax;
    int xMin, xMax;
    int yBorder, yOffset, xBorder, xOffset;
    DecimalFormat dec = new DecimalFormat("###.#");

    //static Color bgColor = new Color(194,235,255);
    //private final Color bgColor = new Color(236,236,236);
    private static final Color bgColor = Color.lightGray;

    double deltaY, deltaX;
    FontMetrics fm;
    int fontHeight,ticLength;

    public Button small;


    public Mod1Plot(Mod1State state) {
        super();
        this.state = state;
    }

    public void initialPlot() {
        setLayout(null);
        //setBackground(bgColor);
	setBackground(Color.white);

	
        //im = createImage(mainWidth,mainHeight);
        //imG = im.getGraphics();
        
        mainWidth = state.s630;  // 619
        mainHeight = state.s576;


	/*
      Rectangle virtualBounds = new Rectangle();
      GraphicsEnvironment ge = GraphicsEnvironment.
              getLocalGraphicsEnvironment();
      GraphicsDevice[] gs =
              ge.getScreenDevices();
      for (int j = 0; j < gs.length; j++) {
          GraphicsDevice gd = gs[j];
          GraphicsConfiguration[] gc =
              gd.getConfigurations();
          for (int i=0; i < gc.length; i++) {
              virtualBounds =
                  virtualBounds.union(gc[i].getBounds());
          }
      } 
	*/

	

	
    
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice gs = ge.getDefaultScreenDevice(); 
        GraphicsConfiguration gc = gs.getDefaultConfiguration();


	System.out.printf("device bounds(%d,%d) w=%d  h=%d %n",gc.getBounds().x,
			  gc.getBounds().y,gc.getBounds().width,gc.getBounds().height);


	
        im = gc.createCompatibleImage(mainWidth, mainHeight, Transparency.TRANSLUCENT);

	System.out.printf("image w=%d  h=%d  %n",im.getWidth(this),im.getHeight(this));
	
        imG = im.getGraphics();

        small = new Button("Instructions");
        add(small);
	small.setBackground(Color.white);
	small.setBounds(state.s500,state.s5,state.s120,state.s20);
        //small.setFont(TheFonts.sanSerif11);
        small.setFont(new Font("SanSerif",Font.BOLD,state.font12));

        numEquiLines = EQUI_LINES_INIT;

        // where to draw on the screen:
        //plotPanel.setBounds(12,58,494,480);
        //yOffset = 10;
        //yBorder = 20;
        //xOffset = 10;
        //xBorder = 20;

        yOffset = state.s10;
        yBorder = state.s20;
        xOffset = state.s13;
        xBorder = state.s20;

        xMin = xBorder+xOffset;
        //xMax = 438;    // for 9.5 nm   ... 43 pixels per nm
        xMax = state.s589;    // for 9 nm
        yMin = yBorder+yOffset; 
        yMax = state.s546;

        deltaY = (yMax - yMin)/12.0;  // these are both the same
        deltaX = deltaY;

        g2d = (Graphics2D)imG;
        //g2d.scale(1.11,1.0);  // b/c vertical pixels larger than horizontal

        nmPerPixel = 1.0/deltaX;   // 1 nanometer per deltaX and deltaY
        pixelsPerNm = deltaX/1.0;   
        pixelsPerNmOver4piepsilon = pixelsPerNm/(4*Math.PI*8.85E-12*1.0E-9);
        gridsPerNmOver4piepsilon = pixelsPerNmOver4piepsilon/
            numberPixelsPerGrid;

        pixelsPerNmMuOver4pi = pixelsPerNm*1.0E-7;
        gridsPerNmMuOver4pi = pixelsPerNmMuOver4pi/
            numberPixelsPerGrid;

        //ngrid = Math.min(mainWidth-(int)xoff,mainHeight-(int)yoff) /
        //    numberPixelsPerGrid;

        // grid has 12 nm in y direction and 13 nm in x direction:
        nygrid = (int)(12*pixelsPerNm / numberPixelsPerGrid);
        nxgrid = (int)(13*pixelsPerNm / numberPixelsPerGrid);

        potential = new double[nxgrid][nygrid];
        xrange[0] = xMin;
        xrange[1] = xMax;
        xrange[2] = deltaX;  // spacing for axis tic marks
        yrange[0] = yMin;
        yrange[1] = yMax;
        yrange[2] = deltaY;  // spacing for axis tic marks


        // Get font sizes:
        //setFont(TheFonts.bold16);
        setFont(TheFonts.sanSerif12);  // for axis labels
        fm = getFontMetrics(getFont());
        fontHeight = fm.getHeight();
        //ticLength  = fm.stringWidth("w") - 4; // just a wide letter.
        ticLength = state.s6;
        
        paintImage();
        repaint();
  }


    public boolean changeDeltaV(int sliderVal) {
        int newNumEquiLines;
        int i = sliderVal - 10;
        if (i == 0) newNumEquiLines = EQUI_LINES_INIT;
        //else if (i > 0) newNumEquiLines = (int)(EQUI_LINES_INIT*1.2*i);
        //else newNumEquiLines = (int) (EQUI_LINES_INIT/(1.2*Math.abs(i)));
        else newNumEquiLines = (int) (EQUI_LINES_INIT - (1.5*i));
        if (newNumEquiLines != numEquiLines) {
            numEquiLines = newNumEquiLines;
            return true;
        }
        return false;
    }


    public void getBFieldAtCursor(Point pt) {
        // convert pt from pixels to "places" on this big plot
        // get magnetic field at this place
        // set state.bfieldAtCursor
        //System.out.println("set state.bfieldAtCursor at Point: "+
        //                   String.valueOf(pt.x)+", "+
        //                   String.valueOf(pt.y));

        //pt.x /= 1.11;
        Point gridPt = getGridPointFromPixelPoint(pt);
        ActualVector bfield = getBFieldValueAtPoint(gridPt);
        state.bfieldAtCursor = bfield.ee;
        state.bfieldThetaAtCursor = bfield.theta;
        return;
    }


    public void moveCharge() {
        // convert state.moveFromPoint and state.moveToPoint to
        // the "places" on this big plot
        //  add the charge(s) (or just one?) to the movetoPoint
        Point fromGridPt, toGridPt;

        /*
        Point fromPt = new Point((int)(1.0*state.moveFromPoint.x/1.11),
                                 state.moveFromPoint.y);
        Point toPt = new Point((int)(1.0*state.moveToPoint.x/1.11),
                               state.moveToPoint.y);
        */
        Point fromPt = new Point(state.moveFromPoint.x,state.moveFromPoint.y);
        Point toPt = new Point(state.moveToPoint.x,state.moveToPoint.y);
        fromGridPt = getGridPointFromPixelPoint(fromPt);
        toGridPt = getGridPointFromPixelPoint(toPt);
        Charge c;
        Charge cToMove = null;
        for (int i = 0; i < chargeVector.size(); i++) {
            //c = (Charge) chargeVector.elementAt(i);
            c = chargeVector.elementAt(i);
            xpos[i] = c.xpos;  ypos[i] = c.ypos;
            if (withinReasonableRange(xpos[i],ypos[i],
                                      fromGridPt.x,fromGridPt.y)) {
                cToMove = c;
                break;
            }
        }
        if (cToMove != null) {
            cToMove.xpos = toGridPt.x;
            cToMove.ypos = toGridPt.y;
            cToMove.pixelPt = new Point(toPt);
        }
        return;
    }


    private boolean withinReasonableRange(int x1, int y1, int x2, int y2) {
        // NOTE: assumed that these coordinates are for GRIDS not PIXELS
        if (x2 >= (x1 - 3) && x2 <= (x1 + 3))
            if (y2 >= (y1 - 3) && y2 <= (y1 + 3)) return true;
        return false;
    }


    public void reset() {
        numEquiLines = EQUI_LINES_INIT;
        //chargeVector = new Vector();
        chargeVector = new Vector<Charge>();
        state.moveFromPoint = new Point(-1,-1);
        state.moveToPoint = new Point(-1,-1);
        imG.clearRect(0,0,mainWidth,mainHeight);
    }


    public void placeCharge(Point pt) {
        // convert pt from pixels to "places" on this big plot
        // place a (or more? if a slider ...) negative charge at this "place"
        //System.out.println("placed -ve charge at Point: "+
        //                   String.valueOf(pt.x)+", "+
        //                   String.valueOf(pt.y));
        Point gridPt;
        //pt.x /= 1.11;
        gridPt = getGridPointFromPixelPoint(pt);
        Charge c = new Charge(gridPt.x, gridPt.y, state.chargeToAdd*
                              state.elementaryCharge, state.chargeToAdd, pt);
        chargeVector.addElement(c);
        //repaint();
        return;
    }


    public void removeCharge(Point pt) {
        // convert pt from pixels to "places" on this big plot
        // remove charge (or more? if a slider ...) at this "place"
        Point gridPt;
        //pt.x /= 1.11;
        gridPt = getGridPointFromPixelPoint(pt);
        Charge c;
        for (int i = 0; i < chargeVector.size(); i++) {
            //c = (Charge)chargeVector.elementAt(i);
            c = chargeVector.elementAt(i);
            xpos[i] = c.xpos;  ypos[i] = c.ypos;
            if (withinReasonableRange(xpos[i],ypos[i],gridPt.x,gridPt.y)) {
                chargeVector.removeElementAt(i);
            }
        }        
        return;
    }


    public void changeCharge(Point pt) {
        // convert pt from pixels to "places" on this big plot
        // remove charge (or more? if a slider ...) at this "place"
        Point gridPt;
        //pt.x /= 1.11;
        gridPt = getGridPointFromPixelPoint(pt);
        Charge c;
        for (int i = 0; i < chargeVector.size(); i++) {
            //c = (Charge)chargeVector.elementAt(i);
            c = chargeVector.elementAt(i);
            xpos[i] = c.xpos;  ypos[i] = c.ypos;
            if (withinReasonableRange(xpos[i],ypos[i],gridPt.x,gridPt.y)) {
                c.charge = state.chargeToAdd*state.elementaryCharge;
                c.elemcharge = state.chargeToAdd;
            }
        }        
        return;
    }
        

    private Point getGridPointFromPixelPoint(Point pt) {
        Point newPt = new Point(0,0);
        newPt.x = (int) ((pt.x - xrange[0])/numberPixelsPerGrid);
        newPt.y = (int) ((pt.y - yrange[0])/numberPixelsPerGrid);
        if (newPt.x >= nxgrid) newPt.x = nxgrid-1;
        else if (newPt.x < 0) newPt.x = 0;
        if (newPt.y >= nygrid) newPt.y = nygrid-1;
        else if (newPt.y < 0) newPt.y = 0;
        return newPt;
    }

    private Point getPixelPointFromGridPoint(Point pt) {
        Point newPt = new Point(0,0);
        newPt.x = (int)((pt.x*numberPixelsPerGrid)+xrange[0]);
        newPt.y = (int)((pt.y*numberPixelsPerGrid)+yrange[0]);
        return newPt;
    }

    private ActualVector getBFieldValueAtPoint(Point pt) {
        double exi, eyi, ex, ey, ee, riSquared, ei;
        double vmax = state.vmax;
        int x = pt.x;
        int y = pt.y;
        ex = 0.0;  ey = 0.0;
        double arbitraryMax = 50000.0; // to simulate infinity
        for (int k = 0; k < ncharges; k++) {
            if (x == xpos[k] && y == ypos[k]) {
                if (charge[k] >= 0.0) {exi = vmax; eyi = vmax;}
                else {exi = -1.0*vmax; eyi = -1.0*vmax;}
            } else {
                riSquared = square(x-xpos[k])+square(y-ypos[k]);
                ei = charge[k]/riSquared;
                exi = -(y-ypos[k]) * ei;
                eyi = (x-xpos[k]) * ei;
            }
            
            if (Math.abs(exi) < vmax)
                ex = ex + exi;
            else {
                if (exi > 0.0) 
                    ex = vmax;
                else ex = -1.0*vmax;
            }
            if (Math.abs(eyi) < vmax)
                ey = ey + eyi;
            else {
                if (eyi > 0.0) 
                    ey = vmax;
                else ey = -1.0*vmax;
            }
        }

        ex *= gridsPerNmMuOver4pi   *1.0E9;  // convert from nm to m
        ey *= gridsPerNmMuOver4pi   *1.0E9;  // convert from nm to m

        //if (Math.abs(ex) > state.efieldMax) {
        if (Math.abs(ex) > arbitraryMax) {
            if (ex > 0.0) ex = arbitraryMax;
            else ex = -1.0*arbitraryMax;
        }
        //if (Math.abs(ey) > state.efieldMax) {
        if (Math.abs(ey) > arbitraryMax) {
            if (ey > 0.0) ey = arbitraryMax;
            else ey = -1.0*arbitraryMax;
        }
        ee = Math.sqrt(square(ex) + square(ey));
        //System.out.println("ee = "+String.valueOf(ee));
        //return ee;
        double theta = Math.atan2(-ey,ex);
        ActualVector efield = new ActualVector(ee,theta);
        return efield;
    }


    public void drawPlot() {


	return;
	/*
	
        if (state.currentAction == state.placeCharge ||
            state.currentAction == state.removeCharge ||
            state.currentAction == state.moveCharge ||
            state.currentAction == state.changeChargeValue) {
            
            ncharges = chargeVector.size();
            xpos = new int[ncharges];
            ypos = new int[ncharges];
            charge = new double[ncharges];
            elemcharge = new double[ncharges];
            pixelPt = new Point[ncharges];
            
            Charge c;
            for (int i=0; i<ncharges; i++) {
                //c = (Charge) chargeVector.elementAt(i);
                c = chargeVector.elementAt(i);
                xpos[i] = c.xpos; ypos[i] = c.ypos;
                charge[i] = c.charge;
                elemcharge[i] = c.elemcharge;
                pixelPt[i] = c.pixelPt;
            }
        }
        
        //comment out for now; debug one at a time:
        imG.clearRect(0,0,mainWidth,mainHeight);
	//xxxxxxxxxxxxxx
        
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                             RenderingHints.VALUE_ANTIALIAS_ON);

        calculate_magnetic_field();
        draw_magnetic_field();


        draw_magneticfield_vectors();
        draw_charges();

        //paintImage();     THIS IS NEW !!!!!!!!!!
        repaint();

	*/
    }



    private void calculate_magnetic_field() {
        double x, y;
        int i, j, k;
        //double v, vi;
        double ex, ey, exi, eyi, e;
        double riSquared, ri, ei;
        double vmax = state.vmax;

        potentialMax = -10000.0;
        potentialMin = 10000.0;

        for (i = 0; i < nxgrid; i++) {
            x = i;
            for (j = 0; j < nygrid; j++) {
                y = j;
                ex = 0.0;  ey = 0.0;
                for (k = 0; k < ncharges; k++) {
                    if (x == xpos[k] && y == ypos[k]) {
                        if (charge[k] >= 0.0) {exi = vmax; eyi = vmax;}
                        else {exi = -1.0*vmax; eyi = -1.0*vmax;}
                    } else {
                        riSquared = square(x-xpos[k])+square(y-ypos[k]);
                        ei = charge[k]/riSquared;
                        exi = -(y-ypos[k]) * ei;
                        eyi = (x-xpos[k]) * ei;
                    }
                    
                    if (Math.abs(exi) < vmax)
                        ex = ex + exi;
                    else {
                        if (exi > 0.0) 
                            ex = vmax;
                        else ex = -1.0*vmax;
                    }
                    if (Math.abs(eyi) < vmax)
                        ey = ey + eyi;
                    else {
                        if (eyi > 0.0) 
                            ey = vmax;
                        else ey = -1.0*vmax;
                    }
                }
                
                // DON'T REALLY NEED HERE:
                //ex *= gridsPerNmMuOver4pi;
                //ey *= gridsPerNmMuOver4pi;
                
                //if (Math.abs(ex) > state.bfieldMax) {
                //    if (ex > 0.0) ex = state.bfieldMax;
                //    else ex = -1.0*state.bfieldMax;
                //}
                //if (Math.abs(ey) > state.bfieldMax) {
                //    if (ey > 0.0) ey = state.bfieldMax;
                //    else ey = -1.0*state.bfieldMax;
                //}
                
                e = Math.sqrt(square(ex)+square(ey));
                //if (Math.abs(v) > state.potentialMax) {
                //    if (v > 0.0) v = state.potentialMax;
                //    else v = -1.0*state.potentialMax;
                //}
                potential[i][j] = e;
                if (potential[i][j] > potentialMax)
                    potentialMax = potential[i][j];
                if (potential[i][j] < potentialMin)
                    potentialMin = potential[i][j];
            }
        }
        return;
    }
    
    
    private void draw_magnetic_field() {
        int i, j;
        double colorValue;
        int index;
        Color color;

        Graphics2D g2d = (Graphics2D)imG;
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                             RenderingHints.VALUE_ANTIALIAS_OFF);
        
        double bScaler = 10*100/potentialMax;

        // NOTE: scale B (magnetic field magnitude) to be -100 to +100
        for (i=0; i<nxgrid; i++)
            for (j=0; j<nygrid; j++)
                if (potential[i][j] >= 0.0) {
                    index = (int)(bScaler*potential[i][j]);
                    colorValue = state.lookup[index];
                    /*  green:
                    color = new Color(1-(float)(colorValue),
                                      (float)1.0,
                                      1-(float)(colorValue));
                    */

                    color = new Color(1-(float)(colorValue),
                                      1-(float)(colorValue),
                                      (float)1.0);
		    /*
                    imG.setColor(color);
                    g2d.fillRect((int)xrange[0]+i*numberPixelsPerGrid,
                                 (int)yrange[0]+j*numberPixelsPerGrid,
                                 numberPixelsPerGrid,
                                 numberPixelsPerGrid);
		    */
                } else {
                    index = (int)(-bScaler*potential[i][j]);
                    colorValue = state.lookup[index];
                    color = new Color((float)1.0,
                                      1-(float)(colorValue),
                                      1-(float)(colorValue));
		    /*
                    imG.setColor(color);
                    imG.fillRect((int)xrange[0]+i*numberPixelsPerGrid,
                                 (int)yrange[0]+j*numberPixelsPerGrid,
                                 numberPixelsPerGrid,
                                 numberPixelsPerGrid);
		    */
                }
        
        return;
    }
    
    
    private void draw_magneticfield_vectors() {
        //int stride = 15;
        int stride = 10;  //7
        double x, y;
        int i, j, k;
        int ii,jj, len1,len2;
        double ex, ey, exi, eyi;
        double riSquared, ri, ei;
        double vmax = state.vmax;
        double efieldx[][], efieldy[][];
        double Emax;

        if (ncharges == 0) return;

        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                             RenderingHints.VALUE_ANTIALIAS_ON);


        len1 = nxgrid/stride +1;
        len2 = nygrid/stride +1;

        efieldx = new double[len1][len2];
        efieldy = new double[len1][len2];

        Emax = 0.0;
        ii = 0;
        for (i = 3; i < nxgrid && ii<len1; i+=stride) {
            x = i;
            jj = 0;
            for (j = 3; j < nygrid && jj<len2; j+=stride) {
                y = j;
                ex = 0.0;  ey = 0.0;
                for (k = 0; k < ncharges; k++) {
                    if (x == xpos[k] && y == ypos[k]) {
                        if (charge[k] >= 0.0) {exi = vmax; eyi = vmax;}
                        else {exi = -1.0*vmax; eyi = -1.0*vmax;}
                    } else {
                        riSquared = square(x-xpos[k])+square(y-ypos[k]);
                        ei = charge[k]/riSquared;
                        exi = -(y-ypos[k]) * ei;
                        eyi = (x-xpos[k]) * ei;
                    }
                    
                    if (Math.abs(exi) < vmax)
                        ex = ex + exi;
                    else {
                        if (exi > 0.0) 
                            ex = vmax;
                        else ex = -1.0*vmax;
                    }
                    if (Math.abs(eyi) < vmax)
                        ey = ey + eyi;
                    else {
                        if (eyi > 0.0) 
                            ey = vmax;
                        else ey = -1.0*vmax;
                    }
                }

                // DON'T REALLY NEED HERE:
                //ex *= gridsPerNmMuOver4pi;
                //ey *= gridsPerNmMuOver4pi;

                if (Math.abs(ex) > state.bfieldMax) {
                    if (ex > 0.0) ex = state.bfieldMax;
                    else ex = -1.0*state.bfieldMax;
                }
                if (Math.abs(ey) > state.bfieldMax) {
                    if (ey > 0.0) ey = state.bfieldMax;
                    else ey = -1.0*state.bfieldMax;
                }
                //==============================================================
                //  NOTE 5/26/2022 (Umberto)
                //  It appears that the magnetic field was rotating contrary to 
                //  convention.  Assuming that current is denoted with positive
                //  sign when flowing out of the page, along the positive 
                //  z-direction (with "x" along horizontal axis and "y" along 
                //  vertical axis) then B field should rotate counterclockwise.  
                //  In order to make it so, I have changed the sign of the 
                //  field components below.
                
                //==============================================================
                //efieldx[ii][jj] = ex;
                //efieldy[ii][jj] = ey;
                
                efieldx[ii][jj] = -ex;
                efieldy[ii][jj] = -ey;
                //==============================================================

                if (Math.abs(efieldx[ii][jj]) > Emax)
                    Emax = Math.abs(efieldx[ii][jj]);
                if (Math.abs(efieldy[ii][jj]) > Emax)
                    Emax = Math.abs(efieldy[ii][jj]);
                
                jj++;
            }
            ii++;
        }

        double from[] = new double[2];
        double to[] = new double[2];
        double invEmax;
        double ee;
        int index;
        double colorValue;
        double color[] = new double[3];
        double invnxgrid, invnygrid;
        double linewidth;

        //linewidth=15.0;
        //linewidth=7.0;
        linewidth=10.0;
        invnxgrid = 1.0/nxgrid;
        invnygrid = 1.0/nxgrid;

        invEmax = 1.0/Emax;

        /*
        from[0] = -20+8; // 40X40 box to display the arrow
        to[0] = 20-8;    // line is -12 to +12 long
        */
        /*
        from[0] = -10+4; // 20X20 box to display the arrow
        to[0] = 10-4;    // line is -6 to +6 long
        */
        from[0] = -12+4; // 20X20 box to display the arrow
        to[0] = 12-4;    // line is -8 to +8 long


        from[1] = 0;     // y coordinates; will rotate arrow later
        to[1] = 0;

        Point pixelPt;
        double theta;
        ii = 0;
        for (i=3; i<nxgrid && ii < len1; i+=stride) {
            jj = 0;
            for (j=3; j<nygrid && jj < len2; j+=stride) {
                pixelPt = getPixelPointFromGridPoint(new Point(i,j));
                theta = Math.atan2(-efieldy[ii][jj],efieldx[ii][jj]);
                
                // color is deeper blue if near EMax, white if near zero
                //ee = 0.5*invEmax*Math.sqrt(square(efieldx[ii][jj]) + 
                //                       square(efieldy[ii][jj]));
                //index = (int)(1000.0*ee);
                //colorValue = state.lookup[index];
                //color[0] = 1.0-colorValue;
                //color[1] = 1.0-colorValue;
                //color[2] = 1.0;
        
                //if (ee > 1.0E-10)
                //    plot_arrowline_local(linewidth,color,from,to,theta,pixelPt);
                plot_arrowline_local(linewidth,from,to,theta,pixelPt);
                jj++;
            }
            ii++;
        }
        return;
    }



    private void draw_charges() {
        int scale;
        String params = new String();
        double location[] = new double[2];
        String string = new String();
        double rmax;
        int i;
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                             RenderingHints.VALUE_ANTIALIAS_ON);
        
        rmax = xrange[1]-xrange[0];
        if (yrange[1]-yrange[0] > rmax) rmax = yrange[1]-yrange[0];

        scale = numberPixelsPerGrid;
        
        int xCenter, yCenter;
        int Radius = state.s6;
        

        for (i=0; i<ncharges; i++) {
            imG.setColor(Color.orange);
            Point pt = pixelPt[i];
            
            // CHANGE 5/18/2022 
            //  Smoother circles with drawing of plus and minus to avoid using
            //  fonts for easier scaling
            xCenter = pt.x;
            yCenter = pt.y;
                       
            fillArcThick(imG,(double)(xCenter-Radius),(double)(yCenter-Radius),
                    (double)(2*Radius), 0.0, 360.0, state.s0,Color.yellow);
            drawArcThick(imG,(double)(xCenter-Radius),(double)(yCenter-Radius),
                    (double)(2*Radius), 0.0, 360.0, state.s0,Color.black);
            //imG.fillOval(pt.x-state.s6,pt.y-state.s6,state.s13,state.s13);
            //imG.setColor(Color.black);
            //imG.drawOval(pt.x-state.s6,pt.y-state.s6,state.s13,state.s13);
            
            drawLineThick(imG,(double)(xCenter-state.s3),(double)(yCenter),
                    (double)(xCenter+state.s3),(double)(yCenter),state.s1,Color.black);
            if (elemcharge[i] >= 0.0){
                drawLineThick(imG,(double)(xCenter),(double)(yCenter+state.s3),
                    (double)(xCenter),(double)(yCenter-state.s3),state.s1,Color.black);
            }
            
            /*
            imG.fillOval(pt.x-6,pt.y-6,13,13);
            imG.setColor(Color.black);
            imG.drawOval(pt.x-6,pt.y-6,13,13);

            if (TheFonts.PC_OS) {
                if (elemcharge[i] >= 0.0) 
                    imG.drawString("+",pt.x-3,pt.y+7);
                else
                    imG.drawString("-",pt.x-3,pt.y+5);
            } else {
                
                if (elemcharge[i] >= 0.0) 
                    imG.drawString("+",pt.x-5,pt.y+5);
                else
                    imG.drawString("-",pt.x-4,pt.y+5);
            }
            */
            imG.drawString(String.valueOf(Math.abs(elemcharge[i])),
                           pt.x+state.s9,pt.y+state.s5);
        }
        return;
    }
    
    
    //private void plot_arrowline_local(double linewidth, double color[],
    //                                  double from[], double to[],
    //                                  double theta, Point pixelPt) {
    private void plot_arrowline_local(double linewidth, 
                                      double from[], double to[],
                                      double theta, Point pixelPt) {
        // color(3)          !  0=none, 1=lots
        // both these coordinates are in inches from lower left corner:
        // from(2)           !  x,y coords in inches
        // to(2)             !  x,y coords in inches

        // Draws a straight line from 'from' to 'to' (x,y coords in inches),
        // and puts an arrowhead on the 'to' end.
        // uses linewidth to scale the linewidth and the arrowhead.

        double x[] = new double[7];
        double y[] = new double[7];
        double dto[] = new double[2];

        double L, lw, hw;
        // lw is linewidth in inches
        // hw is (headwidth-1)/2
            
        int i;
        double headwidth;    // multiple of linewidth

        /*
        //calculate the outline of the arrow-line and arrow-head
        // then just fill it in.
        //
        //        y-axis
        //         /|\                         3
        //          |                          |\
        //          |                          | \
        //          |                          |  \
        //          1--------------------------2   \
        //          |                               \
        //          |                                \                  \
        // -------------------------------------------4------------------
        //          |                                /           x-axis /
        //          |                               /
        //          7--------------------------6   /
        //          |                          |  /
        //          |                          | /
        //          |                          |/
        //          |                          5
        //          |
        //          |
        //  points along the path are numbered.
        //
        //-------------------------------------------------------------
        */

        /*
        linewidth=3.0;
        headwidth=5.0;
        */

        /*   didn't look right even for arrow length = 7
        linewidth=1.5;
        headwidth=2.5;
        */

        linewidth=2;
        headwidth=4;


        lw = linewidth;    //linewidth in pixels
        hw = (headwidth-1.0)*0.5;

        // angle of the arrowhead:
        dto[0] = to[0] - from[0];
        dto[1] = to[1] - from[1];
        //theta = Math.atan2(dto[1],dto[0]);

        // calc the arrowhead starting at 0,0, and going to L,0
        L = Math.sqrt(square(dto[0]) + square(dto[1]));

        x[0] = 0.0;
        y[0] = 0.5*lw;
        x[1] = L-(headwidth-1.0)*lw;
        y[1] = y[0];
        x[2] = x[1];
        y[2] = y[1]+hw*lw*0.6;
        x[3] = L;
        y[3] = 0.0;
        // mirror the top to the bottom:
        x[4] = x[2];
        y[4] = -1.0*y[2];
        x[5] = x[1];
        y[5] = -1.0*y[1];
        x[6] = x[0];
        y[6] = -1.8*y[0];
        
        //original:
        //rotate_polyline(x,y,theta,12.0,1.5); // half of xlen and half of lw
        //translate_polyline(x,y,pixelPt.x-12,pixelPt.y);

        // lep:
        double lepxcent= 0.5*L;
        double lepycent= 0.0;
        rotate_polyline(x,y,theta,lepxcent,lepycent);
        translate_polyline(x,y,pixelPt.x-lepxcent,pixelPt.y-lepycent);

        // Draw the arrow (fill the polygon):
        //imG.setColor(new Color((float)color[0],(float)color[1],(float)color[2]));
        //imG.setColor(Color.blue);
        imG.setColor(Color.orange.darker());
        int ix[] = new int[x.length];
        int iy[] = new int[y.length];
        for (i=0; i<x.length; i++) {
            ix[i] = (int)x[i];
            iy[i] = (int)y[i];
        }
        imG.fillPolygon(ix,iy, x.length);
        // Outline the arrow:
        //imG.setColor(Color.black);
        //imG.drawPolygon(ix,iy, x.length);

        return;
    }


    private void rotate_polyline(double x[], double y[], double theta,
                                 double xcent, double ycent) {
        //  x(n),y(n) are coordinates of each point
        //  theta is the amount to rotate points counterclockwise, radians
        // xcent, ycent  is the center of rotation
        double xx, yy;
        int i;
        for (i=0; i<x.length; i++) {
            xx = (x[i]-xcent)*Math.cos(theta) - (ycent-y[i])*Math.sin(theta);
            yy = (x[i]-xcent)*Math.sin(theta) + (ycent-y[i])*Math.cos(theta);
            x[i] = xx+xcent;
            y[i] = ycent-yy;
        }
        return;
    }


    
    private void translate_polyline(double x[], double y[], 
                                    double dx, double dy) {
        // x(n),y(n) are coordinates of each point
        // dx,dy     are amount to translate points
        
        int i;
        for (i=0; i<x.length; i++) {
            x[i] = x[i] + dx;
            y[i] = y[i] + dy;
            //y[i] = dy - y[i];
        }
        return;
    }


  public void paintImage() {
      g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                           RenderingHints.VALUE_ANTIALIAS_ON);
      
      // clear the borders:
      //imG.clearRect(0,0,xMin,mainHeight);
      //imG.clearRect(0,0,mainWidth,yMin);
      //imG.clearRect(0,yMax,mainWidth,mainHeight-yMax);
      //imG.clearRect(xMax,0,mainWidth-xMax,mainHeight);
      
      imG.setColor(Color.green);
      //imG.fillRect(0,0,xMin,mainHeight);
      imG.fillRect(0,0,mainWidth,yMax);

      // JUST CLEAR OUT EVERYTHING:
      //imG.fillRect(0,0,mainWidth,mainHeight);

      imG.setColor(Color.red);
      imG.drawString("A",5,yMax-60);
      imG.drawString("B",5,yMax-40);
      imG.drawString("C",5,yMax-20);
      imG.drawString("D",5,yMax);
      imG.drawString("E",5,yMax+20);
      
      imG.drawString("F",65,yMax-60);
      imG.drawString("G",65,yMax-40);
      imG.drawString("H",65,yMax-20);
      imG.drawString("I",65,yMax);
      imG.drawString("J",65,yMax+20);

      imG.drawString("***",100,mainHeight-15);
      System.out.printf("yMax is %d%n",yMax);
      System.out.printf("mainHeight is %d%n",mainHeight);
      
      //imG.fillRect(0,yMax,mainWidth,mainHeight);
      //imG.fillRect(0,yMax,mainWidth,mainHeight-yMax);  //THIS IS THE SOLUTION  JUST COMMENT OUT ABOVE!
      //imG.fillRect(xMax,0,mainWidth,mainHeight);       // AND THIS ONE

      /*
      drawAxis(imG);
      drawAxisTicks(imG);
      */
  }


  private void drawAxis(Graphics g) {
      g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);

    // Draw x and y axis:
    //drawLine(g,xMin,yOffset,xMin,yMax); // the y-axis
    //drawLine(g,xMin,yMax,xMax+xBorder,yMax); // the x-axis
    //MyArrows.drawDblUpArrow(g,xMin,yOffset);
    //MyArrows.drawDblRightArrow(g,xMax+xBorder,yMax);
    
        //drawLineThick(g,(double)(xMin),(double)(yOffset+state.s10),(double)(xMin),(double)(yMax),state.s1,Color.red.darker());
        //drawLineThick(g,(double)(xMin),(double)(yMax),(double)(xMax+xBorder),(double)(yMax),state.s1,Color.red.darker());
        drawLineThick(g,(double)(xMin-1),(double)(yOffset),(double)(xMin-1),(double)(yMax+1),state.s1,Color.black);
        drawLineThick(g,(double)(xMin-1),(double)(yMax+1),(double)(xMax+xBorder),(double)(yMax+1),state.s1,Color.black);
        
        g.setColor(Color.black);
        //g.setColor(Color.red.darker());
        MyArrows.drawDblUpArrow(g,xMin-1,yOffset,1.2*state.sfactor);
        MyArrows.drawDblRightArrow(g,xMax+xBorder+state.s10,yMax+1, 1.2*state.sfactor);
  }


  private void drawAxisTicks(Graphics g) {
      g2d = (Graphics2D)g;
      g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);

      // Draw x and y axis:
      g2d.setColor(Color.white);
      //g2d.setColor(Color.red.darker());
      g2d.setFont(TheFonts.sanSerif12);
      g2d.setFont(new Font("SanSerif",Font.BOLD,state.font12));

      int x, y;
      // the y-axis:
      for(int i=0;i<13;i++){
          //y = (int)(i*deltaY)+yOffset+yBorder;
          y = (int)(i*deltaY)+yMin;
          //g2d.drawLine(xMin-ticLength,y,xMin,y);
          //drawLineThick(g, (double)(xMin-ticLength), (double)(y), (double)(xMin), (double)(y), state.s2, Color.red.darker());
          drawLineThick(g, (double)(xMin-ticLength), (double)(y), (double)(xMin), (double)(y), state.s1, Color.black);
          
          String vs = dec.format(12-i);
          g.setColor(Color.black);
          //g.setColor(Color.red.darker());
          g2d.drawString(vs,xMin-ticLength-fm.stringWidth(vs)-state.s5,
                       y+(int)(0.5*fontHeight));
      }
      //g.drawString("y",xMin+ticLength,(int)(1.5*fontHeight)+yOffset-10);
      //g.drawString("nm",xOffset-2,(int)(1.5*fontHeight)+yOffset-12);
      //g.drawString("y,nm",0,(int)(fontHeight-5));

      // On the x-axis:
      for(int i=0;i<14;i++){
          x = (int)(i*deltaX)+xMin;
          //g2d.drawLine(x,yMax+ticLength,x,yMax);
          
        //drawLineThick(g, (double)(x), (double)(yMax+ticLength), (double)(x), (double)(yMax), state.s2, Color.red.darker());
        drawLineThick(g, (double)(x), (double)(yMax+ticLength), (double)(x), (double)(yMax), state.s1, Color.black);
        
          String xs = dec.format(i);
          g2d.drawString(xs,x-(int)(0.5*fm.stringWidth(xs)),
                       yMax+ticLength+fontHeight);
      }
      if(state.OS_Mac){
            g.setFont(new Font("SanSerif",Font.BOLD,state.font12));
        }
        else if(state.OS_Windows){
            g.setFont(new Font("SanSerif",Font.BOLD,state.font13));
        }
        else{
            g.setFont(new Font("SanSerif",Font.BOLD,state.font13));
        }
        
      g.drawString("y(nm)",state.s45,state.s20);
      
      //g.drawString("x",xMax-fm.stringWidth("x"),yMax-2);
      //g.drawString("nm",xMax+fm.stringWidth("nm"),yMax+ticLength+fontHeight-2);
      //g.drawString("x,nm",xMax+fm.stringWidth("m"),yMax+ticLength+fontHeight-2);
      g.drawString(" x(nm)",xMax,yMax+-state.s8);

      return;
  }


  public void update(Graphics g) { paint(g); }


  public void paint(Graphics g) {
    g.drawImage(im,0,0,this);
  }


  private void drawLine(Graphics g, int x1, int y1, int x2, int y2) {
      if (y1 != y2) {
          g.drawLine(x1,y1,x2,y2);
          g.drawLine(x1+1,y1,x2+1,y2);
      } else {
          g.drawLine(x1,y1,x2,y2);
          g.drawLine(x1,y1+1,x2,y2+1);
      }
  }

    private void drawPolyline(Graphics g, int[] xpts, int[] ypts, int len) {
        int x1, y1, x2, y2;
        for (int i = 0; i < (len-1); i++) {
            x1 = xpts[i]; y1 = ypts[i];
            x2 = xpts[i+1]; y2 = ypts[i+1];
            drawLine(g,x1,y1,x2,y2);
        }
    }


    private double square(double number) {
        double newNumber;
        newNumber = number*number;
        return newNumber;
    }
    
    public static void drawLineThick(Graphics g, double x1, double y1, double x2, double y2, int thick, Color color){
	
        Graphics2D g2d = (Graphics2D)g;
        g2d.setPaint(color);
        g2d.setStroke(new BasicStroke(thick,BasicStroke.CAP_BUTT,BasicStroke.JOIN_ROUND));
        
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
        
        Line2D.Double line = new Line2D.Double(x1,y1,x2,y2);
        g2d.draw(line);
  
        g2d.setStroke(new BasicStroke(1));
        //g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_OFF);
    }
    
    public static void drawLineThickB(Graphics g, double x1, double y1, double x2, double y2, int thick, int pat1, int pat2, Color color){
	
        Graphics2D g2d = (Graphics2D)g;
        g2d.setPaint(color);
        float[] dashPattern = {pat1,pat2};
        g2d.setStroke(new BasicStroke(thick,BasicStroke.CAP_BUTT,BasicStroke.JOIN_MITER,10.0F,dashPattern,0));
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
        
        Line2D.Double line = new Line2D.Double(x1,y1,x2,y2);
        g2d.draw(line);
 
        g2d.setStroke(new BasicStroke(1.0F,BasicStroke.CAP_BUTT,BasicStroke.JOIN_MITER));
        //g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_OFF);
    }
    
    public static void drawArcThick(Graphics g, double xCenter, double yCenter, double Radius, double startangle, double endangle, int thick, Color color){
	
            Graphics2D g2d = (Graphics2D)g;
            g2d.setPaint(color);
            g2d.setStroke(new BasicStroke(thick));
        
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
        
            Arc2D.Double arc_one = new Arc2D.Double(xCenter,yCenter,Radius,Radius,startangle,endangle,0);
            g2d.draw(arc_one);
  
            g2d.setStroke(new BasicStroke(1));
            //g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_OFF);
    }
    
    public static void fillArcThick(Graphics g, double xCenter, double yCenter, double Radius, double startangle, double endangle, int thick, Color color){
	
            Graphics2D g2d = (Graphics2D)g;
            g2d.setPaint(color);
            g2d.setStroke(new BasicStroke(thick));
        
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
        
            Arc2D.Double arc_one = new Arc2D.Double(xCenter,yCenter,Radius,Radius,startangle,endangle,0);
            g2d.fill(arc_one);
  
            g2d.setStroke(new BasicStroke(1));
            //g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_OFF);
    }

}



class Charge {
    public int xpos, ypos;  // position of charge, in grid coordinates
    public double charge;   // signed charge of each charge, nColoumbs
    public double elemcharge;  // signed charge of each charge as mult of elem charg
    public Point pixelPt;   // actual pixel point (NOT grid point)
    
    public Charge() {
        super();
    }

    public Charge(int xpos, int ypos, double charge, double elemcharge,
                  Point pt) {
        this.xpos = xpos;
        this.ypos = ypos;
        this.charge = charge;
        this.elemcharge = elemcharge;
        this.pixelPt = pt;
    }

    public Charge(Charge ci) {
        super();
        this.xpos = ci.xpos;
        this.ypos = ci.ypos;
        this.charge = ci.charge;
        this.elemcharge = ci.charge;
        this.pixelPt = ci.pixelPt;
    }
}


class ActualVector {
    double ee;
    double theta;

    public ActualVector() {
        super();
    }

    public ActualVector(double ee, double theta) {
        this.ee = ee;
        this.theta = theta;
    }
}
