package ppmrmn;       import java.awt.*;       import java.applet.*;
//30 juillet 2001

public class HahnEkoPowder extends Applet implements Runnable{

  Thread th = null;  //pour exécuter la méthode simplex dans run() et visualiser les courbes

  //les 4 constantes utilisées par la méthode simplex()
  static final int REFLECTFACT = 1;           static final int EXPANDFACT = 2;
  static final double CONTRACTFACT = 0.5;     static final int MAXITER = 1000;

  int numVars = 3;  //nombre de variables à ajuster par la méthode simplex()
  int cent    = 10; //position du graph utilisé dans graph()
  char choix = 'C';    int ligne = 2;    double coeff = 5,   pop = 2,   spin1 = 1.5;
  boolean stopFit = false,      showCurve = true;
  double  dureeFin = 1,         dureePremiere = 0,    dureePas = 1;
  double  dureePremiere1 = 0,   dureePremiere2 = 0;
  int     nbou = 1;                   //nombre de durées d'impulsion + 1
  double  amplitude[] = {0.0};        //intensité du FID
  double  listExpIntensity[] = {0.0}; //intenssité expérimentale
  //matEstimation[][] est la matrice des valeurs estimées initiales
  double  matEstimation[][] = new double[numVars + 1][numVars];

  int hautLB =     20,  //hauteur du label
      hautBouton = 30,  //hauteur des boutons
      hautPanel =  390; //hauteur des Panels
  double milli = 0.001;

  //°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°
  //Dimension de l'applet 760LX390H à préciser dans la source de l'applet (page WEB)
  //°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°

  int population[] = new int[25];  //spectre de poudre

  int populationEta00[] = {1505, 1204, 1204, 1204, 1204, 1505, 1204, 1204,
                           1505, 1505, 1204, 1505, 12040, 9030, 6923, 5719,
                     5418, 4816, 4816, 4816, 4214, 4214, 4214, 4515, 3913};
  int populationEta01[] = {1375, 1224, 1228, 1267, 1338, 1248, 1343, 1343,
                           1370, 1383, 1449, 5071, 6989, 9472, 7457, 5998,
                     5389, 4991, 4738, 4564, 4414, 4289, 4263, 4215, 4183};
  int populationEta02[] = {1363, 1216, 1267, 1264, 1299, 1284, 1339, 1351,
                           1383, 1409, 4739, 4989, 5312, 5865, 7354, 7242,
                     5713, 5146, 4857, 4622, 4490, 4351, 4286, 4252, 4208};
  int populationEta03[] = {1363, 1231, 1284, 1257, 1279, 1292, 1339, 1355,
                           2058, 4069, 4213, 4398, 4583, 4902, 5268, 6146,
                     7215, 5567, 5069, 4751, 4600, 4408, 4383, 4301, 4270};
  int populationEta04[] = {1359, 1279, 1237, 1275, 1307, 1329, 1323, 2469,
                           3643, 3754, 3899, 4032, 4164, 4358, 4580, 4995,
                     5552, 6913, 5580, 4992, 4765, 4581, 4442, 4402, 4371};
  int populationEta05[] = {1359, 1265, 1259, 1303, 1288, 1343, 2815, 3384,
                           3451, 3530, 3652, 3786, 3864, 4039, 4222, 4429,
                     4723, 5199, 6547, 5693, 5017, 4796, 4623, 4544, 4470};
  int populationEta06[] = {1386, 1253, 1291, 1273, 1334, 3103, 3168, 3244,
                           3258, 3426, 3478, 3544, 3701, 3810, 3943, 4118,
                     4277, 4614, 4988, 5950, 6058, 5137, 4881, 4699, 4667};
  int populationEta07[] = {1418, 1241, 1288, 1717, 2930, 3024, 3050, 3136,
                           3162, 3268, 3369, 3395, 3510, 3666, 3757, 3902,
                     4036, 4272, 4487, 4873, 5484, 6395, 5295, 5015, 4911};
  int populationEta08[] = {1434, 1256, 2031, 2834, 2872, 2906, 2978, 3041,
                           3052, 3191, 3231, 3335, 3418, 3501, 3586, 3760,
                     3863, 4067, 4252, 4454, 4791, 5361, 6472, 5588, 5327};
  int populationEta09[] = {1442, 2320, 2714, 2730, 2811, 2854, 2923, 2932,
                           3029, 3099, 3174, 3231, 3346, 3369, 3528, 3631,
                     3783, 3856, 4101, 4199, 4560, 4880, 5351, 6586, 6152};
  int populationEta10[] = {2771, 2610, 2628, 2709, 2760, 2797, 2869, 2887,
                           2966, 3037, 3112, 3170, 3253, 3379, 3447, 3514,
                     3688, 3807, 3976, 4130, 4407, 4656, 5075, 5671, 7282};

  //PanelSpin contient panelSpinLabel, panelSpinData, panelSpinData2,panelSpinData3 et panelSpinUnit
  Panel panelSpin =       new Panel();        Panel panelSpinLabel = new Panel();
  Panel panelSpinData =   new Panel();        Panel panelSpinData2 = new Panel();
  Panel panelSpinUnit =   new Panel();

  Panel panelTransition = new Panel();        Panel panelBlanc =     new Panel();

  //PanelFit contient panelFitAlpha, panelFitEstim, panelFitBouton, panelFitOmegaRF et panelFitQCC
  Panel panelFit =        new Panel();        Panel panelFitEstim =  new Panel();
  Panel panelFitBouton =  new Panel();        Panel panelFitAlpha =  new Panel();
  Panel panelFitOmegaRF = new Panel();        Panel panelFitQCC =    new Panel();

  Choice choiceSpin =     new Choice();       Choice choiceAlpha =   new Choice();
  Choice choiceSequence = new Choice();       Choice choiceEta =     new Choice();
  Button buttonRun =      new Button("Run");  Button buttonFit =     new Button("Fit");
  Button buttonGraph =    new Button("DeactivateGraph");
  Button buttonPrevious = new Button("Previous");
  List listTransition =   new List(7, false);

  Label labelDate =         new Label("30-07-2001");
  Label labelDuree =        new Label("Length (µs)");    //durée de l'impulsion
  Label labelIntensite =    new Label("Intensity");      //intensité du signal
  Label labelExpIntensite = new Label("Exp. Intensity"); //intensité exp. du signal
  Label labelAdvice =       new Label("Deactivate graph speeds up fitting!!!");

  TextField textFieldOmegaQ =         new TextField("50");  //
  TextField textFieldOmegaRF =        new TextField("50.0");

  TextField textFieldDerniereDuree =  new TextField("10.0");
  TextField textFieldPas =            new TextField("0.5");
  TextField textFieldPremiereDuree =  new TextField("0.0");

  TextField textFieldDerniereDuree2 = new TextField("10.0");
  TextField textFieldPas2 =           new TextField("0.5");
  TextField textFieldPremiereDuree2 = new TextField("5.0");

