import java.io.*;
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.font.*;
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 Mod2Plot extends Panel {

    private static final long serialVersionUID = 352386;

    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;

    int ncharges;  // # of charges in 2D area
    int xpos[], ypos[];  // positions of charges, in nm
    Vector<Integer> xposVector = new Vector<Integer>(); // grid x-coordinate
    Vector<Integer> yposVector = new Vector<Integer>(); // grid y-coordinate
    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<Double> chargeVector = new Vector<Double>();
    Vector<Double> elemChargeVector = new Vector<Double>();
    Vector<Point> pixelPtVector = new Vector<Point>();

    int numEquiLines;
    //private static final int EQUI_LINES_INIT = 15;
    //7;  // was 15
    private static final int EQUI_LINES_INIT = 16;   //15-7 X 2

    // 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 chDensity[];
    double chDensityMax;  // abs value
    static double epsilonOverDeltaY;
    double potentialMax, potentialMin;

    int max_number_charges = NMAX;

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

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

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

    //static Color bgColor = new Color(194,235,255);
    private final Color bgColor = new Color(236,236,236);
    private final Color darkerOrange = new Color(255,100,0);
    private final Color lightBlue = new Color(200,200,255);

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

    public Button small, swap;

    private Font chDFonts[] = new Font[5];
    private FontMetrics chDfms[] = new FontMetrics[5];
    private int chDWidths[] = new int[5];

    public static String swapLabel1 = "  View  Image Charges ";
    public static String swapLabel2 = "Return to original view ";


    public Mod2Plot(Mod2State state) {
        super();
        this.state = state;
    }

    public void initialPlot() {
        setLayout(null);
        setBackground(bgColor);
        //im = createImage(mainWidth,mainHeight);
        //imG = im.getGraphics();

        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice gs = ge.getDefaultScreenDevice(); 
        GraphicsConfiguration gc = gs.getDefaultConfiguration();
        im = gc.createCompatibleImage(mainWidth, mainHeight, Transparency.TRANSLUCENT);
        imG = im.getGraphics();

        small = new Button("Instructions");
        add(small);
	small.setBackground(Color.white);
	//small.setBounds(405,7,80,20);
	small.setBounds(540,7,80,20);
        small.setFont(TheFonts.sanSerif11);

        swap = new Button(swapLabel1);
        add(swap);
        swap.setBackground(Color.white);
        swap.setBounds(45,7,140,20);
        swap.setFont(TheFonts.sanSerif11);

        //size = numberPixelsPerGrid;    // grid size (?) OR max abscharge value?
        numEquiLines = EQUI_LINES_INIT;

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

        xMin = xBorder+xOffset;
        //xMax = 410;
        xMax = 589;
        xMid = (int)(0.5*(xMax+xMin));
        yMin = yBorder+yOffset; 
        //yMax = 460;
        yMax = 546;
        yMid = (int)(0.5*(yMax+yMin));

        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;
        epsilonOverDeltaY = 8.85E-12/(numberPixelsPerGrid*nmPerPixel) *
            1E9;  // last number to go from nm to m

        //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.


        //chDFonts[0] = TheFonts.sanSerif9;
        chDFonts[0] = TheFonts.sanSerif10;
        chDFonts[1] = TheFonts.sanSerif12;
        chDFonts[2] = TheFonts.sanSerif14;
        chDFonts[3] = TheFonts.sanSerif16;
        chDFonts[4] = TheFonts.sanSerif18;
        for (int i = 0; i < 5; i++) {
            chDfms[i] = getFontMetrics(chDFonts[i]);
            chDWidths[i] = chDfms[i].stringWidth("+");
        }

        paintImage();
        repaint();
  }



    // want sliderVal to go from 15 to 1
    public boolean changeDeltaV(int sliderVal) {
        int newNumEquiLines;
        //newNumEquiLines = (int) ((sliderVal+1) * 1.5);
        newNumEquiLines = (sliderVal+1) * 2;
        if (newNumEquiLines != numEquiLines) {
            numEquiLines = newNumEquiLines;
            return true;
        }
        return false;
    }


    /*
    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 getVoltageAtCursor(Point pt) {
        // convert pt from pixels to "places" on this big plot
        // get voltage AND electric field at this place
        
        if (state.currentPlotType == state.plotWithGroundPlane &&
            ptBelowPlane(pt)) {
            state.voltageAtCursor = 0.0;
            state.efieldAtCursor = 0.0;
            state.efieldThetaAtCursor = 0.0;
            if (pt.y > (yMid + 5))
                state.chargeDensityAtCursor = 0.0;
            else
                state.chargeDensityAtCursor = getChargeDensityAtPoint(pt);
        } else {
            //pt.x /= 1.11;
            Point gridPt = getGridPointFromPixelPoint(pt);
            state.voltageAtCursor = getPotentialValueAtPoint(gridPt);
            //state.efieldAtCursor = getEfieldValueAtPoint(gridPt);
            ActualVector efield = getEfieldValueAtPoint(gridPt);
            state.efieldAtCursor = efield.ee;
            state.efieldThetaAtCursor = efield.theta;
            if (pt.y >= (yMid - 5) && pt.y <= (yMid + 5))
                state.chargeDensityAtCursor = getChargeDensityAtPoint(pt);
            else
                state.chargeDensityAtCursor = 0.0;
        }
        return;
    }

    public boolean moveCharge() {
        // convert state.moveFromPoint and state.moveToPoint to
        // the "places" (grids) on this big plot

        if (ptBelowMovingPlane(state.moveToPoint)) {
            state.moveToPoint.y = yMid-3;
            return false;
        }
        if (state.moveFromPoint == state.moveToPoint)
            return false;

        //if (ptBelowMovingPlane(state.moveFromPoint) ||
        //    ptBelowMovingPlane(state.moveToPoint)) return false; // invalid command
        Point fromGridPt, toGridPt, fromGridPt2, toGridPt2;

        /*
        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);
        

        Point fromPt2 = getMirroredPoint(fromPt);
        Point toPt2 = getMirroredPoint(toPt);
        fromGridPt = getGridPointFromPixelPoint(fromPt);
        toGridPt = getGridPointFromPixelPoint(toPt);
        fromGridPt2 = getGridPointFromPixelPoint(fromPt2);
        toGridPt2 = getGridPointFromPixelPoint(toPt2);
        double chargeToMove1 = 0.0;
        double elemChargeToMove1 = 0.0;
        double chargeToMove2 = 0.0;
        double elemChargeToMove2 = 0.0;

        //if (state.moveFromPoint.y < yMid-6)
        int i = getClosestIndex(fromGridPt);
        if (i == -1) return false;
        chargeToMove1 += (chargeVector.elementAt(i)).doubleValue();
        elemChargeToMove1 += (elemChargeVector.elementAt(i)).doubleValue();
        chargeVector.removeElementAt(i);
        elemChargeVector.removeElementAt(i);
        xposVector.removeElementAt(i);
        yposVector.removeElementAt(i);
        pixelPtVector.removeElementAt(i);

        i = getClosestIndex(fromGridPt2);
        if (i == -1) return false;
        chargeToMove2 += (chargeVector.elementAt(i)).doubleValue();
        elemChargeToMove2 += (elemChargeVector.elementAt(i)).doubleValue();
        chargeVector.removeElementAt(i);
        elemChargeVector.removeElementAt(i);
        xposVector.removeElementAt(i);
        yposVector.removeElementAt(i);
        pixelPtVector.removeElementAt(i);

        if (elemChargeToMove1 != 0) {
            xposVector.addElement(new Integer(toGridPt.x));
            yposVector.addElement(new Integer(toGridPt.y));
            chargeVector.addElement(new Double(chargeToMove1));
            elemChargeVector.addElement(new Double(elemChargeToMove1));
            pixelPtVector.addElement(new Point(toPt));
        }
        if (elemChargeToMove2 != 0) {
            xposVector.addElement(new Integer(toGridPt2.x));
            yposVector.addElement(new Integer(toGridPt2.y));
            chargeVector.addElement(new Double(chargeToMove2));
            elemChargeVector.addElement(new Double(elemChargeToMove2));
            pixelPtVector.addElement(new Point(toPt2));
        }
        return true;  // move successful
    }



    private int getClosestIndex(Point pt) {
        int index = -1;
        int x, y, sumdiff, diff;
        sumdiff = 1000;
        for (int i = 0; i < xposVector.size(); i++) {
            x = (xposVector.elementAt(i)).intValue();
            y = (yposVector.elementAt(i)).intValue();
            if (withinReasonableRange(x,y,pt.x,pt.y)) {
                diff = Math.abs(x-pt.x) + Math.abs(y-pt.y);
                if (diff < sumdiff) {
                    index = i;
                    sumdiff = diff;
                }
            }
        }
        return index;
    }


    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;
        xposVector = new Vector<Integer>();
        yposVector = new Vector<Integer>();
        chargeVector = new Vector<Double>();
        elemChargeVector = new Vector<Double>();
        pixelPtVector = new Vector<Point>();
        state.moveFromPoint = new Point(-1,-1);
        state.moveToPoint = new Point(-1,-1);
        //imG.clearRect(0,0,mainWidth,mainHeight);
        imG.setColor(bgColor);
        imG.fillRect(0,0,mainWidth,mainHeight);
    }

    public void placeCharge(Point pt) {
        // convert pt from pixels to "places" on this big plot
        // place the current charge at this "place" AND also the
        // opposite charge at the y=-y spot below the plane.
        if (ptBelowPlane(pt)) return; // invalid command
        Point gridPt;
        //pt.x /= 1.11;
        gridPt = getGridPointFromPixelPoint(pt);
        xposVector.addElement(new Integer(gridPt.x));
        yposVector.addElement(new Integer(gridPt.y));
        chargeVector.addElement(new Double(state.chargeToAdd*
                                           state.elementaryCharge));
        elemChargeVector.addElement(new Double(state.chargeToAdd));
        pixelPtVector.addElement(new Point(pt));

        Point pt2 = getMirroredPoint(pt);
        gridPt = getGridPointFromPixelPoint(pt2);
        xposVector.addElement(new Integer(gridPt.x));
        yposVector.addElement(new Integer(gridPt.y));
        chargeVector.addElement(new Double(-1.0*state.chargeToAdd*
                                           state.elementaryCharge));
        elemChargeVector.addElement(new Double(-1.0*state.chargeToAdd));
        pixelPtVector.addElement(new Point(pt2));

        //imG.setColor(Color.black);
        //imG.fillOval(pt.x-3,pt.y-3,7,7);
        //imG.fillOval(pt2.x-3,pt2.y-3,7,7);
        //repaint();
        return;
    }



    public void changeCharge(Point pt) {
        // convert pt from pixels to "places" on this big plot
        if (ptBelowPlane(pt)) return; // invalid command
        Point gridPt, gridPt2;
        //pt.x /= 1.11;
        gridPt = getGridPointFromPixelPoint(pt);
        Point pt2 = getMirroredPoint(pt);
        gridPt2 = getGridPointFromPixelPoint(pt2);
        Double c,e;
        for (int i = 0; i < xposVector.size(); i++) {
            xpos[i] = (xposVector.elementAt(i)).intValue();
            ypos[i] = (yposVector.elementAt(i)).intValue();
            //if (xpos[i] == gridPt.x && ypos[i] == gridPt.y) {
            if (withinReasonableRange(xpos[i],ypos[i],gridPt.x,gridPt.y)) {
                c = new Double(state.chargeToAdd*state.elementaryCharge);
                e = new Double(state.chargeToAdd);
                chargeVector.set(i,c);
                elemChargeVector.set(i,e);
            } else if (withinReasonableRange(xpos[i],ypos[i],gridPt2.x,gridPt2.y)) {
                c = new Double(-1*state.chargeToAdd*state.elementaryCharge);
                e = new Double(-1*state.chargeToAdd);
                chargeVector.set(i,c);
                elemChargeVector.set(i,e);
            }                
        }
        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"
        if (ptBelowPlane(pt)) return; // invalid command
        Point gridPt, gridPt2;
        //pt.x /= 1.11;
        gridPt = getGridPointFromPixelPoint(pt);
        Point pt2 = getMirroredPoint(pt);
        gridPt2 = getGridPointFromPixelPoint(pt2);
        for (int i = 0; i < xposVector.size(); i++) {
            xpos[i] = (xposVector.elementAt(i)).intValue();
            ypos[i] = (yposVector.elementAt(i)).intValue();
            //if (xpos[i] == gridPt.x && ypos[i] == gridPt.y) {
            if (withinReasonableRange(xpos[i],ypos[i],gridPt.x,gridPt.y)) {
                chargeVector.removeElementAt(i);
                elemChargeVector.removeElementAt(i);
                xposVector.removeElementAt(i);
                yposVector.removeElementAt(i);
                pixelPtVector.removeElementAt(i);
                i = -1;
            } else if (withinReasonableRange(xpos[i],ypos[i],gridPt2.x,gridPt2.y)) {
                chargeVector.removeElementAt(i);
                elemChargeVector.removeElementAt(i);
                xposVector.removeElementAt(i);
                yposVector.removeElementAt(i);
                pixelPtVector.removeElementAt(i);
                i = -1;
            }                
        }        
        return;
    }


    private boolean ptBelowMovingPlane(Point pt) {
        if (pt.y > yMid-3) return true;
        else return false;
    }


    private boolean ptBelowPlane(Point pt) {
        if (pt.y > yMid-1) return true;
        else return false;
    }


    private Point getMirroredPoint(Point pt) {
        Point newPt = new Point(pt.x,yMid+(yMid-pt.y)+3);
        return newPt;
    }
        

    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 double getPotentialValueAtPoint(Point pt) {
        // pt already a grid pt, NOT a pixel point
        return potential[pt.x][pt.y];
    }

    private ActualVector getEfieldValueAtPoint(Point pt) {
        double exi, eyi, ex, ey, ee, ri, riSquared, ei;
        double vmax = state.vmax;
        int x = pt.x;
        int y = pt.y;
        ex = 0.0;  ey = 0.0;
        double arbitraryMax = 50.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;
                ri = Math.sqrt(riSquared);
                exi = ((x-xpos[k])/ri) * ei;
                eyi = ((y-ypos[k])/ri) * 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 *= gridsPerNmOver4piepsilon;
        ey *= gridsPerNmOver4piepsilon;
        //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));
        double theta = Math.atan2(-ey,ex);
        ActualVector efield = new ActualVector(ex, ey, ee,theta);
        return efield;
    }



    private double getChargeDensityAtPoint(Point pt) {
        // pt is a pixel point
        Point gridPt = getGridPointFromPixelPoint(new Point(pt.x,yMid));
        ActualVector efield = getEfieldValueAtPoint(gridPt);
        double eyValue = efield.ey;
        // (mult by -1 because decreasing y -> increasing y pixel value)
        double chD = epsilonOverDeltaY*eyValue*-1.0;
        return chD;
    }



    public synchronized void drawPlot() {
        //CALL draw_labelled_axes(ncharges, xpos, ypos, xrange, yrange,
        //&     outfile,xoff, yoff, size)
        Object obj;

        if (state.currentAction == state.placeCharge ||
            state.currentAction == state.removeCharge ||
            state.currentAction == state.moveCharge ||
            state.currentAction == state.changeChargeValue) {

            xpos = new int[xposVector.size()];
            ypos = new int[xpos.length];
            ncharges = chargeVector.size();
            charge = new double[ncharges];
            elemcharge = new double[ncharges];
            pixelPt = new Point[ncharges];
            for (int i=0; i<xpos.length; i++) {
                obj = xposVector.elementAt(i);
                xpos[i] = ((Integer)obj).intValue();
                obj = yposVector.elementAt(i);
                ypos[i] = ((Integer)obj).intValue();
                pixelPt[i] = pixelPtVector.elementAt(i);
            }
            for (int i=0; i<charge.length; i++) {
                obj = chargeVector.elementAt(i);
                charge[i] = ((Double)obj).doubleValue();
                obj = elemChargeVector.elementAt(i);
                elemcharge[i] = ((Double)obj).doubleValue();
            }
        }

        //imG.clearRect(0,0,mainWidth,mainHeight);

        imG.setColor(bgColor);
        imG.fillRect(xMin,yMin,xMax-xMin,yMax-yMin);

        if (state.currentPlotType == state.plotWithoutGroundPlane) {
            imG.setColor(lightBlue);
            imG.fillRect(xMin,yMid,xMax-xMin,yMax-yMid);
        }

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

        if (state.currentAction == state.placeCharge ||
            state.currentAction == state.removeCharge ||
            state.currentAction == state.moveCharge ||
            state.currentAction == state.changeChargeValue)
            calculate_potential_field();
        if (state.displayPotentialField) 
            draw_potential_field();
        if (state.displayEquiPotentialLines) 
            draw_isopotential_lines();



        // This whole section is NEW:
        if (state.currentPlotType == state.plotWithGroundPlane) {

            // clear below image for y < 0
            //imG.clearRect(0,yMid,mainWidth,mainHeight-yMid);
            //imG.setColor(Color.lightGray);
            //imG.fillRect(xMin,yMid,xMax-xMin,yMax-yMid);
            //imG.setColor(Color.black);

            // clear below image for y < 0
            imG.setColor(bgColor);
            imG.fillRect(0,yMid,mainWidth,mainHeight-yMid);
            imG.setColor(Color.lightGray);
            imG.fillRect(xMin,yMid,xMax-xMin,yMax-yMid);
            imG.setColor(Color.black);
        }



        if (state.displayElectricField)
            draw_electricfield_vectors();
        draw_charges();

        if (state.currentPlotType == state.plotWithGroundPlane &&
            state.displayChargeDensity) {
            calculateChargeDensity();
            plotChargeDensity();   // NEW -----------------
        }
        paintImage();
        repaint();
    }


    private void calculate_potential_field() {
        double x, y;
        int i, j, k;
        double v, vi;
        //double vmax = 50;
        //double vmax = 10;
        //double vmax = 6*state.elementaryCharge;
        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;
                v = 0.0;
                for (k = 0; k < ncharges; k++) {
                    if (x == xpos[k] && y == ypos[k]) {
                        //if (charge[k] >= 0.0) vi = vmax;
                        //else vi = -1.0*vmax;
                        vi = charge[k];
                    } else {
                        vi = charge[k]/Math.sqrt(square(x-xpos[k])+
                                                 square(y-ypos[k]));
                    }
                    
                    if (Math.abs(vi) < vmax)
                        v = v + vi;
                    else {
                        if (vi > 0.0) 
                            v = vmax;
                        else v = -1.0*vmax;
                    }
               }
                v *= gridsPerNmOver4piepsilon;
                if (Math.abs(v) > state.potentialMax) {
                  if (v > 0.0) v = state.potentialMax;
                  else v = -1.0*state.potentialMax;
                }
                potential[i][j] = v;
                if (potential[i][j] > potentialMax)
                    potentialMax = potential[i][j];
                if (potential[i][j] < potentialMin)
                    potentialMin = potential[i][j];
            }
        }

        return;
    }

    
    
    private void draw_potential_field() {
        int i, j;
        double colorValue;
        int index;
        Color color;

        Graphics2D g2d = (Graphics2D)imG;
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                             RenderingHints.VALUE_ANTIALIAS_OFF);
        
        // NOTE: potential is -100 to +100
        for (i=0; i<nxgrid; i++)
            for (j=0; j<nygrid; j++)
                if (potential[i][j] >= 0.0) {
                    index = (int)(10*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)(-10.0*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_isopotential_lines() {
        double deltav;
        double x[] = new double[1000];
        double y[] = new double[1000];
        double rmax, rmin;
        int i, j;
        double contours[] = new double[1000];
        int ncontours;
        Color lineColor = Color.orange;

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


        for (i=0; i<nxgrid; i++)
            x[i] = i;
        for (j=0; j<nygrid; j++)
            y[j] = j;

        rmax = potentialMax;
        rmin = potentialMin;
        deltav = (rmax - rmin)/(numEquiLines-1);
        //rmax = potential[0][0];
        //rmin = rmax;
        //for (i=0; i<nxgrid; i++)
        //    for (j=0; j<nygrid; j++) {
        //        if (potential[i][j] > rmax) rmax = potential[i][j];
        //        if (potential[i][j] < rmin) rmin = potential[i][j];
        //    }

        ncontours = 0;
        contours[ncontours] = 0.0;
        while (contours[ncontours] < rmax && ncontours < 1000) {
            ncontours++;
            if (ncontours < 1000)
                contours[ncontours] = contours[ncontours-1]+deltav;
        }
        if (contours[ncontours] <= rmax) ncontours++;

        if (ncontours < 1000)
            contours[ncontours] = contours[0]-deltav;
        while (contours[ncontours] > rmin && ncontours < 1000) {
            ncontours++;
            if (ncontours < 1000)
                contours[ncontours] = contours[ncontours-1]-deltav;
        }
        if (contours[ncontours] < rmin) ncontours--;

        // redo contour values to be logarithmic rather than linear
        int index;
        double zeroToOne;
        double scaler1 = 1000/rmax;
        double scaler2 = 1000/rmin;
        for (i=0; i<=ncontours; i++) {
            if (contours[i] > 0) {
                index = (int)(1000 - contours[i]*scaler1);
                zeroToOne = state.lookup[index];
                contours[i] = (1.0-zeroToOne)*rmax;
            } else {
                index = (int)(1000 - contours[i]*scaler2);
                zeroToOne = state.lookup[index];
                contours[i] = (1.0-zeroToOne)*rmin;
            }
        }
        
        plot_contours(x,y,contours,ncontours);
        return;
    }



    private void draw_electricfield_vectors() {
        //int stride = 15;
        int stride = 10;
        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;

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

        len1 = nxgrid/stride +1;

        //if (state.currentPlotType == state.plotWithGroundPlane)
        //    len2 = (nygrid/stride)/2 +1;
        //else
            len2 = nygrid/stride +1;
        efieldx = new double[len1][len2];
        efieldy = new double[len1][len2];

        int nygridMax;
        if (state.currentPlotType == state.plotWithGroundPlane)
            nygridMax = 86 + 3;
        else
            nygridMax = nygrid;

        Emax = 0.0;
        ii = 0;
        //for (i = 3; i < nxgrid && ii<len1; i+=stride) {
        for (i = 8; i < nxgrid && ii<len1; i+=stride) {
            x = i;
            jj = 0;
            //for (j = 3; j < nygrid && jj<len2; j+=stride) {
            for (j = 8; j < nygridMax && 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;
                        ri = Math.sqrt(riSquared);
                        exi = ((x-xpos[k])/ri) * ei;
                        eyi = ((y-ypos[k])/ri) * 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;
                    }
                }



                if ((j+stride) >= nygridMax) ex = 0.0;




                ex *= gridsPerNmOver4piepsilon;
                ey *= gridsPerNmOver4piepsilon;
                if (Math.abs(ex) > state.efieldMax) {
                    if (ex > 0.0) ex = state.efieldMax;
                    else ex = -1.0*state.efieldMax;
                }
                if (Math.abs(ey) > state.efieldMax) {
                    if (ey > 0.0) ey = state.efieldMax;
                    else ey = -1.0*state.efieldMax;
                }
                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] = -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) {
        for (i=8; i<nxgrid && ii < len1; i+=stride) {
            jj = 0;
            //for (j=3; j<nygrid && jj < len2; j+=stride) {
            for (j=6; j<nygridMax && 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;

        for (i=0; i<ncharges; i++) {
            //imG.setColor(Color.orange);
            Point pt = pixelPt[i];


            if (state.currentPlotType == state.plotWithGroundPlane &&
                pt.y > yMid) continue;
            


            if (pt.y > yMid) 
                imG.setColor(darkerOrange);
            else
                imG.setColor(Color.orange);

            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+9,pt.y+5);
        }
        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(bgColor);
      imG.fillRect(0,0,xMin,mainHeight);
      imG.fillRect(0,0,mainWidth,yMin);
      imG.fillRect(0,yMax,mainWidth,mainHeight-yMax);
      imG.fillRect(xMax,0,mainWidth-xMax,mainHeight);


      if (state.currentPlotType == state.plotWithGroundPlane 
          && ncharges == 0) {

          // clear below image for y < 0
          //imG.clearRect(0,yMid,mainWidth,mainHeight-yMid);
          imG.setColor(bgColor);
          imG.fillRect(0,yMid,mainWidth,mainHeight-yMid);

          // NEW
          imG.setColor(Color.lightGray);
          imG.fillRect(xMin,yMid,xMax-xMin,yMax-yMid);
          imG.setColor(Color.black);
          if (state.displayChargeDensity) plotChargeDensity();
      } else {
          //  mirrored charges will show up on lower half, for y < 0
      }

      drawAxis(imG);
      drawAxisTicks(imG);

      imG.drawString("Dielectric", xMin+20,yMid - 20);
      imG.drawString("Conductor", xMin+20, yMid + 40);
  }


    private void calculateChargeDensity() {
        double eyValue;
        Point gridPt;
        chDensity = new double[nxgrid];
        Point pt = new Point(xMin,yMid);
        gridPt = getGridPointFromPixelPoint(pt);
        chDensityMax = 0;
        for (int i=0; i<nxgrid; i++) {
            ActualVector efield = getEfieldValueAtPoint(gridPt);
            eyValue = efield.ey;
            chDensity[i] = epsilonOverDeltaY*eyValue*-1.0; // -y, higher pixel
            if (Math.abs(chDensity[i]) > chDensityMax)
                chDensityMax = Math.abs(chDensity[i]);
            gridPt.x++;
        }
        return;
    }


    private void OLDcalculateChargeDensity() {
        double pValue1, pValue2;
        Point gridPt1, gridPt2;
        chDensity = new double[nxgrid];
        Point pt = new Point(xMin,yMid);
        gridPt1 = getGridPointFromPixelPoint(pt);
        gridPt2 = new Point(gridPt1.x,gridPt1.y-1);
        chDensityMax = 0;
        for (int i=0; i<nxgrid; i++) {
            pValue1 = getPotentialValueAtPoint(gridPt1);
            pValue2 = getPotentialValueAtPoint(gridPt2);
            chDensity[i] = (pValue2 - pValue1)*epsilonOverDeltaY;
            if (Math.abs(chDensity[i]) > chDensityMax)
                chDensityMax = Math.abs(chDensity[i]);
            gridPt1.x++;
            gridPt2.x++;
        }
        return;
    }



    private void plotChargeDensity() {
        if (chDensity == null) return;
        if (chDensity.length == 0) return;
        double chDLevels[] = new double[5];
        double scale = chDensityMax/5.0;
        //double level = scale;
        double level = 0;
        int index;
        Graphics2D g2d = (Graphics2D)imG;
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                             RenderingHints.VALUE_ANTIALIAS_ON);
        for (int i=0; i<5; i++) {
            chDLevels[i] = level;
            level += scale;
        }
        int x, y;
        imG.setColor(Color.red);
        for (int i = 5; i<chDensity.length; i+=5) {
            index = getIndexForChDensity(chDensity[i],chDLevels);
            imG.setFont(chDFonts[index]);
            x = xMin+(i*numberPixelsPerGrid - chDWidths[index]/2);
            //y = yMid - chDWidths[index];
            y = yMid;

            if (chDensity[i] > 0)
                imG.drawString("+",x,y);
            else
                imG.drawString("-",x,y);
        }
        imG.setColor(Color.black);
    }


    private int getIndexForChDensity(double val, double levels[]) {
        double n = Math.abs(val);
        int i;
        for (i = 0; i < levels.length - 1; i++)
            if (n > levels[i] && n < levels[i+1]) break;
        return i;
    }



    private void OLDplotChargeDensity() {
        if (chDensity == null) return;
        if (chDensity.length == 0) return;
        int x[] = new int[chDensity.length];
        int y[] = new int[chDensity.length];
        double yscale = 40/chDensityMax;
        double xscale = 3.0; // every 3 pixels
        for (int i=0; i<chDensity.length; i++) {
            x[i] = xMin+(int)(i*xscale);
            y[i] = yMid - (int)(chDensity[i]*yscale);
        }
        Graphics2D g2d = (Graphics2D)imG;
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                             RenderingHints.VALUE_ANTIALIAS_ON);
        imG.setColor(Color.orange);
        //drawPolyline(g,x,y,x.length);
        drawPolyline(g2d,x,y,x.length);

        // Labelled axis
        imG.setColor(Color.black);
        drawLine(imG,xMax+4,yMid+50,xMax+4,yMid-50);
        imG.drawLine(xMax-4,yMid-40,xMax+4,yMid-40);
        imG.drawLine(xMax-4,yMid,xMax+4,yMid);
        imG.drawLine(xMax-4,yMid+40,xMax+4,yMid+40);
        imG.setFont(TheFonts.sanSerif11);
        String s = "\u03c1";
        int endingX = STR.displayString(s,imG,xMax+2,yMid-60);

        s = dec2.format(chDensityMax);
        endingX = STR.displayString(s,imG,xMax+6,yMid-35);
        s = dec2.format(-1.0*chDensityMax);
        endingX = STR.displayString(s,imG,xMax+6,yMid+50);
        endingX = STR.displayString("0",imG,xMax+6,yMid);

        s = "C";
        endingX = STR.displayString(s,imG,xMax+16,yMid-65);
        imG.drawLine(xMax+14,yMid-60,xMax+24,yMid-60);
        s = "m"+STR.SUP+"2"+STR.ENDSUP;
        endingX = STR.displayString(s,imG,xMax+14,yMid-50);
    }




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

    // Draw x and y axis:
    g.setColor(Color.black);
    drawLine(g,xMin,yOffset,xMin,yMax); // the y-axis
    drawLine(g,xMin,yMid,xMax+xBorder,yMid); // the x-axis
    MyArrows.drawDblUpArrow(g,xMin,yOffset);
    MyArrows.drawDblRightArrow(g,xMax+xBorder,yMid);
  }



  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.black);
      g2d.setFont(TheFonts.sanSerif12);

      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);
          //String vs = dec.format(12-i);
          String vs = dec.format(6-i);
          if (i != 6)
              g2d.drawString(vs,xMin-ticLength-fm.stringWidth(vs)-2,
                             y+(int)(0.5*fontHeight));
      }
      g.drawString("y(nm)",0,fontHeight-5);
      

      // On the x-axis:
      for(int i=1;i<14;i++){
          x = (int)(i*deltaX)+xMin;
          g2d.drawLine(x,yMid+ticLength,x,yMid);
          String xs = dec.format(i);
          g2d.drawString(xs,x-(int)(0.5*fm.stringWidth(xs)),
                       yMid+ticLength+fontHeight-2);
      }
      g.drawString(" x(nm)",xMax,yMid+ticLength+fontHeight+6);

      g2d.setFont(TheFonts.bold16);      
      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);
        }
    }
    


    // Draws isolines of the given 2D function at the given function values.
    // Each of the contour lines is made up of a number of segments,
    // each of which goes from one edge of a "pixel" to another.
    // The pixels are defined as the edges of the grid defined by {\tt xvec} 
    //  and {\tt yvec}.  (NOTE: a "pixel" is really multiple pixels - scaled)

    // REAL z(nx*ny) ! arranged: (iy-1)*nx + ix
    // Above no longer true; now using potential[nx][ny]
    public void plot_contours(double xvec[], double yvec[],
                              double contours[], int ncontours) {
        int i, j, jj;
        int L;
        double xdenom, ydenom;
        double xmin1, xmax1, ymin1, ymax1;
        //double xscale, yscale;
        int jstart, jend;

        double zvalue;
        int n;
        double x[];
        double y[];

        int nx = nxgrid;
        int ny = nygrid;
        double xmin = xrange[0];
        double xmax = xrange[1];
        double ymin = yrange[0];
        double ymax = yrange[1];

        int ilep=0;

        //xscale = p_plotsize_inches(1);
        //yscale = p_plotsize_inches(2);

        imG.setColor(Color.black);

        // scale the input vectors
        //xdenom = xmax - xmin;
        //ydenom = ymax - ymin;
        xdenom = nxgrid;
        ydenom = nygrid;
        
        for (i = 0; i < nx; i++) {
            xvec[i] = xvec[i] / (xdenom - 1);  // scale from 0 to 1
        }
        for (i = 0; i < ny; i++)
            yvec[i] = yvec[i] / (ydenom - 1);  // scale from 0 to 1

        for (i=0; i<=ncontours; i++) {
            zvalue = contours[i];
            ContourReturn c = get_contour_line(xvec,yvec,nx,ny,zvalue);
            x = c.getX();
            y = c.getY();
            n = c.n;
            
            //xscale = Math.min(mainWidth-(int)xoff,mainHeight-(int)yoff);
            //yscale = xscale;
            xscale = (int)(xrange[1]-xrange[0]);
            yscale = (int)(yrange[1]-yrange[0]);

            for (j=0; j<x.length-1; j+=2) {
                imG.drawLine((int)(x[j]*xscale+xrange[0]),
                             (int)(y[j]*yscale+yrange[0]),
                             (int)(x[j+1]*xscale+xrange[0]),
                             (int)(y[j+1]*yscale+yrange[0]));
            }
        }
        
        //  Now unscale the input vectors:
        for (i = 0; i < nx; i++)
            xvec[i] *= xdenom;
        for (i = 0; i < ny; i++)
            yvec[i] *= ydenom;
        return;
    }


    // Find all line segments in a contour line.
    // USES:
    //     get_segments
    // CALLED FROM:
    //     plot_contours
    
    // Returns x,y coords of iso-z-line specified by the given zvalue.
    // Also returns number of points in this line: n.
    /*  The line is actually many segments: moveto/lineto.
        Since there could be more than one "connected line segment"
        for any zvalue, we just return these segments.
        Figuring out how the segments are arranged into
        connected line segments is not attempted.  */

    private ContourReturn get_contour_line(double xvec[], double yvec[],
                                           int nx, int ny, double zvalue) {
        int i, j, k;
        int nmax;
        double xs[] = new double[10];
        double ys[] = new double[10];
        int nsegs;
        double x1, x2, y1, y2;
        double z11, z12, z21, z22;

        // OUTPUT PARAMETERS,  INSIDE ContourReturn
        //double x[], y[];  // actually Vectors
        //int n;
        ContourReturn c = new ContourReturn();
        
        nmax = NMAX;
        c.n = 0;   // was 0

        nx = nxgrid; // debug
        ny = nygrid; // debug
        for (i=0; i<(nx-1); i++) {
            x1 = xvec[i];
            x2 = xvec[i+1];
            for (j=0; j<(ny-1); j++) {
                y1 = yvec[j];
                y2 = yvec[j+1];
                //z11 = z[(j  )*nx + i  ];
                //z12 = z[(j+1)*nx + i  ];
                //z21 = z[(j  )*nx + i+1];
                //z22 = z[(j+1)*nx + i+1];
                z11 = potential[i][j];
                z12 = potential[i][j+1];
                z21 = potential[i+1][j];
                z22 = potential[i+1][j+1];
                SegmentReturn s = get_segments(x1,x2,y1,y2,
                                               z11,z12,z21,z22,zvalue);
                for (k=0; k<(s.len-1); k+=2) {
                    //x[n] = xs[k];
                    //y[n] = ys[k];
                    c.x.addElement(new Double(s.xs[k]));
                    c.y.addElement(new Double(s.ys[k]));
                    //x[n] = xs[k+1];
                    //y[n] = ys[k+1];
                    c.x.addElement(new Double(s.xs[k+1]));
                    c.y.addElement(new Double(s.ys[k+1]));
                    c.n++;
                    c.n++;
                } // endfor
            }
        }
        return c;
    }


    // Find all line segments in a pixel.
    //
    // CALLED FROM:
    //     get_contour_line
    //
    /* ! A pixel has 4 edges, defined by the 4 variables {\tt x1}, {\tt x2},
       {\tt y1}, and {\tt y2}.
       The values of the function at each corner of the pixel are defined
       by the variables {\tt z11}, {\tt z12}, {\tt z21}, and {\tt z22}.
       The first subscript refers to the x-coordinate, the second to the y-coordinate.
       This is shown in the figure below:

                    z12            z22
       y2         *---------------*
                  |               |
                  |               |
                  |               |
                  |               |
                  |               |
                  | z11           | z21
       y1         *---------------*
       
                  x1              x2
       
       If the z-value is between any pair of z's on the edges, we note where
       along that edge that would occur.
       If there are 2 of these, we connect them with a line segment and return it.
       If there are 3 or 4 of these, we connect them, somewhat arbitrarily,
       and return multiple line segments.
    */
    
    private SegmentReturn get_segments(double x1,double x2,double y1,double y2,
                                       double z11,double z12,double z21,double z22,
                                       double zvalue) {

        // OUTPUT STUFF:  inside SegmentReturn
        //double xs[] = new double[10];
        //double ys[] = new double[10];
        //int nsegs;
        // END OUTPUT STUFF
        SegmentReturn s = new SegmentReturn();

        double zmin, zmax, m1, m2;
        int n_intercepts;
        double xint[] = new double[10];
        double yint[] = new double[10];
        double frac;

        m1 = Math.min(z11, z12);
        m2 = Math.min(z21, z22);
        zmin = Math.min(m1, m2);
        m1 = Math.max(z11, z12);
        m2 = Math.max(z21, z22);
        zmax = Math.max(m1, m2);


        //System.out.println("get_segments: start");

        if (zvalue < zmin || zvalue > zmax) {
            // contour not possible
            s.nsegs = 0;
            return s;
        }

        // go through each edge of rectangle, finding intercepts with zvalue:
        n_intercepts=-1;

        // 1. bottom edge:
        //zmin = Math.min(z11, z21);
        //zmax = Math.max(z11, z21);
        int minat;
        if (z11 < z21) {
            zmin = z11;
            zmax = z21;
            minat = 1;
        } else {
            zmin = z21;
            zmax = z11;
            minat = 2;
        }
        if (zvalue >= zmin && zvalue <= zmax &&
            Math.abs(zmax-zmin) >= 1.0E-6) {
            n_intercepts= n_intercepts+1;
            frac=(x2-x1)*(zvalue-zmin)/(zmax-zmin);
            //if (Math.abs(zmin-z11) <= 0.1*Math.abs(zmin)) {
            if (minat == 1) {
                // zmin is at x1:
                xint[n_intercepts] = x1 + frac;
            } else {
                // zmin is at x2:
                xint[n_intercepts] = x2 - frac;
            }
            yint[n_intercepts] = y1;
        }

        // 2. left edge:
        //zmin = Math.min(z11,z12);
        //zmax = Math.max(z11,z12);
        if (z11 < z12) {
            zmin = z11;
            zmax = z12;
            minat = 1;
        } else {
            zmin = z12;
            zmax = z11;
            minat = 2;
        }
        if (zvalue >= zmin && zvalue <= zmax &&
            Math.abs(zmax-zmin) >= 1.0E-6) {
            n_intercepts= n_intercepts+1;
            frac=(y2-y1)*(zvalue-zmin)/(zmax-zmin);
            //if (Math.abs(zmin-z11) <= 0.1*Math.abs(zmin)) {
            if (minat == 1) {
                // zmin is at y1:
                yint[n_intercepts] = y1 + frac;
            } else {
                // zmin is at y2:
                yint[n_intercepts] = y2 - frac;
            }
            xint[n_intercepts] = x1;
        }

        // 3. top edge:
        //zmin = Math.min(z12,z22);
        //zmax = Math.max(z12,z22);
        if (z12 < z22) {
            zmin = z12;
            zmax = z22;
            minat = 1;
        } else {
            zmin = z22;
            zmax = z12;
            minat = 2;
        }
        if (zvalue >= zmin && zvalue <= zmax &&
            Math.abs(zmax-zmin) >= 1.0E-6) {
            n_intercepts= n_intercepts+1;
            frac=(x2-x1)*(zvalue-zmin)/(zmax-zmin);
            //if (Math.abs(zmin-z12) <= 0.1*Math.abs(zmin)) {
            if (minat == 1) {
                // zmin is at x1:
                xint[n_intercepts] = x1 + frac;
            } else {
                // zmin is at x2:
                xint[n_intercepts] = x2 - frac;
            }
            yint[n_intercepts] = y2;
        }

        // 4. right edge:
        //zmin = Math.min(z21,z22);
        //zmax = Math.max(z21,z22);
        if (z21 < z22) {
            zmin = z21;
            zmax = z22;
            minat = 1;
        } else {
            zmin = z22;
            zmax = z21;
            minat = 2;
        }
        if (zvalue >= zmin && zvalue <= zmax &&
            Math.abs(zmax-zmin) >= 1.0E-6) {
            n_intercepts= n_intercepts+1;
            frac=(y2-y1)*(zvalue-zmin)/(zmax-zmin);
            //if (Math.abs(zmin-z21) <= 0.1*Math.abs(zmin)) {
            if (minat == 1) {
                // zmin is at y1:
                yint[n_intercepts] = y1 + frac;
            } else {
                // zmin is at y2:
                yint[n_intercepts] = y2 - frac;
            }
            xint[n_intercepts] = x2;
        }

        n_intercepts++; // to put back to count instead of index
        
        if (n_intercepts == 0) {
            s.nsegs = 0;
            s.len = 0;
        } else if (n_intercepts == 1) {
            // NOT POSSIBLE
            s.nsegs = 0;
            s.len = 0;
        } else if (n_intercepts == 2) {
            // connect them with a line:
            s.nsegs = 1;
            s.xs[0] = xint[0];
            s.xs[1] = xint[1];
            s.ys[0] = yint[0];
            s.ys[1] = yint[1];
            s.len = 2;
        } else if (n_intercepts == 3) {
            // connect as 2 lines (a guess)
            s.nsegs = 2;
            s.xs[0] = xint[0];
            s.xs[1] = xint[1];
            s.ys[0] = yint[0];
            s.ys[1] = yint[1];

            s.xs[2] = xint[1];
            s.xs[3] = xint[2];
            s.ys[2] = yint[1];
            s.ys[3] = yint[2];
            s.len = 4;
        }else if (n_intercepts == 4) {
            // connect as 3 lines (a guess)
            s.nsegs = 3;
            s.xs[0] = xint[0];
            s.xs[1] = xint[1];
            s.ys[0] = yint[0];
            s.ys[1] = yint[1];

            s.xs[2] = xint[1];
            s.xs[3] = xint[2];
            s.ys[2] = yint[1];
            s.ys[3] = yint[2];

            s.xs[4] = xint[2];
            s.xs[5] = xint[3];
            s.ys[4] = yint[2];
            s.ys[5] = yint[3];
            s.len = 6;
        }
        return s;
    }


    private double square(double number) {
        double newNumber;
        newNumber = number*number;
        return newNumber;
    }
}



class ContourReturn {
    public Vector<Double> x;
    public Vector<Double> y;
    int n;

    public ContourReturn() {
        super();
        this.x = new Vector<Double>();
        this.y = new Vector<Double>();
        this.n = 0;
    }

    
    public double[] getX() {
        double newX[];
        n = x.size();
        newX = new double[n];
        for (int i=0; i<n; i++)
            newX[i] = (x.elementAt(i)).doubleValue();
        return newX;
    }
    
    public double[] getY() {
        double newY[];
        n = y.size();
        newY = new double[n];
        for (int i=0; i<n; i++)
            newY[i] = (y.elementAt(i)).doubleValue();
        return newY;
    }
}


class SegmentReturn{
    double xs[] = new double[10];
    double ys[] = new double[10];
    int nsegs;
    int len;

    public SegmentReturn() {
        super();
    }
}


class ActualVector {
    double ee;
    double theta;
    double ex;
    double ey;

    public ActualVector() {
        super();
    }

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