ui/src/main/java/ucar/nc2/ui/grid/ColorScale.java
public void setMinMax(double min, double max) {
    this.min = min;
    this.max = max;
    interval = (max - min) / (ncolors - 2);

    // set edges
    for (int i = 0; i < ncolors; i++)
      edge[i] = min + i * interval;

    lm.sendEvent(new PropertyChangeEvent(this, "ColorScaleLimits", null, this));
  }


  /**
   * This is an optimization for counting the number of colors in each interval.
   * the histpogram is populated by calls to getIndexFromValue().
   *
   * @return the index with the maximum histogram count.
   */
  public int getHistMax() {
    int max = 0, maxi = 0;
    for (int i = 0; i <= ncolors; i++)
      if (hist[i] > max) {
        max = hist[i];
        maxi = i;
      }
    return maxi;
  }

  /**
   * reset the histogram.
   */
  public void resetHist() {
    for (int i = 0; i <= ncolors; i++)
      hist[i] = 0;
  }

  public String toString() {
    return name;
  }

  public Object clone() {
    ColorScale cl = new ColorScale(name, colors);
    /*try {
      cl = (ColorScale) super.clone();
    } catch(CloneNotSupportedException e) {
      return null;
    } // ignore

    // non primitive fields must be cloned separately
    cl.name = new String(name);
    cl.set(this);
    cl.construct();  */
    return (Object) cl;
  }

  /////////// private ////////////////////

  // this is for editing a colorscale
  private void editModeBegin() {
    Color [] editColors = new Color[ ncolors];
    System.arraycopy(colors, 0, editColors, 0, ncolors);
    useColors = editColors;
  }

  private void editModeEnd(boolean accept) {
    if (accept) {
      System.arraycopy(useColors, 0, colors, 0, ncolors);
    }
    useColors = colors;
  }

  private void setColor(int i, Color c) {
    if (i >= 0 && i < ncolors)
      useColors[i] = c;
  }

  /* private void set( ColorScale cs) {
   set(cs.getColors());
 } */
  private void setColors(Color[] c) {
    ncolors = c.length;
    colors = new Color[ ncolors];
    System.arraycopy(c, 0, colors, 0, ncolors);
    edge = new double[ ncolors];
    hist = new int[ ncolors + 1];
    useColors = colors;        // ??
  }

  // serialization
  private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
    s.readInt();
    this.name = s.readUTF();
    this.colors = (Color[]) s.readObject();
    this.ncolors = colors.length;
    constructTransient();
  }

  private void writeObject(ObjectOutputStream s) throws IOException {
    s.writeInt(objectVersion);
    s.writeUTF(this.name);
    s.writeObject(this.colors);
  }

  // heres the swing component part of a ColorScale: static so its not part of serialization
  // originally designed to allow popup editor to change colors; this has been disabled; all
  // changes made by COlorManager. design could be cleaned up, but maybe I want that back later?
  public static class Panel extends JPanel {
    private int type;

    private int size = 50;
    private ColorScale cs;
    private JLabel unitLabel = new JLabel("unit", SwingConstants.CENTER);
    private JPanel lpanel;

    private boolean editable = false;
    private int selected = -1;

    private int nColorInterval;
    private String[] label;
    private boolean useLabel = true;
    private FontUtil.StandardFont sf = FontUtil.getStandardFont(10);

    public Panel(Component parent) {
      this(parent, ColorScale.VERTICAL, null);
    }

    public Panel(Component parent, ColorScale cscale) {
      this(parent, ColorScale.VERTICAL, cscale);
    }

    public Panel(Component parent, int type, ColorScale cscale) {
      this.cs = (cscale == null) ? new ColorScale("default") : cscale;
      this.type = type;

      if (type == ColorScale.VERTICAL) {
        setPreferredSize(new Dimension(size, 400));
        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
      } else {
        setPreferredSize(new Dimension(400, size));
        setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
      }

      setListener();

      nColorInterval = cs.getNumColors();
      for (int i = 0; i < nColorInterval; i++) {
        ColorInterval intv = new ColorInterval(nColorInterval - i - 1);
        add(intv);
      }
      lpanel = new JPanel();
      lpanel.add(unitLabel);
      //unitLabel.setBorder( new javax.swing.border.EtchedBorder());
      if (type == ColorScale.VERTICAL)
        lpanel.setPreferredSize(new Dimension(size, 0));
      else
        lpanel.setPreferredSize(new Dimension(0, size));
      add(lpanel);

      label = new String[ nColorInterval];
      calcLabels();

      /* creates a popup men, attaches it to this Panel
     PopupMenu popupMenu = new PopupMenu(this, "options");
     popupMenu.add("Edit", new AbstractAction() {
       public void actionPerformed(ActionEvent e) {
         System.out.println("popup edit action");
         dialog.show();
       }
     }); */

    }

    private void calcLabels() {
      label[0] = "<" + Format.d(cs.getEdge(0), sigfig);
      for (int i = 1; i < nColorInterval - 1; i++) {
        label[i] = Format.d(cs.getEdge(i), sigfig);
      }
      label[nColorInterval - 1] = ">" + Format.d(cs.getEdge(nColorInterval - 2), sigfig);
    }

    private void setListener() {
      // listen for changes so we can redraw
      cs.addPropertyChangeListener(new java.beans.PropertyChangeListener() {
        public void propertyChange(java.beans.PropertyChangeEvent e) {
          if (e.getPropertyName().equals("ColorScaleLimits"))
            calcLabels();
          repaint();
        }
      });
    }

    public ColorScale getColorScale() {
      return cs;
    }

    // change the current cscale
    public void setColorScale(ColorScale cscale) {
      if (nColorInterval != cscale.getNumColors()) {
        removeAll();
        nColorInterval = cscale.getNumColors();
        label = new String[ nColorInterval];
        for (int i = 0; i < nColorInterval; i++) {
          ColorInterval intv = new ColorInterval(nColorInterval - i - 1);
          add(intv);
          label[i] = "none";
        }
        add(lpanel);
        revalidate();
      }

      this.cs = cscale;
      setListener();
      calcLabels();
      repaint();
    }

    // set existing colorscale to have new colors
    public void setColors(Color[] c) {
      if (nColorInterval != c.length) {
        removeAll();
        nColorInterval = c.length;
        label = new String[ nColorInterval];
        for (int i = 0; i < nColorInterval; i++) {
          ColorInterval intv = new ColorInterval(nColorInterval - i - 1);
          add(intv);
          label[i] = "none";
        }
        add(lpanel);
        revalidate();
      }

      cs.setColors(c);
      cs.editModeBegin(); // again

      if (debugColors) {
        for (int i = 0; i < cs.getNumColors(); i++)
          System.out.println(cs.getColor(i));
      }
      calcLabels();
      repaint();
    }


    public void setColor(Color c) {
      cs.setColor(selected, c);
    }

    //public void setEditable( boolean b) { editable = b; }
    public void setEditMode(boolean on, boolean accept) {
      if (on) {
        editable = true;
        cs.editModeBegin();
      } else {
        cs.editModeEnd(accept);
        if (accept)
          setColorScale(cs);
        selected = -1;
        editable = false;
      }
      repaint();
    }

    public void setSelected(int i) {
      selected = i;
    }

    public void setShowText(boolean b) {
      useLabel = b;
    }

    /*private void edit(int which) {
      if (!editable)
        return;
      selected = which;
      repaint();
      cs.setEditMode(true);
      //dialog.show();
    }*/
    public void setUnitString(String s) {
      unitLabel.setText(s);
      //System.out.println("new text = "+s);
      //unitLabel.repaint(s);
    }

    public void print(Graphics2D g, double x, double y, double width, double height) {
      int n = cs.getNumColors();
      double size = (type == ColorScale.VERTICAL) ? height / n : width / n;
      int count = 0;
      for (int i = 0; i < getComponentCount(); i++) {
        Component c = getComponent(i);
        if (c instanceof ColorInterval) {
          ColorInterval intv = (ColorInterval) c;
          if (type == ColorScale.VERTICAL)
            intv.printV(g, (int) x, (int) (y + count * size), (int) width, (int) size);
          else {
            double xpos = x + width - (count + 1) * size;
            intv.printH(g, (int) xpos, (int) y, (int) size, (int) height);
          }
          count++;
        }
      }
    }

    /* private class OkListener implements ActionListener {
      public void actionPerformed(ActionEvent e) {
        selected = -1;
        cs.accept();
        repaint();
      }
    }  // end inner class OkListener

    private class CancelListener implements ActionListener {
      public void actionPerformed(ActionEvent e) {
        selected = -1;
        cs.cancel();
        repaint();
      }
    } // end inner class CancelListener  */

    private class ColorInterval extends JComponent {
      private int rank;

      ColorInterval(int r) {
        this.rank = r;
        //setPreferredSize( new Dimension(40,40));
        addMouseListener(new MouseAdapter() {
          public void mousePressed(MouseEvent e) {
            if (editable) {
              selected = rank;
              Panel.this.repaint();
            }
          }
        });
      }

      public void printV(Graphics2D g, int x, int y, int width, int height) {
        int textSize = 15; // LOOK : neeed to calculate this

        g.setColor(cs.getColor(rank));
        g.fillRect(x, y + textSize, width, height - textSize);

        //g.setColor( Color.black);
        //g.drawRect( x, y+textSize, width, height-textSize);

        if (useLabel) {
          g.setColor(Color.black);
          g.setFont(sf.getFont());
          g.drawString(label[rank], x + 3, y + 10);
        }
      }

      public void printH(Graphics2D g, int x, int y, int width, int height) {
        int textSize = 15; // LOOK : neeed to calculate this

        g.setColor(cs.getColor(rank));
        g.fillRect(x, y + textSize, width, height - 2 * textSize);

        g.setColor(Color.white);
        g.drawRect(x, y + textSize, width, height - 2 * textSize);

        if (useLabel) {
          g.setColor(Color.black);
          g.setFont(sf.getFont());
          if (rank % 2 == 0) // even
            g.drawString(label[rank], x, y + textSize);
          else
            g.drawString(label[rank], x, y + height);
        }
      }

      public void paintComponent(Graphics g) {
        Rectangle b = getBounds();

        g.setColor(cs.getColor(rank));
        g.fillRect(0, 0, b.width - 1, b.height - 1);

        g.setColor((selected == rank) ? Color.magenta : Color.black);
        g.drawRect(0, 0, b.width - 1, b.height - 1);

        if (selected == rank) {
          g.drawLine(0, 0, b.width, b.height);
          g.drawLine(0, b.height, b.width, 0);
        }

        if (useLabel) {
          g.setColor(Color.black);
          g.setFont(sf.getFont());
          g.drawString(label[rank], 3, 10);
        }
      }
    } // end inner class ColorInterval


  } // end inner class ColorScale.Panel
}
public void setMinMax(double min, double max) {
    this.min = min;
    this.max = max;
    interval = (max - min) / (ncolors - 2);

    // set edges
    for (int i = 0; i < ncolors; i++)
      edge[i] = min + i * interval;

    lm.sendEvent(new PropertyChangeEvent(this, "ColorScaleLimits", null, this));
  }


  /**
   * This is an optimization for counting the number of colors in each interval.
   * the histpogram is populated by calls to getIndexFromValue().
   *
   * @return the index with the maximum histogram count.
   */
  public int getHistMax() {
    int max = 0, maxi = 0;
    for (int i = 0; i <= ncolors; i++)
      if (hist[i] > max) {
        max = hist[i];
        maxi = i;
      }
    return maxi;
  }

  /**
   * reset the histogram.
   */
  public void resetHist() {
    for (int i = 0; i <= ncolors; i++)
      hist[i] = 0;
  }

  public String toString() {
    return name;
  }

  public Object clone() {
    ColorScale cl = new ColorScale(name, colors);
    /*try {
      cl = (ColorScale) super.clone();
    } catch(CloneNotSupportedException e) {
      return null;
    } // ignore

    // non primitive fields must be cloned separately
    cl.name = new String(name);
    cl.set(this);
    cl.construct();  */
    return (Object) cl;
  }

  /////////// private ////////////////////

  // this is for editing a colorscale
  private void editModeBegin() {
    Color [] editColors = new Color[ ncolors];
    System.arraycopy(colors, 0, editColors, 0, ncolors);
    useColors = editColors;
  }

  private void editModeEnd(boolean accept) {
    if (accept) {
      System.arraycopy(useColors, 0, colors, 0, ncolors);
    }
    useColors = colors;
  }

  private void setColor(int i, Color c) {
    if (i >= 0 && i < ncolors)
      useColors[i] = c;
  }

  /* private void set( ColorScale cs) {
   set(cs.getColors());
 } */
  private void setColors(Color[] c) {
    ncolors = c.length;
    colors = new Color[ ncolors];
    System.arraycopy(c, 0, colors, 0, ncolors);
    edge = new double[ ncolors];
    hist = new int[ ncolors + 1];
    useColors = colors;        // ??
  }

  // serialization
  private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
    s.readInt();
    this.name = s.readUTF();
    this.colors = (Color[]) s.readObject();
    this.ncolors = colors.length;
    constructTransient();
  }

  private void writeObject(ObjectOutputStream s) throws IOException {
    s.writeInt(objectVersion);
    s.writeUTF(this.name);
    s.writeObject(this.colors);
  }

  // heres the swing component part of a ColorScale: static so its not part of serialization
  // originally designed to allow popup editor to change colors; this has been disabled; all
  // changes made by COlorManager. design could be cleaned up, but maybe I want that back later?
  public static class Panel extends JPanel {
    private int type;

    private int size = 50;
    private ColorScale cs;
    private JLabel unitLabel = new JLabel("unit", SwingConstants.CENTER);
    private JPanel lpanel;

    private boolean editable = false;
    private int selected = -1;

    private int nColorInterval;
    private String[] label;
    private boolean useLabel = true;
    private FontUtil.StandardFont sf = FontUtil.getStandardFont(10);

    public Panel(Component parent) {
      this(parent, ColorScale.VERTICAL, null);
    }

    public Panel(Component parent, ColorScale cscale) {
      this(parent, ColorScale.VERTICAL, cscale);
    }

    public Panel(Component parent, int type, ColorScale cscale) {
      this.cs = (cscale == null) ? new ColorScale("default") : cscale;
      this.type = type;

      if (type == ColorScale.VERTICAL) {
        setPreferredSize(new Dimension(size, 400));
        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
      } else {
        setPreferredSize(new Dimension(400, size));
        setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
      }

      setListener();

      nColorInterval = cs.getNumColors();
      for (int i = 0; i < nColorInterval; i++) {
        ColorInterval intv = new ColorInterval(nColorInterval - i - 1);
        add(intv);
      }
      lpanel = new JPanel();
      lpanel.add(unitLabel);
      //unitLabel.setBorder( new javax.swing.border.EtchedBorder());
      if (type == ColorScale.VERTICAL)
        lpanel.setPreferredSize(new Dimension(size, 0));
      else
        lpanel.setPreferredSize(new Dimension(0, size));
      add(lpanel);

      label = new String[ nColorInterval];
      calcLabels();

      /* creates a popup men, attaches it to this Panel
     PopupMenu popupMenu = new PopupMenu(this, "options");
     popupMenu.add("Edit", new AbstractAction() {
       public void actionPerformed(ActionEvent e) {
         System.out.println("popup edit action");
         dialog.show();
       }
     }); */

    }

    private void calcLabels() {
      label[0] = "<" + Format.d(cs.getEdge(0), sigfig);
      for (int i = 1; i < nColorInterval - 1; i++) {
        label[i] = Format.d(cs.getEdge(i), sigfig);
      }
      label[nColorInterval - 1] = ">" + Format.d(cs.getEdge(nColorInterval - 2), sigfig);
    }

    private void setListener() {
      // listen for changes so we can redraw
      cs.addPropertyChangeListener(new java.beans.PropertyChangeListener() {
        public void propertyChange(java.beans.PropertyChangeEvent e) {
          if (e.getPropertyName().equals("ColorScaleLimits"))
            calcLabels();
          repaint();
        }
      });
    }

    public ColorScale getColorScale() {
      return cs;
    }

    // change the current cscale
    public void setColorScale(ColorScale cscale) {
      if (nColorInterval != cscale.getNumColors()) {
        removeAll();
        nColorInterval = cscale.getNumColors();
        label = new String[ nColorInterval];
        for (int i = 0; i < nColorInterval; i++) {
          ColorInterval intv = new ColorInterval(nColorInterval - i - 1);
          add(intv);
          label[i] = "none";
        }
        add(lpanel);
        revalidate();
      }

      this.cs = cscale;
      setListener();
      calcLabels();
      repaint();
    }

    // set existing colorscale to have new colors
    public void setColors(Color[] c) {
      if (nColorInterval != c.length) {
        removeAll();
        nColorInterval = c.length;
        label = new String[ nColorInterval];
        for (int i = 0; i < nColorInterval; i++) {
          ColorInterval intv = new ColorInterval(nColorInterval - i - 1);
          add(intv);
          label[i] = "none";
        }
        add(lpanel);
        revalidate();
      }

      cs.setColors(c);
      cs.editModeBegin(); // again

      if (debugColors) {
        for (int i = 0; i < cs.getNumColors(); i++)
          System.out.println(cs.getColor(i));
      }
      calcLabels();
      repaint();
    }


    public void setColor(Color c) {
      cs.setColor(selected, c);
    }

    //public void setEditable( boolean b) { editable = b; }
    public void setEditMode(boolean on, boolean accept) {
      if (on) {
        editable = true;
        cs.editModeBegin();
      } else {
        cs.editModeEnd(accept);
        if (accept)
          setColorScale(cs);
        selected = -1;
        editable = false;
      }
      repaint();
    }

    public void setSelected(int i) {
      selected = i;
    }

    public void setShowText(boolean b) {
      useLabel = b;
    }

    /*private void edit(int which) {
      if (!editable)
        return;
      selected = which;
      repaint();
      cs.setEditMode(true);
      //dialog.show();
    }*/
    public void setUnitString(String s) {
      unitLabel.setText(s);
      //System.out.println("new text = "+s);
      //unitLabel.repaint(s);
    }

    public void print(Graphics2D g, double x, double y, double width, double height) {
      int n = cs.getNumColors();
      double size = (type == ColorScale.VERTICAL) ? height / n : width / n;
      int count = 0;
      for (int i = 0; i < getComponentCount(); i++) {
        Component c = getComponent(i);
        if (c instanceof ColorInterval) {
          ColorInterval intv = (ColorInterval) c;
          if (type == ColorScale.VERTICAL)
            intv.printV(g, (int) x, (int) (y + count * size), (int) width, (int) size);
          else {
            double xpos = x + width - (count + 1) * size;
            intv.printH(g, (int) xpos, (int) y, (int) size, (int) height);
          }
          count++;
        }
      }
    }

    /* private class OkListener implements ActionListener {
      public void actionPerformed(ActionEvent e) {
        selected = -1;
        cs.accept();
        repaint();
      }
    }  // end inner class OkListener

    private class CancelListener implements ActionListener {
      public void actionPerformed(ActionEvent e) {
        selected = -1;
        cs.cancel();
        repaint();
      }
    } // end inner class CancelListener  */

    private class ColorInterval extends JComponent {
      private int rank;

      ColorInterval(int r) {
        this.rank = r;
        //setPreferredSize( new Dimension(40,40));
        addMouseListener(new MouseAdapter() {
          public void mousePressed(MouseEvent e) {
            if (editable) {
              selected = rank;
              Panel.this.repaint();
            }
          }
        });
      }

      public void printV(Graphics2D g, int x, int y, int width, int height) {
        int textSize = 15; // LOOK : neeed to calculate this

        g.setColor(cs.getColor(rank));
        g.fillRect(x, y + textSize, width, height - textSize);

        //g.setColor( Color.black);
        //g.drawRect( x, y+textSize, width, height-textSize);

        if (useLabel) {
          g.setColor(Color.black);
          g.setFont(sf.getFont());
          g.drawString(label[rank], x + 3, y + 10);
        }
      }

      public void printH(Graphics2D g, int x, int y, int width, int height) {
        int textSize = 15; // LOOK : neeed to calculate this

        g.setColor(cs.getColor(rank));
        g.fillRect(x, y + textSize, width, height - 2 * textSize);

        g.setColor(Color.white);
        g.drawRect(x, y + textSize, width, height - 2 * textSize);

        if (useLabel) {
          g.setColor(Color.black);
          g.setFont(sf.getFont());
          if (rank % 2 == 0) // even
            g.drawString(label[rank], x, y + textSize);
          else
            g.drawString(label[rank], x, y + height);
        }
      }

      public void paintComponent(Graphics g) {
        Rectangle b = getBounds();

        g.setColor(cs.getColor(rank));
        g.fillRect(0, 0, b.width - 1, b.height - 1);

        g.setColor((selected == rank) ? Color.magenta : Color.black);
        g.drawRect(0, 0, b.width - 1, b.height - 1);

        if (selected == rank) {
          g.drawLine(0, 0, b.width, b.height);
          g.drawLine(0, b.height, b.width, 0);
        }

        if (useLabel) {
          g.setColor(Color.black);
          g.setFont(sf.getFont());
          g.drawString(label[rank], 3, 10);
        }
      }
    } // end inner class ColorInterval


  } // end inner class ColorScale.Panel
}
Set the data min/max interval. The color intervals are set based on this.
A PropertyChangeEvent is sent when this is called. Currently the intervals are
calculated in the following way (where incr = (max-min)/(n-2)) :
<pre>
<p/>
edge           data interval
0    min             value <= min
1    min+incr        min <= value < min + incr
2    min+2*incr      min+incr <= value < min+2*incr
ith  min+i*incr      min+(i-1)*incr <= value < min+i*incr
n-2  max             min+(n-3)*incr <= value < max
n-1  max             max < value
n                    value = missingDataValue
</pre>

@param min minimum data value
@param max maximum data value