  TextField textFieldOmegaQEst =      new TextField("55");
  TextField textFieldOmegaRFEst =     new TextField("55");  //

  TextArea textAreaDuree =            new TextArea(12,7);  //durée
  TextArea textAreaIntensite =        new TextArea(12,7);  //intensité
  TextArea textAreaExpIntensite =     new TextArea(12,7);  //Exp intensité
  TextArea textAreaParameter =        new TextArea(5,7);   //Exp fit

  /**Construct the applet*/
  public HahnEkoPowder() {
  }
  /**Initialize the applet*/
  public void init() {
    try {
      jbInit();
    }
    catch(Exception e) {
      e.printStackTrace();
    }
  }
  /**Component initialization*/
  private void jbInit() throws Exception {
    setBackground(Color.white); setLayout(null);
    buttonGraph.move(75, 355);  buttonGraph.resize(150, hautBouton);    add(buttonGraph);
    buttonGraph.hide();
    labelAdvice.move(15, 320);  labelAdvice.resize(220, hautBouton);    add(labelAdvice);
    labelAdvice.setFont(new Font("TimesRoman", Font.PLAIN, 10));
    labelAdvice.setBackground(Color.pink);  //sauf IE5.5, les autres navigateurs créent une zone
    labelAdvice.hide();
    //***modifie panelSpin largeur + 100
    panelSpin.move(0, 0);        panelSpin.resize(260, hautPanel);       add(panelSpin);
    panelSpinLabel.move(0, 0);   panelSpinLabel.resize(90, hautPanel-3); panelSpin.add(panelSpinLabel);
    panelSpinData.move(90, 0);   panelSpinData.resize(60, hautPanel-3);  panelSpin.add(panelSpinData);
    panelSpinData2.move(150, 0); panelSpinData2.resize(60, hautPanel-3); panelSpin.add(panelSpinData2);
    panelSpinUnit.move(210, 0);  panelSpinUnit.resize(60, hautPanel-3);  panelSpin.add(panelSpinUnit);
    //***modifie panelTransition + 100
    panelTransition.move(260, 0);   panelTransition.resize(220, hautPanel);   add(panelTransition);
    //***modifie panelFit + 100
    panelFit.move(480, 0);       panelFit.resize(220, hautPanel);        add(panelFit);
    panelFit.hide();

//******************************************************************************
//                                  panelSpin
//******************************************************************************
    panelSpin.setBackground(Color.pink);        panelSpin.setLayout(null);
    panelSpin.setFont(new Font("TimesRoman", Font.PLAIN, 12));

    panelSpinLabel.setLayout(new GridLayout(9, 1, 0, 10)); //9 lignes, 1 colonne
    panelSpinData.setLayout(new GridLayout(9, 1, 0, 10));  //0 espace horizontale
    panelSpinData2.setLayout(new GridLayout(9, 1, 0, 10)); //10 espaces verticales
    panelSpinUnit.setLayout(new GridLayout(9, 1, 0, 10));
    panelSpinLabel.setBackground(Color.pink);   panelSpinData.setBackground(Color.pink);
    panelSpinData2.setBackground(Color.pink);
    panelSpinUnit.setBackground(Color.pink);

    panelSpinLabel.add(labelDate);              panelSpinData.add(new Label(""));
    panelSpinData2.add(new Label(""));
    panelSpinUnit.add(new Label(""));

    panelSpinLabel.add(new Label(" Spin"));     panelSpinData.add(choiceSpin);
    panelSpinData2.add(new Label(""));
    panelSpinUnit.add(new Label(""));

    panelSpinLabel.add(new Label(" RF field")); panelSpinData.add(textFieldOmegaRF);
    panelSpinData2.add(new Label("  kHz"));
    panelSpinUnit.add(new Label(""));

    panelSpinLabel.add(new Label(" QCC"));      panelSpinData.add(textFieldOmegaQ);
    panelSpinData2.add(new Label("  kHz"));
    panelSpinUnit.add(new Label(""));

    panelSpinLabel.add(new Label(" Eta"));      panelSpinData.add(choiceEta);
    panelSpinData2.add(new Label(""));
    panelSpinUnit.add(new Label(""));

    panelSpinLabel.add(choiceSequence);         panelSpinData.add(new Label("Pulse-1"));
    panelSpinData2.add(new Label("Pulse-2"));
    panelSpinUnit.add(new Label(""));

    panelSpinLabel.add(new Label(" MinLength"));  panelSpinData.add(textFieldPremiereDuree);
    panelSpinData2.add(textFieldPremiereDuree2);
    panelSpinUnit.add(new Label("  µs"));

    panelSpinLabel.add(new Label(" MaxLength"));  panelSpinData.add(textFieldDerniereDuree);
    panelSpinData2.add(textFieldDerniereDuree2);
    panelSpinUnit.add(new Label("  µs"));

    panelSpinLabel.add(new Label(" Step"));     panelSpinData.add(textFieldPas);
    panelSpinData2.add(textFieldPas2);
    panelSpinUnit.add(new Label("  µs"));

    choiceSpin.addItem("3/2");                  choiceSpin.addItem("5/2");
    choiceSpin.addItem("7/2");                  choiceSpin.addItem("9/2");

    choiceSequence.addItem("A(p1)B()");         choiceSequence.addItem("A()B(p2)");
    choiceSequence.addItem("B(p2)");            choiceSequence.select(0);  //

    choiceEta.addItem("0.0");       choiceEta.addItem("0.1");     choiceEta.addItem("0.2");
    choiceEta.addItem("0.3");       choiceEta.addItem("0.4");     choiceEta.addItem("0.5");
    choiceEta.addItem("0.6");       choiceEta.addItem("0.7");     choiceEta.addItem("0.8");
    choiceEta.addItem("0.9");       choiceEta.addItem("1.0");
    population = populationEta00;

    textFieldDerniereDuree2.hide();
    textFieldPas2.hide();

//******************************************************************************
//                                 panelTransition
//******************************************************************************
    panelTransition.setBackground(Color.pink); panelTransition.setLayout(null);
    panelTransition.setFont(new Font("TimesRoman", Font.PLAIN, 12));

    labelDuree.move(15, 7);                    labelDuree.resize(100, hautLB);
    panelTransition.add(labelDuree);
    labelIntensite.move(125, 7);               labelIntensite.resize(100, hautLB);
    panelTransition.add(labelIntensite);
    textAreaDuree.move(10, 30);                textAreaDuree.resize(80, 150);
    panelTransition.add(textAreaDuree);
    textAreaIntensite.move(110, 30);           textAreaIntensite.resize(80, 150);
    panelTransition.add(textAreaIntensite);
    listTransition.move(10, 182);              listTransition.resize(180, 170);
    panelTransition.add(listTransition);
    listTransition.addItem("Central Transition");
    listTransition.addItem("Satellite Transitions");
    listTransition.select(0);  //
    choix = 'C';  //
    textAreaDuree.setEditable(false);          textAreaIntensite.setEditable(false);
    buttonRun.move(50, 355);                   buttonRun.resize(100, hautBouton);
    panelTransition.add(buttonRun);

//******************************************************************************
//                 panelFitBouton et panelFitEstim dans panelFit
//******************************************************************************
    panelFit.setBackground(Color.orange);      panelFit.setLayout(null);
    panelFit.setFont(new Font("TimesRoman", Font.PLAIN, 12));

    labelExpIntensite.move(10, 7);             labelExpIntensite.resize(100, hautLB);
    panelFit.add(labelExpIntensite);
    textAreaExpIntensite.move(10, 30);         textAreaExpIntensite.resize(80, 150);
    panelFit.add(textAreaExpIntensite);
    textAreaParameter.move(10, 182);           textAreaParameter.resize(200, 170);
    panelFit.add(textAreaParameter);
    panelFitBouton.move(10, 355);              panelFitBouton.resize(200, hautBouton);
    panelFit.add(panelFitBouton);              panelFitBouton.setLayout(new GridLayout(1, 2));
    panelFitBouton.add(buttonPrevious);        panelFitBouton.add(buttonFit);
    buttonPrevious.enable(false);

    panelFitEstim.move(110, 5);                panelFitEstim.resize(100, 176);
    panelFit.add(panelFitEstim);
    panelFitEstim.setBackground(Color.orange); panelFitEstim.setLayout(new GridLayout(5, 1));
    panelFitEstim.setFont(new Font("TimesRoman", Font.PLAIN, 12));

    panelFitEstim.add(panelFitAlpha);
    panelFitEstim.add(new Label("RF field:")); panelFitEstim.add(panelFitOmegaRF);
    panelFitEstim.add(new Label("QCC:"));      panelFitEstim.add(panelFitQCC);

    choiceAlpha.addItem("1.1");                choiceAlpha.addItem("1.2");
    choiceAlpha.addItem("1.3");                choiceAlpha.addItem("1.4");
    choiceAlpha.select(2);
    panelFitOmegaRF.setLayout(new GridLayout(1, 2));
    panelFitOmegaRF.add(textFieldOmegaRFEst);  panelFitOmegaRF.add(new Label("  kHz"));
    panelFitQCC.setLayout(new GridLayout(1, 2));
    panelFitQCC.add(textFieldOmegaQEst);       panelFitQCC.add(new Label("  kHz"));
    panelFitAlpha.setLayout(new GridLayout(1, 2));
    panelFitAlpha.add(new Label("Alpha:"));    panelFitAlpha.add(choiceAlpha);
    panelFitEstim.hide();

    //valeurs exemples
    String s5 = 0+"\n"+-743+"\n"+-1468+"\n"+-2156+"\n"+-2791+"\n"+-3356+"\n";
    s5 += -3836+"\n"+-4219+"\n"+-4495+"\n"+-4655+"\n"+-4697+"\n"+-4618+"\n"+-4423+"\n";
    s5 += -4116+"\n"+-3708+"\n"+-3209+"\n"+-2635+"\n"+-2000+"\n";
    s5 += -1320+"\n"+-615+"\n"+99+"\n";
    textAreaExpIntensite.setText(s5);
  }

