001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2026
005 * Space Science and Engineering Center (SSEC)
006 * University of Wisconsin - Madison
007 * 1225 W. Dayton Street, Madison, WI 53706, USA
008 * https://www.ssec.wisc.edu/mcidas/
009 * 
010 * All Rights Reserved
011 * 
012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
013 * some McIDAS-V source code is based on IDV and VisAD source code.  
014 * 
015 * McIDAS-V is free software; you can redistribute it and/or modify
016 * it under the terms of the GNU Lesser Public License as published by
017 * the Free Software Foundation; either version 3 of the License, or
018 * (at your option) any later version.
019 * 
020 * McIDAS-V is distributed in the hope that it will be useful,
021 * but WITHOUT ANY WARRANTY; without even the implied warranty of
022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
023 * GNU Lesser Public License for more details.
024 * 
025 * You should have received a copy of the GNU Lesser Public License
026 * along with this program.  If not, see https://www.gnu.org/licenses/.
027 */
028
029
030package edu.wisc.ssec.mcidasv.control;
031
032import java.awt.BorderLayout;
033import java.awt.Color;
034import java.awt.Component;
035import java.awt.Container;
036
037import java.awt.FlowLayout;
038import java.awt.GridLayout;
039import java.awt.event.ActionEvent;
040import java.awt.event.ActionListener;
041import java.awt.geom.Rectangle2D;
042import java.net.URL;
043import java.rmi.RemoteException;
044import java.util.ArrayList;
045import java.util.Hashtable;
046import java.util.List;
047
048import javax.swing.ButtonGroup;
049import javax.swing.ImageIcon;
050import javax.swing.JComponent;
051import javax.swing.JLabel;
052import javax.swing.JPanel;
053import javax.swing.JRadioButton;
054import javax.swing.JToggleButton;
055import javax.swing.JButton;
056import javax.swing.border.CompoundBorder;
057import javax.swing.border.EmptyBorder;
058import javax.swing.border.LineBorder;
059
060import org.slf4j.Logger;
061import org.slf4j.LoggerFactory;
062
063import ucar.unidata.idv.ControlContext;
064import visad.AxisScale;
065import visad.BaseColorControl;
066import visad.CellImpl;
067import visad.CoordinateSystem;
068import visad.Data;
069import visad.DelaunayCustom;
070import visad.DisplayEvent;
071import visad.DisplayListener;
072
073import visad.FieldImpl;
074import visad.FlatField;
075import visad.FunctionType;
076import visad.Gridded2DSet;
077import visad.Gridded3DSet;
078import visad.Integer1DSet;
079import visad.Linear2DSet;
080import visad.LinearLatLonSet;
081import visad.RealTupleType;
082import visad.MathType;
083import visad.RealType;
084import visad.SampledSet;
085import visad.ScalarMap;
086import visad.Set;
087import visad.SetType;
088import visad.UnionSet;
089import visad.VisADException;
090import visad.data.mcidas.BaseMapAdapter;
091import visad.georef.MapProjection;
092import visad.georef.TrivialMapProjection;
093import visad.python.JPythonMethods;
094
095import ucar.unidata.data.DataAlias;
096import ucar.unidata.data.DataChoice;
097import ucar.unidata.data.DataSelection;
098import ucar.unidata.data.grid.GridUtil;
099import ucar.unidata.idv.DisplayConventions;
100import ucar.unidata.idv.control.ColorTableWidget;
101import ucar.unidata.idv.control.DisplayControlImpl;
102import ucar.unidata.ui.colortable.ColorTableManager;
103import ucar.unidata.util.ColorTable;
104import ucar.unidata.util.LogUtil;
105import ucar.unidata.util.Range;
106import ucar.unidata.view.geoloc.MapProjectionDisplay;
107import ucar.unidata.view.geoloc.MapProjectionDisplayJ3D;
108import ucar.visad.display.DisplayMaster;
109import ucar.visad.display.LineDrawing;
110import ucar.visad.display.MapLines;
111import ucar.visad.display.RGBDisplayable;
112import ucar.visad.display.RubberBandBox;
113import ucar.visad.display.XYDisplay;
114
115import edu.wisc.ssec.mcidasv.data.hydra.CurveDrawer;
116import edu.wisc.ssec.mcidasv.data.hydra.HistogramField;
117import edu.wisc.ssec.mcidasv.data.hydra.HydraRGBDisplayable;
118import edu.wisc.ssec.mcidasv.data.hydra.MultiSpectralData;
119import edu.wisc.ssec.mcidasv.data.hydra.SubsetRubberBandBox;
120import edu.wisc.ssec.mcidasv.data.hydra.LongitudeLatitudeCoordinateSystem;
121import edu.wisc.ssec.mcidasv.data.StatsTable;
122
123public class ScatterDisplay extends DisplayControlImpl {
124
125    private static final Logger logger =
126        LoggerFactory.getLogger(ScatterDisplay.class);
127
128    private Container container;
129    private FlatField X_field;
130    private FlatField Y_field;
131    private FlatField Area_field;
132    private double total_area;
133    private DisplayMaster scatterMaster = null;
134
135    private DisplayMaster dspMasterX;
136    private DisplayMaster dspMasterY;
137
138    private HistogramField histoField;
139
140    private FlatField mask_field;
141    private float[][] mask_range;
142    private float[][] scatterFieldRange;
143    private Data X_data;
144    private Data Y_data;
145    private String X_name;
146    private String Y_name;
147    
148    private boolean cancel = false;
149
150    private ScatterDisplayable scatterMarkDsp;
151
152    private BoxCurveSwitch boxCurveSwitch;
153
154    public DataChoice dataChoiceX = null;
155
156    public DataChoice dataChoiceY = null;
157
158    public DataSelection dataSelectionX = null;
159
160    public DataSelection dataSelectionY = null;
161
162    JComponent ctwCompX;
163
164    JComponent ctwCompY;
165
166    ColorTableWidget ctw;
167
168    int n_selectors = 3;
169
170    List<ScatterBoxSelector> scatterBoxSelectors = new ArrayList<>();
171
172    List<ScatterCurveSelector> scatterCurveSelectors = new ArrayList<>();
173
174    List<ImageBoxSelector> imageXBoxSelectors = new ArrayList<>();
175
176    List<ImageBoxSelector> imageYBoxSelectors = new ArrayList<>();
177
178    List<ImageCurveSelector> imageXCurveSelectors = new ArrayList<>();
179
180    List<ImageCurveSelector> imageYCurveSelectors = new ArrayList<>();
181
182    JToggleButton[] selectorToggleButtons = new JToggleButton[n_selectors];
183
184    Color[] selectorColors = new Color[] {
185        Color.magenta,
186        Color.green,
187        Color.blue
188    };
189
190    float[][] maskColorPalette = new float[][] {
191        { 0.8f, 0.0f, 0.0f },
192        { 0.0f, 0.8f, 0.0f },
193        { 0.8f, 0.0f, 0.8f }
194    };
195
196    float[][] markPaletteBlackBackground = new float[][] {
197        { 1.0f, 0.8f, 0.0f, 0.0f },
198        { 1.0f, 0.0f, 0.8f, 0.0f },
199        { 1.0f, 0.8f, 0.0f, 0.8f }
200    };
201
202    float[][] markPaletteWhiteBackground = new float[][] {
203        { 0.0f, 0.8f, 0.0f, 0.0f },
204        { 0.0f, 0.0f, 0.8f, 0.0f },
205        { 0.0f, 0.8f, 0.0f, 0.8f }
206    };
207
208    /** used for persistence */
209    private boolean blackBackground = true;
210    
211    JRadioButton bgColorBlack;
212    
213    JRadioButton bgColorWhite;
214    
215    ButtonGroup bgColorGroup;
216    
217    JButton computeStatsButton;
218    
219    StatsTable statsTable;
220   
221    boolean selectByCurve = false;
222
223    public ScatterDisplay() {
224      super();
225      setHelpUrl("idv.controls.misc.scatteranalysiscontrol");
226    }
227    
228
229    @Override public boolean init(List choices) throws VisADException, RemoteException {
230        bgColorBlack = new JRadioButton("Black");
231        bgColorBlack.addActionListener(e -> {
232            scatterMaster.setForeground(Color.white);
233            scatterMaster.setBackground(Color.black);
234            setBlackBackground(true);
235            try {
236                scatterMarkDsp.setColorPalette(markPaletteBlackBackground);
237            } catch (Exception ex) {
238                logger.error("could not change color palette", ex);
239            }
240        });
241
242        bgColorWhite = new JRadioButton("White");
243        bgColorWhite.addActionListener(e -> {
244            scatterMaster.setForeground(Color.black);
245            scatterMaster.setBackground(Color.white);
246            setBlackBackground(false);
247            try {
248                scatterMarkDsp.setColorPalette(markPaletteWhiteBackground);
249            } catch (Exception ex) {
250                logger.error("could not change color palette", ex);
251            }
252        });
253
254        bgColorGroup = new ButtonGroup();
255        bgColorGroup.add(bgColorBlack);
256        bgColorGroup.add(bgColorWhite);
257
258        bgColorBlack.setSelected(getBlackBackground());
259        bgColorWhite.setSelected(!getBlackBackground());
260        
261        if ((dataChoiceX != null) && (dataChoiceY != null)) {
262          setupFromUnpersistence();
263        } else {
264            try {
265                setup();
266            } catch (VisADException vade) {
267                return false;
268            }
269        }
270
271        mask_field = new FlatField(
272             new FunctionType(((FunctionType)X_field.getType()).getDomain(), RealType.Generic),
273                  X_field.getDomainSet());
274
275        int len = X_field.getDomainSet().getLength();
276        int[] lens = ((Gridded2DSet)X_field.getDomainSet()).getLengths();
277        mask_range = new float[1][len];
278        for (int t=0; t<len; t++) {
279          mask_range[0][t] = Float.NaN;
280        }
281        mask_range[0][0] = 0; //- field should not be all missing
282        mask_field.setSamples(mask_range, false);
283                                                                                                                                                  
284        try {
285          int binSize = ((lens[0]*lens[1]/(256*256))*4)/10;
286          if (binSize < 2) binSize = 2;
287          histoField = new HistogramField(X_field, Y_field, mask_field, 256, binSize);
288        } catch (Exception e) {
289          logger.error("Problem creating HistogramField", e);
290        }
291
292        Range rangeX = getImageRange(X_field);
293        Range rangeY = getImageRange(Y_field);
294        ColorTable clrTableX = getColorTable(X_field);
295        ColorTable clrTableY = getColorTable(Y_field);
296
297        dspMasterX = makeImageDisplay(getDataProjection(X_field), X_field, mask_field, 
298                         rangeX, clrTableX);
299
300        dspMasterY = makeImageDisplay(getDataProjection(Y_field), Y_field, mask_field, 
301                         rangeY, clrTableY);
302
303        dspMasterX.addDisplayListener(e -> {
304            double[] xProjection = dspMasterX.getProjectionMatrix();
305            double[] yProjection = dspMasterY.getProjectionMatrix();
306            if (xProjection.equals(yProjection))
307                return;
308
309            try {
310                dspMasterY.setProjectionMatrix(xProjection);
311            } catch (Exception ex) {
312                LogUtil.logException("dspMasterX.displayChanged", ex);
313            }
314        });
315
316        dspMasterY.addDisplayListener(e -> {
317            double[] xProjection = dspMasterX.getProjectionMatrix();
318            double[] yProjection = dspMasterY.getProjectionMatrix();
319            if (yProjection.equals(xProjection))
320                return;
321
322            try {
323                dspMasterX.setProjectionMatrix(yProjection);
324            } catch (Exception ex) {
325                LogUtil.logException("dspMasterX.displayChanged", ex);
326            }
327        });
328
329        X_name = ((((FunctionType)X_field.getType()).getFlatRange().getRealComponents())[0]).getName();
330        Y_name = ((((FunctionType)Y_field.getType()).getFlatRange().getRealComponents())[0]).getName();
331
332        if (statsTable != null) statsTable.setNames(X_name, Y_name);
333
334        Grid2DReadoutProbe probeX = new Grid2DReadoutProbe(X_field, dspMasterX);
335        Grid2DReadoutProbe probeY = new Grid2DReadoutProbe(Y_field, dspMasterY);
336        probeX.doMakeProbe(Color.red, dspMasterX);
337        probeY.doMakeProbe(Color.red, dspMasterY);
338        
339        ImageControl dCntrl = new ImageControl((HydraRGBDisplayable)dspMasterX.getDisplayables(0), getDisplayConventions());
340        ctw = new ColorTableWidget(dCntrl, ColorTableManager.getManager(), clrTableX, rangeX);
341        ctwCompX = ctw.getLegendPanel(BOTTOM_LEGEND);
342        dCntrl.ctw = ctw;
343
344        dCntrl = new ImageControl((HydraRGBDisplayable)dspMasterY.getDisplayables(0), getDisplayConventions());
345        ctw = new ColorTableWidget(dCntrl, ColorTableManager.getManager(), clrTableY, rangeY);
346        ctwCompY = ctw.getLegendPanel(BOTTOM_LEGEND);
347        dCntrl.ctw = ctw;
348
349        return true;
350    }
351
352    public void setup() throws VisADException, RemoteException {
353        dataSelectionX = getDataSelection();
354        dataChoiceX = getDataChoice();
355        X_data = dataChoiceX.getData(dataSelectionX);
356
357        if (X_data instanceof FlatField) {
358          X_field = (FlatField) X_data;
359        } else if (X_data instanceof FieldImpl) {
360          X_field = (FlatField) ((FieldImpl)X_data).getSample(0);
361        }
362
363        popupDataDialog("select Y Axis field", container, false, null);
364        
365        // if user canceled the popup, popupDataDialog will set the cancel flag
366        if (cancel) throw new VisADException("Scatter Display Canceled");
367
368        dataSelectionY = getDataSelection();
369        dataChoiceY = getDataChoice();
370
371        dataSelectionY.setGeoSelection(dataSelectionX.getGeoSelection());
372                                                                                                                                                  
373        Y_data = dataChoiceY.getData(dataSelectionY);
374
375        if (Y_data instanceof FlatField) {
376          Y_field = (FlatField) Y_data;
377        } else if (Y_data instanceof FieldImpl) {
378          Y_field = (FlatField) ((FieldImpl)Y_data).getSample(0);
379        }
380
381        if (!( X_field.getDomainSet().equals(Y_field.getDomainSet())))
382        {
383          Y_field = resample(X_field, Y_field);
384        }
385
386        Area_field = JPythonMethods.createAreaField(X_field);
387        statsTable = new StatsTable();
388    }
389
390    public void setupFromUnpersistence() throws VisADException, RemoteException {
391        X_data = dataChoiceX.getData(dataSelectionX);
392        if (X_data instanceof FlatField) {
393          X_field = (FlatField) X_data;
394        } else if (X_data instanceof FieldImpl) {
395          X_field = (FlatField) ((FieldImpl)X_data).getSample(0);
396        }
397                                                                                                                                                  
398        Y_data = dataChoiceY.getData(dataSelectionY);
399        if (Y_data instanceof FlatField) {
400          Y_field = (FlatField) Y_data;
401        } else if (X_data instanceof FieldImpl) {
402          Y_field = (FlatField) ((FieldImpl)Y_data).getSample(0);
403        }
404    }
405
406    @Override public void initAfterUnPersistence(ControlContext vc,
407                                                 Hashtable properties,
408                                                 List preSelectedDataChoices) 
409    {
410        super.initAfterUnPersistence(vc, properties, preSelectedDataChoices);
411
412        Color fg;
413        Color bg;
414        float[][] bgPalette;
415        if (getBlackBackground()) {
416            fg = Color.white;
417            bg = Color.black;
418            bgPalette = markPaletteBlackBackground;
419        } else {
420            fg = Color.black;
421            bg = Color.white;
422            bgPalette = markPaletteWhiteBackground;
423        }
424        scatterMaster.setForeground(fg);
425        scatterMaster.setBackground(bg);
426        try {
427            scatterMarkDsp.setColorPalette(bgPalette);
428        } catch (Exception ex) {
429            logger.error("could not change color palette", ex);
430        }
431    }
432    
433    @Override protected void popupDataDialog(final String dialogMessage,
434                                   Component from, boolean multiples,
435                                   List categories) {
436
437        List choices = selectDataChoices(dialogMessage, from,
438                                       multiples, categories);
439        if ((choices == null) || (choices.size() == 0)) {
440            logger.debug("popupDataDialog, no data choice, user canceled");
441                cancel = true;
442            return;
443        }
444        List<DataChoice> tempList = new ArrayList<>(1);
445        // TODO(jon): why is choices.get(0) now an arraylist!?
446        DataChoice firstChoice;
447        if (choices.get(0) instanceof List) {
448            List tmp = (List)choices.get(0);
449            firstChoice = (DataChoice)tmp.get(0);
450        } else {
451            firstChoice = (DataChoice)choices.get(0);
452        }
453        tempList.add(firstChoice);
454        final List clonedList =
455            DataChoice.cloneDataChoices(tempList);
456        dataSelection = ((DataChoice) clonedList.get(0)).getDataSelection();
457        //- don't do this in a separate thread like the IDV does.
458        //- We want the dataChoice list updated before return.
459        try {
460          addNewData(clonedList);
461        } catch (Exception exc) {
462          logException("Selecting new data", exc);
463        }
464    }
465
466
467    @Override public void initDone() {
468       try {
469         DisplayMaster master = makeScatterDisplay();
470         for (int k=0; k<n_selectors; k++) {
471           scatterBoxSelectors.add(new ScatterBoxSelector(master, selectorColors[k], (float)k));
472           scatterCurveSelectors.add(new ScatterCurveSelector(master, selectorColors[k], (float)k));
473         }
474         master.draw();
475
476         for (int k=0; k<n_selectors; k++) {
477           SubsetRubberBandBox X_subsetBox =
478              new SubsetRubberBandBox(getIsLatLon(X_field), X_field,
479                      ((MapProjectionDisplayJ3D)dspMasterX).getDisplayCoordinateSystem(), 1, false);
480           X_subsetBox.setColor(selectorColors[k]);
481
482           ImageBoxSelector markX = new ImageBoxSelector(X_subsetBox, X_field.getDomainSet(), dspMasterX, selectorColors[k], (float)k+1, statsTable);
483
484           SubsetRubberBandBox Y_subsetBox =
485              new SubsetRubberBandBox(getIsLatLon(Y_field), Y_field,
486                 ((MapProjectionDisplayJ3D)dspMasterY).getDisplayCoordinateSystem(), 1, false);
487           Y_subsetBox.setColor(selectorColors[k]);
488           ImageBoxSelector markY = new ImageBoxSelector(Y_subsetBox, Y_field.getDomainSet(), dspMasterY, selectorColors[k], (float)k+1, statsTable);
489
490           markX.setOther(markY);
491           markY.setOther(markX);
492           imageXBoxSelectors.add(markX);
493           imageYBoxSelectors.add(markY);
494         }
495
496         for (int k=0; k<n_selectors; k++) {
497           CurveDrawer curveDraw = new CurveDrawer(RealType.Longitude, RealType.Latitude, 1);
498           curveDraw.setColor(selectorColors[k]);
499           curveDraw.setLineWidth(2);
500           ImageCurveSelector curveX = new ImageCurveSelector(curveDraw, X_field, dspMasterX, selectorColors[k], (float) k+1, statsTable);
501           curveX.setActive(false);
502           curveDraw.addAction(curveX);
503           curveX.setVisible(false);
504           dspMasterX.addDisplayable(curveDraw);
505
506           curveDraw = new CurveDrawer(RealType.Longitude, RealType.Latitude, 1);
507           curveDraw.setColor(selectorColors[k]);
508           curveDraw.setLineWidth(2);
509           ImageCurveSelector curveY = new ImageCurveSelector(curveDraw, Y_field, dspMasterY, selectorColors[k], (float) k+1, statsTable);
510           curveY.setActive(false);
511           curveDraw.addAction(curveY);
512           curveY.setVisible(false);
513           dspMasterY.addDisplayable(curveDraw);
514
515           curveX.setOther(curveY);
516           curveY.setOther(curveX);
517           imageXCurveSelectors.add(curveX);
518           imageYCurveSelectors.add(curveY);
519         }
520
521         for (int k=0; k<n_selectors; k++) {
522           JToggleButton jtog = selectorToggleButtons[k];
523          
524           jtog.addActionListener(e -> {
525              int idx = Integer.valueOf(e.getActionCommand());
526              try {
527                for (int i=0; i<n_selectors; i++) {
528                  ScatterBoxSelector boxSel = (ScatterBoxSelector) scatterBoxSelectors.get(i);
529                  ImageBoxSelector imageXbox = (ImageBoxSelector) imageXBoxSelectors.get(i);
530                  ImageBoxSelector imageYbox = (ImageBoxSelector) imageYBoxSelectors.get(i);
531                  ScatterCurveSelector curveSel = (ScatterCurveSelector) scatterCurveSelectors.get(i);
532                  ImageCurveSelector imageXcurve = (ImageCurveSelector) imageXCurveSelectors.get(i);
533                  ImageCurveSelector imageYcurve = (ImageCurveSelector) imageYCurveSelectors.get(i);
534
535                  if (i == idx) {
536                    if (!selectorToggleButtons[i].isSelected()) {
537
538                      if (statsTable != null) statsTable.resetValues(i);
539
540                      boxSel.reset();
541                      boxSel.setActive(false);
542                      boxSel.setVisible(false);
543
544                      imageXbox.reset();
545                      imageXbox.setActive(false);
546                      imageXbox.setVisible(false);
547
548                      imageYbox.reset();
549                      imageYbox.setActive(false);
550                      imageYbox.setVisible(false);
551
552                      curveSel.reset();
553                      curveSel.setActive(false);
554                      curveSel.setVisible(false);
555
556                      imageXcurve.reset();
557                      imageXcurve.setActive(false);
558                      imageXcurve.setVisible(false);
559                      imageYcurve.reset();
560                      imageYcurve.setActive(false);
561                      imageYcurve.setVisible(false);
562                      selectorToggleButtons[i].setSelected(true);
563                    }
564                    boxSel.setActive(!getSelectByCurve());
565                    boxSel.setVisible(!getSelectByCurve());
566                    imageXbox.setActive(!getSelectByCurve());
567                    imageXbox.setVisible(!getSelectByCurve());
568                    imageYbox.setActive(!getSelectByCurve());
569                    imageYbox.setVisible(!getSelectByCurve());
570
571                    curveSel.setActive(getSelectByCurve());
572                    curveSel.setVisible(getSelectByCurve());
573                    imageXcurve.setActive(getSelectByCurve());
574                    imageXcurve.setVisible(getSelectByCurve());
575                    imageYcurve.setActive(getSelectByCurve());
576                    imageYcurve.setVisible(getSelectByCurve());
577                  }
578                  else {
579                    selectorToggleButtons[i].setSelected(false);
580                    boxSel.setActive(false);
581                    boxSel.setVisible(false);
582                    imageXbox.setActive(false);
583                    imageXbox.setVisible(false);
584                    imageYbox.setActive(false);
585                    imageYbox.setVisible(false);
586                    curveSel.setActive(false);
587                    curveSel.setVisible(false);
588                    imageXcurve.setActive(false);
589                    imageXcurve.setVisible(false);
590                    imageYcurve.setActive(false);
591                    imageYcurve.setVisible(false);
592                  }
593                }
594              } catch (Exception exc) {
595                logger.error("Problem handling toggle", exc);
596              }
597           });
598
599           ScatterBoxSelector boxSel = (ScatterBoxSelector) scatterBoxSelectors.get(k);
600           ImageBoxSelector imageXbox = (ImageBoxSelector) imageXBoxSelectors.get(k);
601           ImageBoxSelector imageYbox = (ImageBoxSelector) imageYBoxSelectors.get(k);
602           ScatterCurveSelector curveSel = (ScatterCurveSelector) scatterCurveSelectors.get(k);
603           ImageCurveSelector imageXcurve = (ImageCurveSelector) imageXCurveSelectors.get(k);
604           ImageCurveSelector imageYcurve = (ImageCurveSelector) imageYCurveSelectors.get(k);
605
606           if (k == 0) {
607              jtog.setSelected(true);
608              boxSel.setActive(!getSelectByCurve());
609              boxSel.setVisible(!getSelectByCurve());
610              imageXbox.setActive(!getSelectByCurve());
611              imageXbox.setVisible(!getSelectByCurve());
612              imageYbox.setActive(!getSelectByCurve());
613              imageYbox.setVisible(!getSelectByCurve());
614
615              curveSel.setActive(getSelectByCurve());
616              curveSel.setVisible(getSelectByCurve());
617              imageXcurve.setActive(getSelectByCurve());
618              imageXcurve.setVisible(getSelectByCurve());
619              imageYcurve.setActive(getSelectByCurve());
620              imageYcurve.setVisible(getSelectByCurve());
621            }
622            else {
623              boxSel.setActive(false);
624              boxSel.setVisible(false);
625              imageXbox.setActive(false);
626              imageXbox.setVisible(false);
627              imageYbox.setActive(false);
628              imageYbox.setVisible(false);
629              curveSel.setActive(false);
630              curveSel.setVisible(false);
631              imageXcurve.setActive(false);
632              imageXcurve.setVisible(false);
633              imageYcurve.setActive(false);
634              imageYcurve.setVisible(false);
635           }
636         }
637       } catch (Exception e) {
638         logger.error("Problem initializing", e);
639       }
640    }
641    
642    public DisplayMaster makeScatterDisplay() throws VisADException, RemoteException {
643
644       ScatterDisplayable scatterDsp = new ScatterDisplayable("scatter",
645                   RealType.getRealType("mask"), markPaletteBlackBackground, false);
646       float[] valsX = X_field.getFloats(false)[0];
647       float[] valsY = Y_field.getFloats(false)[0];
648       Integer1DSet set = new Integer1DSet(valsX.length);
649       FlatField scatter = new FlatField(
650           new FunctionType(RealType.Generic,
651               new RealTupleType(RealType.XAxis, RealType.YAxis, RealType.getRealType("mask"))), set);
652       float[] mask = new float[valsX.length];
653       for (int k=0; k<mask.length; k++) {
654           mask[k] = 0;
655       }
656       scatterFieldRange = new float[][] {valsX, valsY, mask};
657       scatter.setSamples(scatterFieldRange);
658       scatterDsp.setPointSize(2f);
659       scatterDsp.setRangeForColor(0,n_selectors);
660
661       float[] xRange = minmax(valsX);
662       float[] yRange = minmax(valsY);
663       
664       scatterDsp.setData(scatter);
665
666       scatterMarkDsp = new ScatterDisplayable("scatter",
667                   RealType.getRealType("mask"), markPaletteBlackBackground, false);
668       set = new Integer1DSet(2);
669       scatter = new FlatField(
670           new FunctionType(RealType.Generic,
671               new RealTupleType(RealType.XAxis, RealType.YAxis, RealType.getRealType("mask"))), set);
672       scatterMarkDsp.setData(scatter);
673       scatterMarkDsp.setPointSize(2f);
674       scatterMarkDsp.setRangeForColor(0,n_selectors);
675
676       DisplayMaster master = scatterMaster;
677       ((XYDisplay)master).showAxisScales(true);
678       AxisScale scaleX = ((XYDisplay)master).getXAxisScale();
679       scaleX.setTitle(X_name);
680       AxisScale scaleY = ((XYDisplay)master).getYAxisScale();
681       scaleY.setTitle(Y_name);
682
683       ((XYDisplay)master).setXRange((double)xRange[0], (double)xRange[1]);
684       ((XYDisplay)master).setYRange((double)yRange[0], (double)yRange[1]);
685       master.addDisplayable(scatterDsp);
686       master.addDisplayable(scatterMarkDsp);
687
688       return master;
689    }
690
691    @Override public Container doMakeContents() {
692        JPanel pane = new JPanel(new GridLayout(1,3));
693
694        Component[] comps = new Component[] {null, null, null};
695        comps[0] = dspMasterX.getComponent();
696        comps[1] = dspMasterY.getComponent();
697        comps[2] = getScatterTabComponent();
698
699        JPanel panelX = new JPanel(new BorderLayout());
700        panelX.setBorder(new EmptyBorder(4,4,4,4));
701        panelX.add(comps[0], BorderLayout.CENTER);
702        panelX.add(ctwCompX, BorderLayout.SOUTH);
703
704        JPanel panelY = new JPanel(new BorderLayout());
705        panelY.setBorder(new EmptyBorder(4,4,4,4));
706        panelY.add(comps[1], BorderLayout.CENTER);
707        panelY.add(ctwCompY, BorderLayout.SOUTH);
708
709        JPanel panelS = new JPanel(new BorderLayout());
710        panelS.setBorder(new EmptyBorder(4,4,4,4));
711        panelS.add(comps[2], BorderLayout.CENTER);
712
713        pane.add(panelX);
714        pane.add(panelY);
715        pane.add(panelS);
716
717        
718        JPanel buttonPanel = new JPanel();
719        buttonPanel.setLayout(new FlowLayout());
720        JRadioButton boxSelect = new JRadioButton("Box");
721        boxSelect.setSelected(true);
722        JRadioButton curveSelect = new JRadioButton("Curve");
723        ButtonGroup buttonGroup = new ButtonGroup();
724        buttonGroup.add(boxSelect);
725        buttonGroup.add(curveSelect);
726        buttonPanel.add(boxSelect);
727        buttonPanel.add(curveSelect);
728
729        boxCurveSwitch = new BoxCurveSwitch();
730        boxSelect.addActionListener(boxCurveSwitch);
731        curveSelect.addActionListener(boxCurveSwitch);
732        
733
734        JPanel toggleButtonPanel = new JPanel(new FlowLayout());
735        for (int k=0; k<n_selectors; k++) {
736          JToggleButton jtog = 
737             new JToggleButton(
738                 new ImageIcon(getClass().getResource("/edu/wisc/ssec/mcidasv/resources/icons/buttons/subset12.jpg")));
739          jtog.setBorder(new CompoundBorder(new LineBorder(selectorColors[k],2), new EmptyBorder(4,4,4,4)));
740          jtog.setActionCommand(String.valueOf(k));
741          toggleButtonPanel.add(jtog);
742          selectorToggleButtons[k] = jtog;
743        }
744
745        buttonPanel.add(toggleButtonPanel);
746
747        JButton computeStatsButton = new JButton("Compute Statistics");
748
749        computeStatsButton.addActionListener(e -> {
750           if (statsTable == null) {
751             statsTable = new StatsTable();
752           }
753
754           statsTable.setIsShowing();
755           statsTable.setFields(X_field, Y_field,0);
756        });
757
758        JButton reset = new JButton("Reset Selections");
759        reset.addActionListener(e -> resetAllScatterSelectors());
760
761        buttonPanel.add(reset);
762        buttonPanel.add(computeStatsButton);
763        buttonPanel.add(new JLabel("Background Color:"));
764        buttonPanel.add(bgColorBlack);
765        buttonPanel.add(bgColorWhite);
766
767        //-container = pane;
768        JPanel new_pane = new JPanel(new BorderLayout());
769        new_pane.add(pane, BorderLayout.CENTER);
770        new_pane.add(buttonPanel, BorderLayout.SOUTH);
771        container = new_pane;
772        return container;
773    }
774
775    /**
776     * Reset the state of all scatter display selections.
777     */
778    public void resetAllScatterSelectors() {
779        int originalActiveIdx = 0;
780        boolean originalSelectByCurve = getSelectByCurve(); // Save user's current drawing shape mode
781        
782        for (int i = 0; i < n_selectors; i++) {
783            if (selectorToggleButtons[i] != null) {
784                if (selectorToggleButtons[i].isSelected()) originalActiveIdx = i;
785                
786                // 1. Force Box mode active and clear the box shape
787                selectByCurve = false;
788                selectorToggleButtons[i].doClick();
789                resetScatterSelector(i);
790                
791                // 2. Force Curve mode active and clear the curve shape
792                selectByCurve = true;
793                selectorToggleButtons[i].doClick();
794                resetScatterSelector(i);
795            }
796        }
797        
798        // Restore original UI states completely
799        selectByCurve = originalSelectByCurve;
800        if (selectorToggleButtons[originalActiveIdx] != null) {
801            selectorToggleButtons[originalActiveIdx].doClick();
802        }
803    }
804
805    /**
806     * Reset the state of a specific scatter display selection.
807     *
808     * @param idx Index of the scatter display selection within {@link #n_selectors}.
809     */
810    public void resetScatterSelector(int idx) {
811        try {
812            if (statsTable != null) {
813                statsTable.resetValues(idx);
814            }
815
816            ScatterBoxSelector boxSel = scatterBoxSelectors.get(idx);
817            ImageBoxSelector imageXbox = imageXBoxSelectors.get(idx);
818            ImageBoxSelector imageYbox = imageYBoxSelectors.get(idx);
819            ScatterCurveSelector curveSel = scatterCurveSelectors.get(idx);
820            ImageCurveSelector imageXcurve = imageXCurveSelectors.get(idx);
821            ImageCurveSelector imageYcurve = imageYCurveSelectors.get(idx);
822
823            boxSel.reset();
824            boxSel.setActive(false);
825            boxSel.setVisible(false);
826
827            imageXbox.reset();
828            imageXbox.setActive(false);
829            imageXbox.setVisible(false);
830
831            imageYbox.reset();
832            imageYbox.setActive(false);
833            imageYbox.setVisible(false);
834
835            curveSel.reset();
836            curveSel.setActive(false);
837            curveSel.setVisible(false);
838
839            imageXcurve.reset();
840            imageXcurve.setActive(false);
841            imageXcurve.setVisible(false);
842            imageYcurve.reset();
843            imageYcurve.setActive(false);
844            imageYcurve.setVisible(false);
845        } catch (Exception e) {
846            logger.error("Problem resetting selector at index " + idx, e);
847        }
848    }
849
850    public void setBlackBackground(boolean value) {
851        blackBackground = value;
852    }
853    
854    public boolean getBlackBackground() {
855        return blackBackground;
856    }
857
858    protected Component getScatterTabComponent() {
859       try {
860         scatterMaster = new XYDisplay("Scatter", RealType.XAxis, RealType.YAxis);
861       } catch (Exception e) {
862         logger.error("Problem creating XYDisplay", e);
863       }
864       return scatterMaster.getComponent();
865    }
866
867    public DisplayMaster makeImageDisplay(MapProjection mapProj, FlatField image, 
868                  FlatField mask_image, Range imageRange, ColorTable colorTable) 
869           throws VisADException, RemoteException {
870      MapProjectionDisplayJ3D mapProjDsp;
871      DisplayMaster dspMaster;
872
873      mapProjDsp = new MapProjectionDisplayJ3D(MapProjectionDisplay.MODE_2Din3D);
874      mapProjDsp.enableRubberBanding(false);
875      dspMaster = mapProjDsp;
876      mapProjDsp.setMapProjection(mapProj);
877
878      RealType imageRangeType =
879        (((FunctionType)image.getType()).getFlatRange().getRealComponents())[0];
880
881      boolean alphaflag = false;
882      HydraRGBDisplayable imageDsp = new HydraRGBDisplayable("image", imageRangeType, null, alphaflag, null);
883
884      imageDsp.setData(image);
885      dspMaster.addDisplayable(imageDsp);
886      addMapDisplayables(mapProjDsp);
887
888      if (mask_image != null) {
889        RGBDisplayable maskDsp = 
890            new ScatterDisplayable("mask", RealType.Generic, maskColorPalette, false);
891        maskDsp.setData(mask_image);
892        maskDsp.setRangeForColor(0, n_selectors-1);
893        dspMaster.addDisplayable(maskDsp);
894      }
895
896      dspMaster.draw();
897
898      ScalarMap colorMap = imageDsp.getColorMap();
899      colorMap.setRange(imageRange.getMin(), imageRange.getMax());
900      BaseColorControl clrCntrl = (BaseColorControl) colorMap.getControl();
901      float[][] ct = colorTable.getColorTable();
902
903      if ( !(alphaflag) && (ct.length == 4) ) {
904         float[][] new_ct = new float[3][];
905         new_ct[0] = ct[0];
906         new_ct[1] = ct[1];
907         new_ct[2] = ct[2];
908         ct = new_ct;
909      }
910
911      clrCntrl.setTable(ct);
912
913      return dspMaster;
914    }
915
916    public Range getImageRange(FlatField image)
917           throws VisADException, RemoteException {
918      DisplayConventions dc = getDisplayConventions();
919      Range[] range = GridUtil.fieldMinMax(image);
920      Range imageRange = range[0];
921      RealType imageRangeType =
922        (((FunctionType)image.getType()).getFlatRange().getRealComponents())[0];
923      String canonicalName = DataAlias.aliasToCanonical(imageRangeType.getName());
924      Range dfltRange = dc.getParamRange(canonicalName, null);
925
926      if (dfltRange == null) {
927        imageRange = range[0];
928      }
929      else if ((imageRange.getMax() - imageRange.getMin()) < (dfltRange.getMax() - dfltRange.getMin())) {
930      }
931      else {
932        imageRange = dfltRange;
933      }
934      return imageRange;
935    }
936
937    public ColorTable getColorTable(FlatField image) 
938           throws VisADException, RemoteException {
939      RealType imageRangeType =
940        (((FunctionType)image.getType()).getFlatRange().getRealComponents())[0];
941      DisplayConventions dc = getDisplayConventions();
942      return dc.getParamColorTable(imageRangeType.getName());
943    }
944
945
946    public MapProjection getDataProjection(FlatField image) 
947           throws VisADException, RemoteException {
948      MapProjection mp = null;
949      //- get MapProjection from incoming image.  If none, use default method
950      FunctionType fnc_type = (FunctionType) image.getType();
951      RealTupleType rtt = fnc_type.getDomain();
952      CoordinateSystem cs = rtt.getCoordinateSystem();
953      Set domainSet = image.getDomainSet();
954
955      if (cs instanceof visad.CachingCoordinateSystem) {
956        cs = ((visad.CachingCoordinateSystem)cs).getCachedCoordinateSystem();
957      }
958
959      if (cs instanceof MapProjection) {
960        return (MapProjection) cs;
961      }
962      else if (cs instanceof LongitudeLatitudeCoordinateSystem) {
963        Rectangle2D rect = MultiSpectralData.getLonLatBoundingBox(image);
964        try {
965          mp = new LambertAEA(rect);
966        } catch (Exception e) {
967          System.out.println(" getDataProjection"+e);
968        }
969        return mp;
970      }
971
972      float minLon = Float.NaN;
973      float minLat = Float.NaN;
974      float delLon = Float.NaN;
975      float delLat = Float.NaN;
976
977      if (domainSet instanceof LinearLatLonSet) {
978         MathType type0 = ((SetType)domainSet.getType()).getDomain().getComponent(0);
979         int latI = RealType.Latitude.equals(type0) ? 0 : 1;
980         int lonI = (latI == 1) ? 0 : 1;
981
982         float[] min = ((LinearLatLonSet)domainSet).getLow();
983         float[] max = ((LinearLatLonSet)domainSet).getHi();
984         minLon = min[lonI];
985         minLat = min[latI];
986         delLon = max[lonI] - min[lonI];
987         delLat = max[latI] - min[latI];
988
989         try {
990            mp = new TrivialMapProjection(RealTupleType.SpatialEarth2DTuple,
991                    new Rectangle2D.Float(minLon, minLat, delLon, delLat));
992         } catch (Exception e) {
993             logException("MultiSpectralControl.getDataProjection", e);
994         }
995
996         return mp;
997      }
998      else if (domainSet instanceof Gridded2DSet) {
999        rtt = ((SetType)domainSet.getType()).getDomain();
1000        rtt = RealTupleType.SpatialEarth2DTuple;
1001        if (!(rtt.equals(RealTupleType.SpatialEarth2DTuple) || rtt.equals(RealTupleType.LatitudeLongitudeTuple))) {
1002          minLon = -180f;
1003          minLat = -90f;
1004          delLon = 360f;
1005          delLat = 180f;
1006        }
1007        else {
1008          int latI = rtt.equals(RealTupleType.SpatialEarth2DTuple) ? 1 : 0;
1009          int lonI = (latI == 1) ? 0 : 1;
1010
1011          float[] min = ((Gridded2DSet)domainSet).getLow();
1012          float[] max = ((Gridded2DSet)domainSet).getHi();
1013          minLon = min[lonI];
1014          minLat = min[latI];
1015          delLon = max[lonI] - min[lonI];
1016          delLat = max[latI] - min[latI];
1017        }
1018      }
1019      
1020      try {
1021         mp = new TrivialMapProjection(RealTupleType.SpatialEarth2DTuple,
1022                 new Rectangle2D.Float(minLon, minLat, delLon, delLat));
1023      } catch (Exception e) {
1024          logException("MultiSpectralControl.getDataProjection", e);
1025      }
1026
1027      return mp;
1028    }
1029
1030    public void addMapDisplayables(MapProjectionDisplayJ3D mapProjDsp) 
1031           throws VisADException, RemoteException {
1032        MapLines mapLines  = new MapLines("maplines");
1033        URL      mapSource =
1034        mapProjDsp.getClass().getResource("/auxdata/maps/OUTLSUPU");
1035        try {
1036            BaseMapAdapter mapAdapter = new BaseMapAdapter(mapSource);
1037            mapLines.setMapLines(mapAdapter.getData());
1038            mapLines.setColor(java.awt.Color.cyan);
1039            mapProjDsp.addDisplayable(mapLines);
1040        } catch (Exception excp) {
1041            System.out.println("Can't open map file " + mapSource);
1042            System.out.println(excp);
1043        }
1044                                                                                                                                                  
1045        mapLines  = new MapLines("maplines");
1046        mapSource =
1047        mapProjDsp.getClass().getResource("/auxdata/maps/OUTLSUPW");
1048        try {
1049            BaseMapAdapter mapAdapter = new BaseMapAdapter(mapSource);
1050            mapLines.setMapLines(mapAdapter.getData());
1051            mapLines.setColor(java.awt.Color.cyan);
1052            mapProjDsp.addDisplayable(mapLines);
1053        } catch (Exception excp) {
1054            System.out.println("Can't open map file " + mapSource);
1055            System.out.println(excp);
1056        }
1057                                                                                                                                                  
1058        mapLines  = new MapLines("maplines");
1059        mapSource =
1060        mapProjDsp.getClass().getResource("/auxdata/maps/OUTLHPOL");
1061        try {
1062            BaseMapAdapter mapAdapter = new BaseMapAdapter(mapSource);
1063            mapLines.setMapLines(mapAdapter.getData());
1064            mapLines.setColor(java.awt.Color.cyan);
1065            mapProjDsp.addDisplayable(mapLines);
1066        } catch (Exception excp) {
1067            System.out.println("Can't open map file " + mapSource);
1068            System.out.println(excp);
1069        }
1070    }
1071
1072    public boolean getSelectByCurve() {
1073      return selectByCurve;
1074    }
1075
1076    private FlatField resample(FlatField X_field, FlatField Y_field) throws VisADException, RemoteException {
1077
1078       RealTupleType X_domainRef = null;
1079       RealTupleType Y_domainRef = null;
1080       float[][] coords = null;
1081       int[] indexes = null;
1082       float[][] Yvalues = Y_field.getFloats(false);
1083       float[][] Xsamples = ((SampledSet)X_field.getDomainSet()).getSamples(false);
1084
1085       CoordinateSystem X_cs = X_field.getDomainCoordinateSystem();
1086       if (X_cs == null) {
1087          RealTupleType X_domain = ((FunctionType)X_field.getType()).getDomain();
1088       }
1089       else {
1090         X_domainRef = X_cs.getReference();
1091       }
1092
1093       CoordinateSystem Y_cs = Y_field.getDomainCoordinateSystem();
1094       if (Y_cs == null) {
1095          RealTupleType Y_domain = ((FunctionType)Y_field.getType()).getDomain();
1096       }
1097       else {
1098         Y_domainRef = Y_cs.getReference();
1099       }
1100
1101       if ( X_domainRef != null && Y_domainRef != null) {
1102         Xsamples = X_cs.toReference(Xsamples);
1103         coords = Y_cs.fromReference(Xsamples);
1104         indexes = ((SampledSet)Y_field.getDomainSet()).valueToIndex(coords);
1105       }
1106       else if ( X_domainRef == null && Y_domainRef != null ) {
1107         Xsamples = Y_cs.fromReference(Xsamples);
1108         indexes = ((SampledSet)Y_field.getDomainSet()).valueToIndex(Xsamples);
1109       }
1110       else if ( X_domainRef != null && Y_domainRef == null) {
1111         Xsamples = X_cs.toReference(Xsamples);
1112         Gridded2DSet domSet = (Gridded2DSet) Y_field.getDomainSet();
1113
1114         // TODO this is a hack for the longitude range problem
1115         float[] hi = domSet.getHi();
1116         if (hi[0] <= 180f) {
1117           for (int t=0; t<Xsamples[0].length; t++) {
1118             if (Xsamples[0][t] > 180f) Xsamples[0][t] -=360;
1119           }
1120         }
1121         
1122         indexes = ((SampledSet)Y_field.getDomainSet()).valueToIndex(Xsamples);
1123       }
1124       else if (X_domainRef == null && Y_domainRef == null) {
1125         Gridded2DSet domSet = (Gridded2DSet) Y_field.getDomainSet();
1126         indexes = domSet.valueToIndex(Xsamples);
1127       }
1128       
1129       float[][] new_values = new float[1][indexes.length];
1130       for (int k=0; k<indexes.length; k++) {
1131          new_values[0][k] = Float.NaN;
1132          if (indexes[k] >= 0) {
1133            new_values[0][k] = Yvalues[0][indexes[k]];
1134          }
1135       }
1136
1137       FunctionType ftype = new FunctionType(((FunctionType)X_field.getType()).getDomain(),
1138                ((FunctionType)Y_field.getType()).getRange());
1139       Y_field = new FlatField(ftype, X_field.getDomainSet());
1140       Y_field.setSamples(new_values);
1141
1142       return Y_field;
1143    }
1144
1145
1146    private class ScatterDisplayable extends RGBDisplayable {
1147       ScatterDisplayable(String name, RealType rgbRealType, float[][] colorPalette, boolean alphaflag) 
1148           throws VisADException, RemoteException {
1149         super(name, rgbRealType, colorPalette, alphaflag);
1150       }
1151    }
1152
1153    private class ImageControl extends DisplayControlImpl {
1154      HydraRGBDisplayable rgbDisp;
1155      DisplayConventions dc;
1156      ColorTableWidget ctw;
1157
1158      ImageControl(HydraRGBDisplayable rgbDisp, DisplayConventions dc) {
1159        super();
1160        this.rgbDisp = rgbDisp;
1161        this.dc = dc;
1162      }
1163
1164      @Override public void setRange(Range r) throws VisADException, RemoteException {
1165          if (r != null) {
1166              rgbDisp.setRangeForColor(r.getMin(), r.getMax());
1167          }
1168      }
1169
1170      @Override public DisplayConventions getDisplayConventions() {
1171        return dc;
1172      }
1173
1174      @Override public void setColorTable(ColorTable ct) {
1175        try {
1176          ctw.setColorTable(ct);
1177          ScalarMap colorMap = rgbDisp.getColorMap();
1178          BaseColorControl clrCntrl = (BaseColorControl) colorMap.getControl();
1179
1180          // Force incoming color dimension to that of the colorMap
1181          //
1182          int numComps = clrCntrl.getNumberOfComponents();
1183          float[][] clrTable = ct.getColorTable();
1184          float[][] newTable = null;
1185          if (numComps != clrTable.length) {
1186            if (numComps < clrTable.length) {
1187              newTable = new float[numComps][clrTable[0].length];
1188              for (int k=0; k<numComps; k++) {
1189                System.arraycopy(clrTable[k], 0, newTable[k], 0, newTable[0].length);
1190              }
1191            }
1192            else if (numComps > clrTable.length) {
1193              newTable = new float[numComps][clrTable[0].length];
1194              for (int k=0; k<clrTable.length; k++) {
1195                System.arraycopy(clrTable[k], 0, newTable[k], 0, newTable[0].length);
1196              }
1197              newTable[3] = new float[clrTable[0].length];
1198            }
1199          } else {
1200              newTable = new float[numComps][clrTable[0].length];
1201              for (int k = 0; k < clrTable.length; k++) {
1202                System.arraycopy(clrTable[k], 0, newTable[k], 0, newTable[0].length);
1203              }
1204          } 
1205          clrCntrl.setTable(newTable);
1206        } 
1207        catch (Exception e) {
1208          LogUtil.logException("Problem changing color table", e);
1209        }
1210      }
1211    }
1212
1213    private class ImageCurveSelector extends CellImpl implements DisplayListener {
1214      boolean init = false;
1215      CurveDrawer curveDraw;
1216      DisplayMaster dspMaster;
1217      Gridded2DSet domainSet;
1218      CoordinateSystem cs;
1219      int domainLen_0;
1220      int domainLen_1;
1221      ImageCurveSelector other;
1222      UnionSet last_uSet = null;
1223      boolean imageLatLon = false;
1224      boolean active = true;
1225      float maskVal;
1226      LineDrawing lastCurve;
1227      StatsTable myTable = null;
1228      int myTableIndex = 0;
1229
1230      ImageCurveSelector(CurveDrawer curveDraw, FlatField image, DisplayMaster master, Color color, float maskVal, StatsTable mst) 
1231           throws VisADException, RemoteException {
1232        this.curveDraw = curveDraw;
1233        this.maskVal = maskVal;
1234        this.myTable = mst;
1235        myTableIndex = 0;
1236        if (color == Color.magenta) myTableIndex = 1;
1237        if (color == Color.green) myTableIndex = 2;
1238        if (color == Color.blue) myTableIndex = 3;
1239        dspMaster = master;
1240        dspMaster.addDisplayListener(this);
1241        domainSet = (Gridded2DSet) image.getDomainSet();
1242        int[] lens = domainSet.getLengths();
1243        domainLen_0 = lens[0];
1244        domainLen_1 = lens[1];
1245        cs = ((FunctionType)image.getType()).getDomain().getCoordinateSystem();
1246        RealTupleType reference = null;
1247        if (cs != null) {
1248          reference = cs.getReference();
1249        }
1250        else {
1251          reference = ((SetType)domainSet.getType()).getDomain();
1252        }
1253        RealType[] rtypes = reference.getRealComponents();
1254        if (rtypes[0].equals(RealType.Latitude)) imageLatLon = true;
1255        lastCurve = new LineDrawing("lastCurve");
1256        lastCurve.setColor(color);
1257        lastCurve.setLineWidth(2);
1258        master.addDisplayable(lastCurve);
1259      }
1260
1261      @Override public void displayChanged(DisplayEvent de)
1262             throws VisADException, RemoteException {
1263         if ((de.getId() == DisplayEvent.MOUSE_RELEASED) && (active)) {
1264           UnionSet uSet = curveDraw.getCurves();
1265           if (uSet == last_uSet) return;
1266           SampledSet[] sets = uSet.getSets();
1267           int s_idx = sets.length-1;
1268           float[][] crv;
1269
1270           if (cs != null) {
1271             crv = sets[s_idx].getSamples();
1272             if (imageLatLon) {
1273                float[] tmp = crv[0];
1274                crv[0] = crv[1];
1275                crv[1] = tmp;
1276             }
1277             crv = cs.fromReference(crv);
1278             crv = domainSet.valueToGrid(crv);
1279           }
1280           else {
1281             crv = sets[s_idx].getSamples();
1282             crv = domainSet.valueToGrid(crv);
1283           }
1284
1285           float[][] onImage = new float[2][crv[0].length];
1286           int cnt = 0;
1287           for (int i=0; i<crv[0].length; i++) {
1288             if ( ((crv[0][i] >= 0)&&(crv[0][i] <= domainLen_0)) &&
1289                  ((crv[1][i] >= 0)&&(crv[1][i] <= domainLen_1)) ) {
1290               onImage[0][cnt] = crv[0][i];
1291               onImage[1][cnt] = crv[1][i];
1292               cnt++;
1293             }
1294           }
1295           uSet = new UnionSet(new SampledSet[] {sets[s_idx]});
1296           last_uSet = uSet;
1297           lastCurve.setData(last_uSet);
1298           curveDraw.setCurves(uSet);
1299           other.updateCurve(sets[s_idx]);
1300
1301           if (cnt == 0) {
1302             return;
1303           }
1304
1305           float[][] tmp = new float[2][cnt];
1306           System.arraycopy(onImage[0], 0, tmp[0], 0, cnt);
1307           System.arraycopy(onImage[1], 0, tmp[1], 0, cnt);
1308           onImage = tmp;
1309
1310           float[] minmaxvals = minmax(onImage[0]);
1311           int low_0 = Math.round(minmaxvals[0]);
1312           int hi_0 = Math.round(minmaxvals[1]);
1313           minmaxvals = minmax(onImage[1]);
1314           int low_1 = Math.round(minmaxvals[0]);
1315           int hi_1 = Math.round(minmaxvals[1]);
1316
1317           int len_0 = (hi_0 - low_0) + 1;
1318           int len_1 = (hi_1 - low_1) + 1;
1319           int len = len_0*len_1;
1320
1321           tmp = new float[3][len];
1322           int[] tmpsel = new int[len];
1323
1324           int num_inside = 0;
1325           for (int j=0; j<len_1; j++) {
1326             for (int i=0; i<len_0; i++) {
1327               int idx = (j+low_1)*domainLen_0 + (i+low_0);
1328               float x = (float) (i + low_0);
1329               float y = (float) (j + low_1);
1330               if (DelaunayCustom.inside(crv, x, y)) {
1331                 tmp[0][num_inside] = scatterFieldRange[0][idx];
1332                 tmp[1][num_inside] = scatterFieldRange[1][idx];
1333                 tmp[2][num_inside] = maskVal;
1334                 tmpsel[num_inside] = idx;
1335                 num_inside++;
1336               }
1337             }
1338           }
1339           len = num_inside;
1340           float[][] markScatter = new float[3][len];
1341           System.arraycopy(tmp[0], 0, markScatter[0], 0, len);
1342           System.arraycopy(tmp[1], 0, markScatter[1], 0, len);
1343           System.arraycopy(tmp[2], 0, markScatter[2], 0, len);
1344
1345
1346           int last_len = 0;
1347           float[][] lastMark = ((FlatField)scatterMarkDsp.getData()).getFloats(false);
1348           tmp = new float[3][lastMark[0].length];
1349           for (int k=0; k<lastMark[0].length; k++) {
1350             if (lastMark[2][k] != maskVal) {
1351               tmp[0][last_len] = lastMark[0][k];
1352               tmp[1][last_len] = lastMark[1][k];
1353               tmp[2][last_len] = lastMark[2][k];
1354               last_len++;
1355             }
1356           }
1357
1358           float[][] newMarkScatter = new float[3][len+last_len];
1359           System.arraycopy(tmp[0], 0, newMarkScatter[0], 0, last_len);
1360           System.arraycopy(tmp[1], 0, newMarkScatter[1], 0, last_len);
1361           System.arraycopy(tmp[2], 0, newMarkScatter[2], 0, last_len);
1362           System.arraycopy(markScatter[0], 0, newMarkScatter[0], last_len, len);
1363           System.arraycopy(markScatter[1], 0, newMarkScatter[1], last_len, len);
1364           System.arraycopy(markScatter[2], 0, newMarkScatter[2], last_len, len);
1365
1366           Integer1DSet dset = new Integer1DSet(len+last_len);
1367           FlatField scatterFieldMark = new FlatField(
1368             new FunctionType(RealType.Generic,
1369                new RealTupleType(RealType.XAxis, RealType.YAxis, RealType.getRealType("mask"))), dset);
1370
1371           scatterFieldMark.setSamples(newMarkScatter, false);
1372           scatterMarkDsp.setData(scatterFieldMark);
1373
1374           if (myTable != null) {
1375             int[] selected = new int[len];
1376             System.arraycopy(tmpsel, 0, selected, 0, len);
1377             total_area = JPythonMethods.computeSum(Area_field, selected);
1378             myTable.setPoints(markScatter, len, myTableIndex, total_area);  
1379           }
1380
1381         }
1382      }
1383
1384      public void setActive(boolean active) {
1385        this.active = active;
1386      }
1387
1388      public void reset() throws VisADException, RemoteException {
1389
1390        float[][] lastMark = ((FlatField)scatterMarkDsp.getData()).getFloats(false);
1391        float[][] tmp = new float[3][lastMark[0].length];
1392        int cnt = 0;
1393        for (int k=0; k<lastMark[0].length; k++) {
1394          if (lastMark[2][k] != maskVal) {
1395             tmp[0][cnt] = lastMark[0][k];
1396             tmp[1][cnt] = lastMark[1][k];
1397             tmp[2][cnt] = lastMark[2][k];
1398             cnt++;
1399          }
1400        }
1401
1402        RealTupleType type = ((SetType)curveDraw.getCurves().getType()).getDomain();
1403        curveDraw.setCurves(new UnionSet(new Gridded2DSet[]{
1404            new Gridded2DSet(type, new float[][] {
1405            { 0.0f }, { 0.0f }}, 1) }));
1406        
1407        lastCurve.setData(new UnionSet(new Gridded2DSet[]{
1408            new Gridded2DSet(type, new float[][] {
1409            { 0.0f }, { 0.0f }}, 1) }));
1410
1411        FlatField scatterFieldMark = null;
1412        if (cnt == 0) {
1413        Integer1DSet dset = new Integer1DSet(2);
1414        scatterFieldMark = new FlatField(
1415        new FunctionType(RealType.Generic,
1416              new RealTupleType(RealType.XAxis, RealType.YAxis, RealType.getRealType("mask"))), dset);
1417        float[][] markScatter = new float[3][2]; 
1418        for (int k=0; k<2; k++) {
1419          markScatter[0][k] = scatterFieldRange[0][k];
1420          markScatter[1][k] = scatterFieldRange[1][k];
1421          markScatter[2][k] = 0;
1422        }
1423        scatterFieldMark.setSamples(markScatter, false);
1424        }
1425        else {
1426          Integer1DSet dset = new Integer1DSet(cnt);
1427          scatterFieldMark = new FlatField(
1428          new FunctionType(RealType.Generic,
1429                new RealTupleType(RealType.XAxis, RealType.YAxis, RealType.getRealType("mask"))), dset);
1430          float[][] markScatter = new float[3][cnt];
1431          for (int k=0; k<cnt; k++) {
1432            markScatter[0][k] = tmp[0][k];
1433            markScatter[1][k] = tmp[1][k];
1434            markScatter[2][k] = tmp[2][k];
1435          }
1436          scatterFieldMark.setSamples(markScatter, false);
1437        }
1438
1439        scatterMarkDsp.setData(scatterFieldMark);
1440      }
1441
1442      public void updateCurve(SampledSet set) throws VisADException, RemoteException {
1443        last_uSet = new UnionSet(new SampledSet[] {set});
1444        curveDraw.setCurves(last_uSet);
1445        lastCurve.setData(last_uSet);
1446      }
1447
1448      public void setOther(ImageCurveSelector other) {
1449        this.other = other;
1450      }
1451
1452      @Override public void doAction()
1453           throws VisADException, RemoteException {
1454        if (!init) {
1455          init = true;
1456          return;
1457        }
1458      }
1459
1460      public void setVisible(boolean visible) throws VisADException, RemoteException {
1461        curveDraw.setVisible(visible);
1462      }
1463
1464    }
1465
1466    private class ImageBoxSelector extends CellImpl {
1467        boolean init = false;
1468        boolean active = true;
1469        SubsetRubberBandBox subsetBox;
1470        Set imageDomain;
1471        int domainLen_0;
1472        LineDrawing lastBox;
1473        ImageBoxSelector other;
1474        float maskVal;
1475        boolean earthCoordDomain = false;
1476        StatsTable myTable = null;
1477        int myTableIndex = 0;
1478
1479        ImageBoxSelector(SubsetRubberBandBox subsetBox, Set imageDomain, DisplayMaster master, Color color, float maskVal, StatsTable mst)
1480            throws VisADException, RemoteException {
1481            super();
1482            this.myTable = mst;
1483            myTableIndex = 0;
1484            if (color == Color.magenta) myTableIndex = 1;
1485            if (color == Color.green) myTableIndex = 2;
1486            if (color == Color.blue) myTableIndex = 3;
1487            this.subsetBox = subsetBox;
1488            this.imageDomain = imageDomain;
1489            int[] lens = ((Gridded2DSet)imageDomain).getLengths();
1490            this.maskVal = maskVal;
1491            domainLen_0 = lens[0];
1492            lastBox = new LineDrawing("last_box");
1493            lastBox.setColor(color);
1494            master.addDisplayable(lastBox);
1495            subsetBox.addAction(this);
1496            master.addDisplayable(subsetBox);
1497            RealTupleType rtt = ((SetType)imageDomain.getType()).getDomain();
1498            if (rtt.equals(RealTupleType.SpatialEarth2DTuple) ||
1499                rtt.equals(RealTupleType.LatitudeLongitudeTuple)) {
1500                earthCoordDomain = true;
1501            }
1502        }
1503
1504        @Override public void doAction()
1505            throws VisADException, RemoteException
1506        {
1507            if (!init) {
1508                init = true;
1509                return;
1510            }
1511
1512            if (!active) {
1513                return;
1514            }
1515
1516            Gridded2DSet set = subsetBox.getBounds();
1517            float[][] corners = set.getSamples(false);
1518            float[][] coords = corners;
1519            if (corners == null) return;
1520
1521            if ((imageDomain instanceof Linear2DSet) || !earthCoordDomain) {
1522                coords = ((Gridded2DSet)imageDomain).valueToGrid(corners);
1523            }
1524
1525            float[] coords_0 = coords[0];
1526            float[] coords_1 = coords[1];
1527
1528            int low_0 = Math.round(Math.min(coords_0[0], coords_0[1]));
1529            int low_1 = Math.round(Math.min(coords_1[0], coords_1[1]));
1530            int hi_0  = Math.round(Math.max(coords_0[0], coords_0[1]));
1531            int hi_1  = Math.round(Math.max(coords_1[0], coords_1[1]));
1532
1533            int len_0 = (hi_0 - low_0) + 1;
1534            int len_1 = (hi_1 - low_1) + 1;
1535            int len = len_0*len_1;
1536
1537            float[][] markScatter = new float[3][len];
1538            int[] selected = new int[len];
1539
1540            for (int j=0; j<len_1; j++) {
1541                for (int i=0; i<len_0; i++) {
1542                    int idx = (j+low_1)*domainLen_0 + (i+low_0);
1543                    int k = j*len_0 + i;
1544                    markScatter[0][k] = scatterFieldRange[0][idx];
1545                    markScatter[1][k] = scatterFieldRange[1][idx];
1546                    markScatter[2][k] = maskVal;
1547                    selected[k] = idx;
1548                }
1549            }
1550
1551            int last_len = 0;
1552            float[][] lastMark = ((FlatField)scatterMarkDsp.getData()).getFloats(false);
1553            float[][] tmp = new float[3][lastMark[0].length];
1554            for (int k=0; k<lastMark[0].length; k++) {
1555                if (lastMark[2][k] != maskVal) {
1556                    tmp[0][last_len] = lastMark[0][k];
1557                    tmp[1][last_len] = lastMark[1][k];
1558                    tmp[2][last_len] = lastMark[2][k];
1559                    last_len++;
1560                }
1561            }
1562
1563            float[][] newMarkScatter = new float[3][len+last_len];
1564            System.arraycopy(tmp[0], 0, newMarkScatter[0], 0, last_len);
1565            System.arraycopy(tmp[1], 0, newMarkScatter[1], 0, last_len);
1566            System.arraycopy(tmp[2], 0, newMarkScatter[2], 0, last_len);
1567            System.arraycopy(markScatter[0], 0, newMarkScatter[0], last_len, len);
1568            System.arraycopy(markScatter[1], 0, newMarkScatter[1], last_len, len);
1569            System.arraycopy(markScatter[2], 0, newMarkScatter[2], last_len, len);
1570
1571            Integer1DSet dset = new Integer1DSet(len+last_len);
1572            FlatField scatterFieldMark = new FlatField(
1573                new FunctionType(RealType.Generic,
1574                    new RealTupleType(RealType.XAxis, RealType.YAxis, RealType.getRealType("mask"))), dset);
1575
1576            scatterFieldMark.setSamples(newMarkScatter, false);
1577            scatterMarkDsp.setData(scatterFieldMark);
1578
1579            if (myTable != null) {
1580                total_area = JPythonMethods.computeSum(Area_field, selected);
1581                myTable.setPoints(markScatter, len, myTableIndex, total_area);
1582            }
1583
1584            updateBox();
1585        }
1586
1587        public void setActive(boolean active) {
1588            this.active = active;
1589        }
1590
1591        public void setVisible(boolean visible) throws VisADException, RemoteException {
1592            subsetBox.setVisible(visible);
1593            if (visible) {
1594                lastBox.setVisible(visible);
1595            }
1596        }
1597
1598        public void reset() throws VisADException, RemoteException {
1599            Gridded2DSet set2D =
1600                new Gridded2DSet(RealTupleType.SpatialCartesian2DTuple,
1601                    new float[][] {{0},{0}}, 1);
1602            lastBox.setVisible(false);
1603            lastBox.setData(set2D);
1604
1605            float[][] lastMark = ((FlatField)scatterMarkDsp.getData()).getFloats(false);
1606            float[][] tmp = new float[3][lastMark[0].length];
1607            int cnt = 0;
1608            for (int k=0; k<lastMark[0].length; k++) {
1609                if (lastMark[2][k] != maskVal) {
1610                    tmp[0][cnt] = lastMark[0][k];
1611                    tmp[1][cnt] = lastMark[1][k];
1612                    tmp[2][cnt] = lastMark[2][k];
1613                    cnt++;
1614                }
1615            }
1616
1617            FlatField scatterFieldMark;
1618            if (cnt == 2) {
1619                Integer1DSet dset = new Integer1DSet(2);
1620                scatterFieldMark = new FlatField(
1621                    new FunctionType(RealType.Generic,
1622                        new RealTupleType(RealType.XAxis, RealType.YAxis, RealType.getRealType("mask"))), dset);
1623                float[][] markScatter = new float[3][2];
1624                for (int k=0; k<2; k++) {
1625                    markScatter[0][k] = scatterFieldRange[0][k];
1626                    markScatter[1][k] = scatterFieldRange[1][k];
1627                    markScatter[2][k] = 0;
1628                }
1629                scatterFieldMark.setSamples(markScatter, false);
1630            }
1631            else {
1632                Integer1DSet dset = new Integer1DSet(cnt);
1633                scatterFieldMark = new FlatField(
1634                    new FunctionType(RealType.Generic,
1635                        new RealTupleType(RealType.XAxis, RealType.YAxis, RealType.getRealType("mask"))), dset);
1636                float[][] markScatter = new float[3][cnt];
1637                for (int k=0; k<cnt; k++) {
1638                    markScatter[0][k] = tmp[0][k];
1639                    markScatter[1][k] = tmp[1][k];
1640                    markScatter[2][k] = tmp[2][k];
1641                }
1642                scatterFieldMark.setSamples(markScatter, false);
1643            }
1644
1645            scatterMarkDsp.setData(scatterFieldMark);
1646        }
1647
1648        public void setOther(ImageBoxSelector other) {
1649            this.other = other;
1650        }
1651
1652        public void updateBox() throws VisADException, RemoteException {
1653            Gridded3DSet set3D = subsetBox.getLastBox();
1654            float[][] samples = set3D.getSamples(false);
1655            Gridded2DSet set2D =
1656                new Gridded2DSet(RealTupleType.SpatialCartesian2DTuple,
1657                    new float[][] {samples[0], samples[1]}, samples[0].length);
1658            lastBox.setData(set2D);
1659            other.updateBox(set2D);
1660        }
1661
1662        public void updateBox(Gridded2DSet set2D) throws VisADException, RemoteException {
1663            lastBox.setData(set2D);
1664        }
1665
1666    }
1667
1668    private class ScatterBoxSelector extends CellImpl {
1669       boolean init = false;
1670       double[] x_coords = new double[2];
1671       double[] y_coords = new double[2];
1672       RubberBandBox rbb;
1673       LineDrawing selectBox;
1674       boolean active = true;
1675       float maskVal = 0;
1676
1677       ScatterBoxSelector(DisplayMaster master, Color color, float maskVal) throws VisADException, RemoteException {
1678         selectBox = new LineDrawing("select");
1679         selectBox.setColor(color);
1680
1681         rbb = new RubberBandBox(RealType.XAxis, RealType.YAxis, 1);
1682         rbb.setColor(color);
1683         rbb.addAction(this);
1684
1685         master.addDisplayable(rbb);
1686         master.addDisplayable(selectBox);
1687         this.maskVal = maskVal;
1688       }
1689
1690
1691       @Override public void doAction() throws VisADException, RemoteException {
1692         if (!init) {
1693           init = true;
1694           return;
1695         }
1696
1697         if (!active) {
1698           return;
1699         }
1700
1701         Gridded2DSet set = rbb.getBounds();
1702         float[] low = set.getLow();
1703         float[] hi = set.getHi();
1704         x_coords[0] = low[0];
1705         x_coords[1] = hi[0];
1706         y_coords[0] = low[1];
1707         y_coords[1] = hi[1];
1708                                                                                                                                                  
1709         SampledSet[] sets = new SampledSet[4];
1710         sets[0] = new Gridded2DSet(RealTupleType.SpatialCartesian2DTuple, new float[][] {{low[0], hi[0]}, {low[1], low[1]}}, 2);
1711         sets[1] = new Gridded2DSet(RealTupleType.SpatialCartesian2DTuple, new float[][] {{hi[0], hi[0]}, {low[1], hi[1]}}, 2);
1712         sets[2] = new Gridded2DSet(RealTupleType.SpatialCartesian2DTuple, new float[][] {{hi[0], low[0]}, {hi[1], hi[1]}}, 2);
1713         sets[3] = new Gridded2DSet(RealTupleType.SpatialCartesian2DTuple, new float[][] {{low[0], low[0]}, {hi[1], low[1]}}, 2);
1714         UnionSet uset = new UnionSet(sets);
1715         selectBox.setData(uset);
1716
1717         try {
1718           histoField.markMaskFieldByRange(x_coords, y_coords, maskVal);
1719         } catch (Exception e) {
1720           logger.error("Problem changing histogram", e);
1721         }
1722       }
1723
1724       public void setVisible(boolean visible) throws VisADException, RemoteException {
1725         rbb.setVisible(visible);
1726         if (visible) {
1727           selectBox.setVisible(visible);
1728         }
1729       }
1730
1731       public void setActive(boolean active) {
1732         this.active = active;
1733       }
1734
1735       public void reset() throws Exception {
1736         if (!active) return;
1737         selectBox.setVisible(false);
1738         selectBox.setData(new Gridded2DSet(RealTupleType.SpatialCartesian2DTuple, new float[][] {{0f, 0f}, {0f, 0f}}, 2));
1739         histoField.resetMaskField(maskVal);
1740       }
1741   }
1742
1743   private class ScatterCurveSelector extends CellImpl implements DisplayListener {
1744     CurveDrawer curveDraw;
1745     boolean init = false;
1746     UnionSet last_uSet = null;
1747     boolean active = true;
1748     float maskVal = 0;
1749     LineDrawing selectCurve;
1750
1751     ScatterCurveSelector(DisplayMaster master, Color color, float maskVal) throws VisADException, RemoteException {
1752       curveDraw = new CurveDrawer(RealType.XAxis, RealType.YAxis, 1);
1753       curveDraw.setColor(color);
1754       curveDraw.setLineWidth(2);
1755       curveDraw.setData(new UnionSet(new Gridded2DSet[]{
1756            new Gridded2DSet(RealTupleType.SpatialCartesian2DTuple, new float[][] {
1757            { scatterFieldRange[0][0] }, { scatterFieldRange[1][0]}
1758        }, 1) }));
1759
1760       selectCurve = new LineDrawing("select");
1761       selectCurve.setColor(color);
1762       selectCurve.setLineWidth(2);
1763       master.addDisplayable(curveDraw);
1764       master.addDisplayable(selectCurve);
1765       this.maskVal = maskVal;
1766
1767       curveDraw.addAction(this);
1768       master.addDisplayListener(this);
1769     }
1770
1771     @Override public void displayChanged(DisplayEvent de)
1772            throws VisADException, RemoteException {
1773       if ((de.getId() == DisplayEvent.MOUSE_RELEASED) && (active)) {
1774         UnionSet uSet = curveDraw.getCurves();
1775         if (uSet == last_uSet) return;
1776         SampledSet[] sets = uSet.getSets();
1777         int s_idx = sets.length-1;
1778         float[][] crv;
1779                                                                                                                                                  
1780         crv = sets[s_idx].getSamples();
1781         last_uSet = new UnionSet(new SampledSet[] {sets[s_idx]});
1782         curveDraw.setCurves(last_uSet);
1783         selectCurve.setData(last_uSet);
1784
1785         try {
1786           histoField.clearMaskField(maskVal);
1787           histoField.markMaskFieldByCurve(crv, maskVal);
1788         } catch (Exception e) {
1789           logger.error("Problem handling displayChange", e);
1790         }
1791       }
1792     }
1793
1794     @Override public  void doAction() throws VisADException, RemoteException {
1795       if (!init) {
1796         init = true;
1797         return;
1798       }
1799     }
1800
1801     public void setVisible(boolean visible) throws VisADException, RemoteException {
1802       curveDraw.setVisible(visible);
1803     }
1804
1805     public void setActive(boolean active) {
1806       this.active = active;
1807     }
1808
1809     public void reset() throws Exception {
1810       if (!active) return;
1811       curveDraw.setData(new UnionSet(new Gridded2DSet[]{
1812            new Gridded2DSet(RealTupleType.SpatialCartesian2DTuple, new float[][] {
1813            { scatterFieldRange[0][0] }, { scatterFieldRange[1][0]}
1814        }, 1) }));
1815       selectCurve.setData(new UnionSet(new Gridded2DSet[]{
1816            new Gridded2DSet(RealTupleType.SpatialCartesian2DTuple, new float[][] {
1817            { scatterFieldRange[0][0] }, { scatterFieldRange[1][0]}
1818        }, 1) }));
1819       histoField.resetMaskField(maskVal);
1820     }
1821   }
1822
1823    private class BoxCurveSwitch implements ActionListener {
1824
1825        public BoxCurveSwitch() {
1826        }
1827
1828        @Override public void actionPerformed(ActionEvent ae) {
1829            String cmd = ae.getActionCommand();
1830            try {
1831                if (cmd.equals("Box")) {
1832                    selectByCurve = false;
1833                } else if (cmd.equals("Curve")) {
1834                    selectByCurve = true;
1835                }
1836            } catch (Exception e) {
1837                logger.error("Problem switching curve type", e);
1838            }
1839        }
1840    }
1841
1842    public static float[] minmax(float[] values) {
1843        float min =  Float.MAX_VALUE;
1844        float max = -Float.MAX_VALUE;
1845        for (int k = 0; k < values.length; k++) {
1846            float val = values[k];
1847            if ((val == val) && (val < Float.POSITIVE_INFINITY) && (val > Float.NEGATIVE_INFINITY)) {
1848                if (val < min) min = val;
1849                if (val > max) max = val;
1850            }
1851        }
1852        return new float[] {min, max};
1853    }
1854
1855    public boolean getIsLatLon(FlatField field) throws VisADException, RemoteException {
1856        boolean isLL = false;
1857        FunctionType fnc_type = (FunctionType) field.getType();
1858        RealTupleType rtt = fnc_type.getDomain();
1859        if (rtt.equals(RealTupleType.LatitudeLongitudeTuple)) {
1860            isLL = true;
1861        } else if (!rtt.equals(RealTupleType.SpatialEarth2DTuple)) {
1862            rtt = fnc_type.getDomain().getCoordinateSystem().getReference();
1863            if ( rtt.equals(RealTupleType.LatitudeLongitudeTuple)) {
1864                isLL = true;
1865            }
1866        }
1867        return isLL;
1868    }
1869}