  /**Start the applet*/
  public void start() {
  }
  /**Stop the applet*/
  public void stop() {
  }
  /**Destroy the applet*/
  public void destroy() {
  }
  /**Get Applet information*/
  public String getAppletInfo() {
    return "Applet Information";
  }
  /**Get parameter info*/
  public String[][] getParameterInfo() {
    return null;
  }

  private void afficheTransition(){
    listTransition.clear();
    switch (choiceSpin.getSelectedIndex()) {
      case 0: listTransition.addItem("Central Transition");
              listTransition.addItem("Satellite Transitions");
              break;
      case 1: listTransition.addItem("Central Transition");
              listTransition.addItem("Inner Satellite Transitions");
              listTransition.addItem("Outer Satellite Transitions");
              break;
      case 2: listTransition.addItem("Central Transition");
              listTransition.addItem("Inner Satellite Transitions");
              listTransition.addItem("Medium Satellite Transitions");
              listTransition.addItem("Outer Satellite Transitions");
              break;
      case 3: listTransition.addItem("Central Transition");
              listTransition.addItem("Inner Satellite Transitions");
              listTransition.addItem("Medium Satellite Transitions");
              listTransition.addItem("Next Satellite Transitions");
              listTransition.addItem("Outer Satellite Transitions");
              break;
      }  //end of switch
    listTransition.select(0);
    choix ='C';
  }

  private void normalisationSignal(){
    switch (choiceSpin.getSelectedIndex()) {
      case 0: ComplexMat.setTailleMatrice (4);
              coeff = 5.0;                   spin1 = 1.5;
              switch (choix) {
                case 'C': ligne = 2;  pop = Math.sqrt(4);     break;
                case 'I': ligne = 1;  pop = 2*Math.sqrt(3);   break;
              }
              break;
      case 1: ComplexMat.setTailleMatrice (6);
              coeff = 35.0/2.0;              spin1 = 2.5;
              switch (choix) {
                case 'C': ligne = 3;  pop = Math.sqrt(9);     break;
                case 'I': ligne = 2;  pop = 2*Math.sqrt(8);   break;
                case 'O': ligne = 1;  pop = 2*Math.sqrt(5);   break;
              }
              break;
      case 2: ComplexMat.setTailleMatrice (8);
              coeff = 42.0;                  spin1 = 3.5;
              switch (choix) {
                case 'C': ligne = 4;  pop = Math.sqrt(16);    break;
                case 'I': ligne = 3;  pop = 2*Math.sqrt(15);  break;
                case 'M': ligne = 2;  pop = 2*Math.sqrt(12);  break;
                case 'O': ligne = 1;  pop = 2*Math.sqrt(7);   break;
              }
              break;
      case 3: ComplexMat.setTailleMatrice (10);
              coeff = 165.0/2.0;              spin1 = 4.5;
              switch (choix) {
                case 'C': ligne = 5;  pop = Math.sqrt(25);    break;
                case 'I': ligne = 4;  pop = 2*Math.sqrt(24);  break;
                case 'M': ligne = 3;  pop = 2*Math.sqrt(21);  break;
                case 'N': ligne = 2;  pop = 2*Math.sqrt(16);  break;
                case 'O': ligne = 1;  pop = 2*Math.sqrt(9);   break;
              }
    break;
    }  //end of switch
  }

  private void choixTableauEta(){
    switch (choiceEta.getSelectedIndex()) {
      case 0: population = populationEta00;  break;
      case 1: population = populationEta01;  break;
      case 2: population = populationEta02;  break;
      case 3: population = populationEta03;  break;
      case 4: population = populationEta04;  break;
      case 5: population = populationEta05;  break;
      case 6: population = populationEta06;  break;
      case 7: population = populationEta07;  break;
      case 8: population = populationEta08;  break;
      case 9: population = populationEta09;  break;
      case 10: population = populationEta10; break;
    }  //end of switch
  }

  private void hahnSequence(int i, int k, double omegaQTmp, double omegaRFTmp, double tp1, double tp2,
                      RealMat matHD, RealMat matR, RealMat matRt, RealMat matR2){
    ComplexMat   matHH, matHHC, matC1, matC2, matC3, matC4, matC5, matC6,
                 matC7, matC8, matC9, matC10, matCz;
    double z1;
    Nmr myNmrTmp =  new Nmr(omegaQTmp, omegaRFTmp);   //Déclaration formelle

    //impulsion +x
    matHH  = myNmrTmp.matriceHamilton(matHD, tp1);
    matHHC = myNmrTmp.matConjugue(matHH);
    matC1  = myNmrTmp.mult(matR2, matHH);    //effet de +x
    matC2  = myNmrTmp.mult(matHHC, matC1);   //effet de +x
    matC3  = myNmrTmp.mult(matC2, matRt);    //retour dans le ref tournant
    matC4  = myNmrTmp.mult(matR, matC3);     //retour dans le ref tournant

    z1     = matC4.im[ligne - 1][ligne];
    matCz  = myNmrTmp.matSingle(matC4, ligne - 1, ligne);

    //impulsion +x
    matC5  = myNmrTmp.mult(matCz, matR);     //passage dans le ref propre de matH
    matC6  = myNmrTmp.mult(matRt, matC5);    //passage dans le ref propre de matH
    matHH  = myNmrTmp.matriceHamilton(matHD, tp2);
    matHHC = myNmrTmp.matConjugue(matHH);
    matC7  = myNmrTmp.mult(matC6, matHH);    //effet de +x
    matC8  = myNmrTmp.mult(matHHC, matC7);   //effet de +x
    matC9  = myNmrTmp.mult(matC8, matRt);    //retour dans le ref tournant
    matC10 = myNmrTmp.mult(matR, matC9);     //retour dans le ref tournant

    switch (choiceSequence.getSelectedIndex()){
      case 2:  //B(p2)
        amplitude[i] += -(pop/coeff)*population[population.length - k -1]*(matC10.im[ligne][ligne - 1])/z1;
        break;
      default: //A()B(p2) and A(p1)B()
        amplitude[i] += (pop/coeff)*population[population.length - k -1]*matC10.im[ligne][ligne - 1];
        break;
    }
  }

  private void calculIntensite(double omegaQTmp, double omegaRFTmp){
    RealMat      matH, matHD, matR, matRt, matIz, matR1, matR2;
    double  tp =  0,  deltaY = 2.0/25.0;            //pas du spectre de poudre
    double rap =  3.0/(8*spin1*(2*spin1 - 1));      //coefficient de QCC
    Nmr myNmr =   new Nmr(omegaQTmp, omegaRFTmp);   //OmegaQ, omegaRF en kHz
    double tmp1 = myNmr.getOmegaQ();                //QCC en radian par seconde

    for (int i = 0; i <= nbou; i++) amplitude[i] = 0.0;  //RAZ
    //ATTENTION A K < POPULATION.LENGTH
    for (int k = 0 ; k < population.length; k++){
      double tmp2 = k*tmp1*deltaY*rap;
      myNmr.setOmegaQ(tmp2);

      //impulsion x
      matH   = myNmr.dataIn();            //matrice matH à diagonaliser
      matHD  = myNmr.jacRot(matH);        //matrice matH devient diagonale matHD
      matR   = myNmr.getMatR();           //matR est la matrice des vecteurs propres
      matRt  = RealMat.transpose(matR);   //matRt est la matrice transposée de matR
      matIz  = myNmr.matriceIz();             //etat equilibre
      matR1  = myNmr.mult(matIz, matR);       //etat equilibre dans le ref propre
      matR2  = myNmr.mult(matRt, matR1);      //de matH

      switch (choiceSequence.getSelectedIndex()){
        case 0:  //A(p1)B(), V-F
          for (int i = 0; i <= nbou; i++) {
            tp = dureePremiere + i*dureePas;
            //omegaQTmp n'a pas de grande utilité, variable muet
            hahnSequence(i, k, omegaQTmp, omegaRFTmp, tp, dureePremiere2, matHD, matR, matRt, matR2);
          }
          break;
        default:  //A()B(p2), F-V; and B(p2), I-V
          for (int i = 0; i <= nbou; i++) {
            tp = dureePremiere + i*dureePas;
            hahnSequence(i, k, omegaQTmp, omegaRFTmp, dureePremiere1, tp, matHD, matR, matRt, matR2);
          }
          break;
      }  //end of switch
    }  //end of for k
  }

  private void afficheValeur(){     //affichage des résultats de durées et intensités
    double tp = 0;
    String s = "", s2 = "";
    textAreaIntensite.setText("");  //RAZ intensité
    textAreaDuree.setText("");      //RAZ durée
    for (int i = 0; i <= nbou; i++) {
      tp = dureePremiere + i*dureePas;
      s = textAreaIntensite.getText() + (int)(amplitude[i]) + "\n";
      textAreaIntensite.setText(s);
      s2 = textAreaDuree.getText() + Math.round(100000*tp)/100.0 + "\n";
      textAreaDuree.setText(s2);
    }
  }

  private double mySimplexFonction(double omegaQFit, double omegaRFFit, double norme){
    double sqsum = 0;
    calculIntensite(omegaQFit, omegaRFFit);
    for (int i = 0; i <= nbou; i++){
      double tmp = norme*amplitude[i] - listExpIntensity[i];
      sqsum += tmp*tmp;
    }
    return sqsum;
  }

  public boolean handleEvent(Event event){
    switch (event.id){
      case Event.LIST_SELECT:
        String rangTransition = listTransition.getItem(((Integer)event.arg).intValue());
        if (rangTransition == "Central Transition") choix = 'C';
          else if (rangTransition == "Satellite Transitions") choix = 'I';
            else if (rangTransition == "Inner Satellite Transitions") choix = 'I';
              else if (rangTransition == "Medium Satellite Transitions") choix = 'M';
                else if (rangTransition == "Next Satellite Transitions") choix = 'N';
                  else if (rangTransition == "Outer Satellite Transitions") choix = 'O';
        textAreaIntensite.setText("");  //RAZ intensité
        textAreaDuree.setText("");      //RAZ durée
        break;
      case Event.ACTION_EVENT:
        if (event.target == choiceEta){
          choixTableauEta();
        }
        else if (event.target == choiceSpin){
          textAreaIntensite.setText("");  //RAZ intensité
          textAreaDuree.setText("");      //RAZ durée
          afficheTransition();
        }
        else if (event.target == choiceSequence){
          switch (choiceSequence.getSelectedIndex()) {
            //A(p1)B(), V-F
            case 0: textFieldPremiereDuree.show();   textFieldDerniereDuree.show();
                    textFieldPas.show();
                    textFieldPremiereDuree2.show();  textFieldDerniereDuree2.hide();
                    textFieldPas2.hide();
                    break;
            //A()B(p2), F-V
            case 1: textFieldPremiereDuree.show();   textFieldDerniereDuree.hide();
                    textFieldPas.hide();
                    textFieldPremiereDuree2.show();  textFieldDerniereDuree2.show();
                    textFieldPas2.show();
                    break;
            //B(p2), I-V
            case 2: textFieldPremiereDuree.hide();   textFieldDerniereDuree.hide();
                    textFieldPas.hide();
                    textFieldPremiereDuree2.show();  textFieldDerniereDuree2.show();
                    textFieldPas2.show();
                    break;
          }
        }
        else if (event.target == buttonRun){
          normalisationSignal();
          switch (choiceSequence.getSelectedIndex()){
            case 0:  //A(p1)B(), V-F
              //convertit les durées de microsecondes en ms, car les interactions sont en kHz
              dureeFin =         milli*Double.valueOf(textFieldDerniereDuree.getText()).doubleValue();
              dureePremiere =    milli*Double.valueOf(textFieldPremiereDuree.getText()).doubleValue();
              dureePas =         milli*Double.valueOf(textFieldPas.getText()).doubleValue();
              dureePremiere2 =   milli*Double.valueOf(textFieldPremiereDuree2.getText()).doubleValue();
              break;
            case 1:  //A()B(p2), F-V
              dureePremiere1 =   milli*Double.valueOf(textFieldPremiereDuree.getText()).doubleValue();
              dureeFin =         milli*Double.valueOf(textFieldDerniereDuree2.getText()).doubleValue();
              dureePremiere =    milli*Double.valueOf(textFieldPremiereDuree2.getText()).doubleValue();
              dureePas =         milli*Double.valueOf(textFieldPas2.getText()).doubleValue();
              break;
            case 2:  //B(p2), I-V
              //convertit les durées de microsecondes en ms, car les interactions sont en kHz
              dureePremiere1 =   1.0/(2*pop*Double.valueOf(textFieldOmegaRF.getText()).doubleValue());
              dureeFin =         milli*Double.valueOf(textFieldDerniereDuree2.getText()).doubleValue();
              dureePremiere =    milli*Double.valueOf(textFieldPremiereDuree2.getText()).doubleValue();
              dureePas =         milli*Double.valueOf(textFieldPas2.getText()).doubleValue();
              break;
          } //end of switch
          nbou =             (int)((dureeFin - dureePremiere)/dureePas);
          amplitude =        new double[nbou + 1];         //intensité du FID
          listExpIntensity = new double[nbou + 1];         //intensité exp
          calculIntensite(Double.valueOf(textFieldOmegaQ.getText()).doubleValue(),
                          Double.valueOf(textFieldOmegaRF.getText()).doubleValue());
          afficheValeur();
          panelFit.show();                       panelFitEstim.show();
        }
        else if (event.target == buttonPrevious){
          panelSpin.show();                      panelBlanc.hide();  //pour impression
          //***modifie panelTransition et panelFit + 100
          panelTransition.move(260, 0);          panelTransition.show();
          panelFit.move(480, 0);                 buttonPrevious.enable(false);
          setBackground(Color.white);            panelSpin.setBackground(Color.pink);
          panelFit.setBackground(Color.orange);  panelTransition.setBackground(Color.pink);
          buttonGraph.hide();                    buttonFit.setLabel("Fit");
          buttonRun.enable(true);
        }
        else if (event.target == buttonFit){
          if (buttonFit.getLabel() == "Fit"){
            //conversion des intensités expérimentales en nombres
            String s = textAreaExpIntensite.getText(),     s1 = "";
            int spaceAt = 0, startingForm = 0, ip = 0;
            while (true){
              spaceAt = s.indexOf("\n", startingForm);
              if (spaceAt == -1) break;
              s1 = s.substring(startingForm, spaceAt);
              listExpIntensity[ip] = Double.valueOf(s1).doubleValue();
              startingForm = spaceAt + 1;
              ip++;
            }
            if (ip != nbou + 1){   //controle le nombre de données
              textAreaParameter.setText("");
              textAreaParameter.appendText("The number of experimental" + "\n");
              textAreaParameter.appendText("intensities is wrong!!!" + "\n");
              textAreaParameter.appendText("May be type enter key" + "\n");
              textAreaParameter.appendText("for the last intensity data!!!");
            }
            else{  //demarrage du fit
              panelFit.move(300, 0);                setBackground(Color.pink);
              panelTransition.hide();               panelSpin.hide();
              panelBlanc.move(520 ,0);              panelBlanc.resize(240, hautPanel);
              add(panelBlanc);                      panelBlanc.show();  //***modifie panelBlanc +100
              panelBlanc.setBackground(Color.white);
              buttonFit.setLabel("StopFit");        buttonPrevious.enable(false);
              textAreaParameter.setText("");
              double alpha = Double.valueOf(choiceAlpha.getSelectedItem()).doubleValue();
              double val1 =  Double.valueOf(textFieldOmegaQEst.getText()).doubleValue();
              double val2 =  Double.valueOf(textFieldOmegaRFEst.getText()).doubleValue();
              //recherche de la valeur maximum de listExpIntensity[] et de amplitude[]
              double maxExp = 0, maxCal = 0;
              for (int i = 0; i < nbou + 1; i++)
                if (Math.abs(listExpIntensity[i]) > maxExp) maxExp = Math.abs(listExpIntensity[i]);
              for (int i = 0; i < nbou + 1; i++)
                if (Math.abs(amplitude[i]) > maxCal) maxCal = Math.abs(amplitude[i]);
              double val3 =  maxExp/maxCal;  //scaling
              choiceAlpha.enable(false);           textFieldOmegaRFEst.enable(false);
              textFieldOmegaQEst.enable(false);
              textAreaExpIntensite.enable(false);
              normalisationSignal();
              //initial parameters for simplex procedure
              matEstimation[0][0] = val1;          matEstimation[0][1] = val2;
              matEstimation[0][2] = val3;
              matEstimation[1][0] = alpha*val1;    matEstimation[1][1] = val2;
              matEstimation[1][2] = val3;
              matEstimation[2][0] = val1;          matEstimation[2][1] = alpha*val2;
              matEstimation[2][2] = val3;
              matEstimation[3][0] = val1;          matEstimation[3][1] = val2;
              matEstimation[3][2] = alpha*val3;
              th = new Thread(this);               th.start();
              buttonGraph.setLabel("DeactivateGraph");
              buttonGraph.show();                  buttonGraph.enable(true);
              labelAdvice.show();
              showCurve = true;                    stopFit = false;
            }
          }
          else{  //on arrete le fit
            stopFit = true;                       th = null;
            buttonPrevious.enable(true);          buttonFit.setLabel("Fit");
            choiceAlpha.enable(true);             textFieldOmegaRFEst.enable(true);
            textFieldOmegaQEst.enable(true);
            textAreaExpIntensite.enable(true);
            buttonGraph.enable(false);            labelAdvice.hide();
          }
        }
        else if (event.target == buttonGraph){
          if (buttonGraph.getLabel() == "DeactivateGraph"){
            showCurve = false;    buttonGraph.setLabel("ActivateGraph");     labelAdvice.hide();
            }
          else{
            showCurve = true;     buttonGraph.setLabel("DeactivateGraph");   labelAdvice.show();
          }
        }
      break;
    }  //end of switch
    return super.handleEvent(event);
  }

  private void plotCourbe(double b0, double b1, double b2){
    //tracer la courbe
    switch (choiceSequence.getSelectedIndex()){
      case 0: graph("Hahn echo to powder", "p1 (µs)", "A(p1)B()"); break;
      case 1: graph("Hahn echo to powder", "p2 (µs)", "A()B(p2)"); break;
      case 2: graph("Hahn echo to powder", "p2 (µs)", "B(p2)");    break;
    }
    double inc;
    setSymbol(true);            setLine(false);
    setColor(blue);             setTitle("experimental");
    for (int i = 0; i <= nbou; i++){
      inc = Math.round(100000*(dureePremiere + i*dureePas))/100.0;
      add(inc, listExpIntensity[i]);
    }
    nextGraph();
    setColor(black);              setTitle("fit");
    calculIntensite(b0, b1);
    for (int i = 0; i <= nbou; i++){
      inc = Math.round(100000*(dureePremiere + i*dureePas))/100.0;
      add(inc, b2*amplitude[i]);
    }
    repaint();
  }

  public void run() {        //run the simplex procedure
  /* The flexible simplex method allows you to find the minimum for a function
   * with multiple variables. The method obtains the minimum for a function
   * with N variables by examining the function values at N + 1 points.
   * This method locates the points with the best and worst values
   * then attempts to replace the worst-value point with a better point.
   * This replacement process involves expanding and contracting the simplex
   * near the worst-value point to determine a better replacement point.
   * The iterations of the method shrinks the multidimensional simplex around
   * the minimum point.
   * Given
   * # N variables that evaluate function f(X)
   * # The matrix X, which contains N + 1 rows and N columns
   * # The array of function values, Y(containing N + 1 values)
   * # The maximum number of iterations, MAXITER
   * # The reflection factor, which is a positive value that may be unity
   * # The expansion factor, which is greater than one (usually two)
   * # The contraction factor, a value between zero and one (ususally 0.5)
   */
    //(1) set Iter = 0
    //(2) set P = N + 1
    int      numIter = 0, numPoints = numVars + 1, worstI = 0, bestI = 0;
    double   y1 = 0, y2 = 0;
    //(5) set the convergence flag to false
    boolean flag = false;
    double y[] = new double[numVars + 1];

    //(3) allocate dynamic arrays x1, x2 and centroid to contain N elements
    double x1[] = new double[numVars];        double x2[] = new double[numVars];
    double centroid[] = new double[numVars];

    //(4)calculate the values for y() using x1()
    for (int i = 0; i < numPoints; i++){
      for (int j = 0; j < numVars; j++) x1[j] = matEstimation[i][j];
      y[i] = mySimplexFonction(x1[0], x1[1], x1[2]);
    }
    //**************************************************************************
    //                         boucle while
    //**************************************************************************

    //(6) repeat the next step while Iter < M and the convergence flag is false
    while ((numIter < MAXITER) && (!stopFit)){
      String sx = "";                           textAreaParameter.setText("");
      sx = "Iteration: " + numIter + " \n";     textAreaParameter.appendText(sx);

      numIter += 1;      //(6.1) increment Iter

      //(6.2) find the indices of the worst A and best points
      worstI = 0;      bestI = 0;
      for (int i = 1; i < numPoints; i++){
        if (y[i] < y[bestI])        bestI = i;
        else if (y[i] > y[worstI])  worstI = i;
      }

      //affiche les valeurs
      sx = Math.round(1000*matEstimation[bestI][1])/1000.0
          + " ( " + Math.round(1000*matEstimation[worstI][1])/1000.0 + " ) kHz\n";
      textAreaParameter.appendText("RF field: " + sx);
      sx = Math.round(1000*matEstimation[bestI][0])/1000.0
          + " ( " + Math.round(1000*matEstimation[worstI][0])/1000.0 + " ) kHz\n";
      textAreaParameter.appendText("QCC: " + sx);
      sx = Math.round(1000*matEstimation[bestI][2])/1000.0
          + " ( " + Math.round(1000*matEstimation[worstI][2])/1000.0 + " )\n";
      textAreaParameter.appendText("Scale: " + sx);
      sx = y[bestI] + "\n";      textAreaParameter.appendText(sx);
      sx = y[worstI] + "\n";     textAreaParameter.appendText(sx);

      if (showCurve){
        try{Thread.sleep(10);} catch (InterruptedException e){}
        //plotCourbe après try sinon clignotement avec Netscape
        plotCourbe(matEstimation[worstI][0], matEstimation[worstI][1], matEstimation[worstI][2]);
      }

      //(6.3) calculate centroid (exclude the worst point A) = center of gravity G
      for (int i = 0; i < numVars; i++){
        centroid[i] = 0;
        for (int j = 0; j < numPoints; j++){
          if (j != worstI) centroid[i] += matEstimation[j][i];
        }
        centroid[i] /= numVars;
      }

      //(6.4) calculate reflected point A' (symmetrical point of A relative to G): A' = 2*G - A
      for (int i = 0; i < numVars; i++)
        x1[i] = (1 + REFLECTFACT) * centroid[i] - REFLECTFACT * matEstimation[worstI][i];

      //(6.5) set y1 = function value at point x1 or f(A')
      y1 = mySimplexFonction(x1[0], x1[1], x1[2]);

      //************************************************************************
      //                   debut du if
      //************************************************************************

      //(6.6) if y1 or f(A') < best y, that is, f(A') is the best value found so far
      if (y1 < y[bestI]){       //perform the following tasks:
        //(6.6.1) calculate the expandeded point x2 or A" = 2A' - G: a point twice as far from G as A'
        for (int k = 0; k < numVars; k++)
          x2[k] = (1 + EXPANDFACT) * x1[k] - EXPANDFACT * centroid[k];

        //(6.6.2) set y2 = function value at point x2, or f(A")
        y2 = mySimplexFonction(x2[0], x2[1], x2[2]);

        //(6.6.3) if y2 or f(A") < best y
        if (y2 < y[bestI]){ //then replace worst point matEstimation with point x2 or A"
                            //and resume at step 7
          for (int m = 0; m < numVars; m++) matEstimation[worstI][m] = x2[m];
        }
        else {  //else replace worst point matEstimation with point x1 or A'
                //and resume at step 7
          for (int n = 0; n < numVars; n++) matEstimation[worstI][n] = x1[n];
        }
      }
      //************************************************************************
      //                        else
      //************************************************************************

      else {    //(6.7) y1 or f(A') >= best y
        flag = true;
        for (int q = 0; q < numPoints; q++){
          if ((q != worstI) && (y1 <= y[q])) {
            flag = false;  //f(A') est compris entre best y et les autres y, sauf A
            break;         //exit for
          }
        }

        if (flag){   //y1 ou f(A') n'est pas compris entre best y et les autres y sauf A
          if (y1 < y[worstI]){     //f(A') est compris entre best y et f(A),
            //(6.7.1) replace worst point by x1 or A'
            for (int p = 0; p < numVars; p++) matEstimation[worstI][p] = x1[p];
            y[worstI] = y1;
          }
          //f(A') >= f(A)
          //(6.7.2) calculate contracted point x2 or B = (A + G)/2 or B = (A' + G)/2
          for (int s = 0; s < numVars; s ++)
            x2[s] = CONTRACTFACT * matEstimation[worstI][s] + (1 - CONTRACTFACT) * centroid[s];
          //(6.7.3) set y2 = function value at point x2 or f(B)
          y2 = mySimplexFonction(x2[0], x2[1], x2[2]);

          if (y2 > y[worstI]){  //f(B)>f(A) ou f(B)>f(A'), TOUT CE QUE NOUS AVONS FAIT NE SERT A RIEN
            //store best matEstimation in x2
            for (int u = 0; u < numVars; u++) x2[u] = matEstimation[bestI][u];
            //and build a new simplex obtained by dividing
            //all edges leading to the point yielding the best value by 2
            for (int j = 0; j < numPoints; j++)
              for (int q = 0; q < numVars; q++)
                matEstimation[j][q] = 0.5 * (x2[q] + matEstimation[j][q]);
          }  //resume 7
          else { //(else of y2), f(B)<f(A) ou f(B)<f(A'), replace worst point or A or A' by x2 or B,
            for (int w = 0; w < numVars; w++) matEstimation[worstI][w] = x2[w];
          } //resume 7
        }
        else {   //flag = false,
                 //f(A') est compris entre best y et les autres Y sauf A
                 //replace worst point by x1 or A' and resume 7
          for (int z = 0; z < numVars; z++) matEstimation[worstI][z] = x1[z];
        } //end of flag
      }
      //************************************************************************
      //                       fin de if
      //************************************************************************

      //(7) calculate the values for y() using x1
      for (int i = 0; i < numPoints; i++){
        for (int j = 0; j < numVars; j++) x1[j] = matEstimation[i][j];
        y[i] = mySimplexFonction(x1[0], x1[1], x1[2]);
      }
    }
    //**************************************************************************
    //                            end of while
    //**************************************************************************
  }

/*
  add(x, y)              Adds a point to a list. Expects points to come in x-order.
  graph()                Compulsory call
  graph(graphTitle, xAxisTitle, yAxisTitle)
                         Version with labelling options.
                         Use empty strings if not all titles applicable
  nextGraph()            Starts a new graph on the same axes.
                         One showGraph call can be used for all the graphs.
  setColor(int 0 to 3)   Choice of black, magenta, blue, red
                         (constants are available instead of numbers)
  setSymbol(boolean)     Deduced from the colour
                         (Convenient if colours are being set)
  setSymbol(int 0 to 3)  circle, upside down triangle, triangle, square
                         (Used if colours are not being set)
  setLine(boolean)       Normally on, can be turned off
                         (Remember to turn it on again for the next line)
  setTitle(String)       Will appear on a key alongside the symbol and/or
                         in the chosen colour
  */

  protected String xAxisTitle, yAxisTitle, graphTitle;
  protected boolean keys;
  protected int largeur = 300;  //largeur de la fenetre
  protected int hauteur = 340;  //hauteur de la fenetre

  protected void graph (String g, String x, String y) {
    // axes titles
    initializeGraph();    graphTitle = g;     xAxisTitle = x;       yAxisTitle = y;
  }

    protected void graph () {
    // axes titles
    initializeGraph();    graphTitle = "";    xAxisTitle = "";       yAxisTitle = "";
  }

  protected ListGentle points;
  protected ListGentle datasets;

  protected void initializeGraph () {
    datasets = new ListGentle ();     nextGraph();    points = new ListGentle ();
    xMax = yMax = Double.MIN_VALUE;    xMin = yMin = Double.MAX_VALUE;
    keys = false;
  }

  public void nextGraph () {
    Dataset d = new Dataset ();    d.count = 0;
    d.plotType = black;            d.title = "";
    d.symbolRequired = false;      d.colorRequired = false;
    d.titleRequired = false;       d.lineRequired = true;
    datasets.add (d);
  }

  public void setColor (int c) {
    ((Dataset) datasets.current()).colorRequired = true;
    ((Dataset) datasets.current()).plotType = c;
  }

  public void setSymbol (boolean b) {
    ((Dataset) datasets.current()).symbolRequired = b;
  }

  public void setSymbol (int c) {
    ((Dataset) datasets.current()).symbolRequired = true;
    ((Dataset) datasets.current()).plotType = c;
  }

  public void setTitle (String s) {
    ((Dataset) datasets.current()).titleRequired = true;
    ((Dataset) datasets.current()).title = s;
    keys = true;
  }

  public void setLine (boolean b) {
    ((Dataset) datasets.current()).lineRequired = b;
    if (b==false) ((Dataset) datasets.current()).symbolRequired = true;
  }

  public void add (double x, double y) {
    points.add (new PointDot (x,y));
    ((Dataset)datasets.current()).count++;
    if (x > xMax) xMax = x;    if (x < xMin) xMin = x;
    if (y > yMax) yMax = y;    if (y < yMin) yMin = y;
  }

  protected void drawTitles (Graphics g) {
    Dataset d;
    int x = xBorder + cent;  //legende
    int y = hauteur-yBorder/2 + cent;  //legende
    datasets.reset();
    boolean lastset = datasets.eol();
    while (!lastset) {
      d = (Dataset) datasets.current();
      if (d.colorRequired) changeColor(g, d.plotType);
      if (d.symbolRequired) {
        drawSymbol(g, d.plotType, x, y-cs);
        x += 4*cs;
      }
      g.drawString(d.title, x, y);
      x += g.getFontMetrics().stringWidth(d.title) +20;
      lastset = datasets.eol();
      if (!lastset) datasets.succ();
    }
    g.setColor(Color.black);
  }

  protected void drawAxes (Graphics g) {
    Font plain = g.getFont();
    Font small = new Font(plain.getFamily(), Font.PLAIN,10);
    Font bold  = new Font(plain.getFamily(), Font.BOLD,14);

    g.drawLine(xBorder-5 + cent,yOrigin,xAxisLength+xBorder+5 + cent,yOrigin);  //axe x
    g.drawLine(xOrigin,yBorder-5 + cent,xOrigin,yAxisLength+yBorder+5 + cent);  //axe y
    g.drawString(xAxisTitle,
      largeur-g.getFontMetrics().stringWidth(xAxisTitle)-xBorder/2 + cent, yOrigin-5);  //"x"
    g.drawString(yAxisTitle,
      xOrigin-g.getFontMetrics().stringWidth(yAxisTitle)/2, yBorder-8 + cent); //"y"

    g.setFont(bold);
    g.drawString(graphTitle,
      (largeur-g.getFontMetrics().stringWidth(graphTitle))/2, yBorder/2 + cent);  //titre
    g.setFont(plain);
    if (keys) drawTitles(g);

    // Tick and Label the four min/max points only
    int scaleXMin = scaleX(xMin);    int scaleXMax = scaleX(xMax);
    int scaleYMin = scaleY(yMin);    int scaleYMax = scaleY(yMax);
    g.drawLine(xOrigin-5, scaleYMax + cent, xOrigin+5, scaleYMax + cent); //tick yMax
    g.drawLine(xOrigin-5, scaleYMin + cent, xOrigin+5, scaleYMin + cent); //tick yMin
    g.drawLine(scaleXMax + cent, yOrigin+5, scaleXMax + cent, yOrigin);  //tick xMax
    g.drawLine(scaleXMin + cent, yOrigin+5, scaleXMin + cent, yOrigin);  //tick xMin

    g.setFont(small);
    g.drawString(Double.toString(xMin),scaleXMin-10 + cent,yOrigin+15);  //xMin
    g.drawString(Double.toString(xMax),scaleXMax-10 + cent,yOrigin+15);  //xMax
    g.drawString(Double.toString(Math.round(100*yMin)/100.0), xOrigin-35, scaleYMin+4 + cent);  //yMin
    g.drawString(Double.toString(Math.round(100*yMax)/100.0), xOrigin-35, scaleYMax+4 + cent);  //yMax
    g.setFont(plain);
  }

  protected double xSpread, ySpread, xMin, xMax, yMin, yMax;
  protected int xAxisLength, yAxisLength, xOrigin, yOrigin;
  protected int xBorder, yBorder;

  public void paint (Graphics g) {
    // calulate length of axes from window size minus a border of 20
    xBorder = 40;    yBorder = 80;
    xAxisLength = (int) largeur - 2*xBorder;    yAxisLength = (int) hauteur - 2*yBorder;

    // calculate value spreads from mins and maxs which have been recorded as we go
    xSpread = xMax - xMin;    ySpread = yMax - yMin;
    if (xMin > 0) xOrigin = scaleX(xMin) + cent; else xOrigin = scaleX(0) + cent;
    if (yMin > 0) yOrigin = scaleY(yMin) + cent; else yOrigin = scaleY(0) + cent;
    drawAxes(g);    plotGraphs(g);
  }

  protected int scaleX(double x) {
    return (int) ((x-xMin) / xSpread*xAxisLength) + xBorder ;
  }

  protected int scaleY(double y) {
    return (int) (yAxisLength - ((y-yMin) / ySpread*yAxisLength)) + yBorder;
  }

  protected void changeColor(Graphics g, int c) {
    switch (c) {
      case black :   {g.setColor(Color.black); break;}
      case magenta : {g.setColor(Color.magenta); break;}
      case blue :    {g.setColor(Color.blue); break;}
      case red :     {g.setColor(Color.red); break;}
    }
  }

  protected static final int cs = 3; // pixel size of a symbol

  protected void drawSymbol (Graphics g, int sy, int x, int y) {
    switch (sy) {
      case black :   {g.drawOval (x-cs, y-cs, 2*cs, 2*cs); break;}
      case magenta : {g.drawPolygon (trix(x),uptriy(y), 3); break;}
      case blue :    {g.drawPolygon (trix(x),triy(y), 3); break;}
      case red :     {g.drawRect (x-cs, y-cs, 2*cs, 2*cs); break;}
    }
  }

  protected void plotGraphs (Graphics g) {
    PointDot p, q;    int x1, y1, x2, y2;    Dataset d;    boolean lastset;    Color c;

    // Loop through each dataset
    datasets.reset();
    /* The points are in one big list, split by the
    counts recorded in each dataset */
    points.reset();
    lastset = datasets.eol();

    do {
      d = (Dataset) datasets.current();
      if (d.colorRequired) changeColor(g, d.plotType);
      // Start with the first point in the list for this graph
      p = (PointDot) points.current();
      x1 = scaleX(p.xCoord) + cent;      y1 = scaleY(p.yCoord) + cent;
      if (d.symbolRequired) drawSymbol(g, d.plotType, x1, y1);

      // Loop through the points as stored in the list
      for (int i=1; i<d.count; i++) {
        points.succ();                         q = (PointDot) points.current();
        x2 = scaleX(q.xCoord) + cent;          y2 = scaleY(q.yCoord) + cent;

        // plot the line and/or point
        if (d.lineRequired)   g.drawLine(x1, y1, x2, y2);
        if (d.symbolRequired) drawSymbol(g, d.plotType, x2, y2);
        x1 = x2; y1 = y2;
      }
      lastset = datasets.eol();
      if (!lastset) {
        datasets.succ();
        points.succ();
      }
    } while (!lastset);
  }

  protected int [] trix (int p) {
    int [] a = new int [3];
    a[0] = p-cs;    a[1] = p+cs;    a[2] = p;
    return a;
  }

  protected int [] triy (int p) {
    int [] a = new int [3];
    a[0] = p+cs;    a[1] = p+cs;    a[2] = p-cs;
    return a;
  }

  protected int [] uptriy (int p) {
    int [] a = new int [3];
    a[0] = p-cs;    a[1] = p-cs;    a[2] = p+cs;
    return a;
  }

  public final int red = 3;       public final int blue = 2;
  public final int magenta = 1;   public final int black = 0;
}

class PointDot {
  /* simple class for an x,y coordinate */
  double xCoord, yCoord;
  PointDot (double x, double y) {
    xCoord = x;    yCoord = y;
  }
}

class Dataset {
  /* Simple class for recording basic info about a set of points.
  Allows more than one graph to be drawn on an axis.
  All initialisation done in nextGraph. */
  int count, plotType;      String title;
  boolean colorRequired, symbolRequired, titleRequired, lineRequired;
}
