001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2024
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        buttonPanel.add(computeStatsButton);
759        buttonPanel.add(new JLabel("Background Color:"));
760        buttonPanel.add(bgColorBlack);
761        buttonPanel.add(bgColorWhite);
762
763        //-container = pane;
764        JPanel new_pane = new JPanel(new BorderLayout());
765        new_pane.add(pane, BorderLayout.CENTER);
766        new_pane.add(buttonPanel, BorderLayout.SOUTH);
767        container = new_pane;
768        return container;
769    }
770
771    public void setBlackBackground(boolean value) {
772        blackBackground = value;
773    }
774    
775    public boolean getBlackBackground() {
776        return blackBackground;
777    }
778
779    protected Component getScatterTabComponent() {
780       try {
781         scatterMaster = new XYDisplay("Scatter", RealType.XAxis, RealType.YAxis);
782       } catch (Exception e) {
783         logger.error("Problem creating XYDisplay", e);
784       }
785       return scatterMaster.getComponent();
786    }
787
788    public DisplayMaster makeImageDisplay(MapProjection mapProj, FlatField image, 
789                  FlatField mask_image, Range imageRange, ColorTable colorTable) 
790           throws VisADException, RemoteException {
791      MapProjectionDisplayJ3D mapProjDsp;
792      DisplayMaster dspMaster;
793
794      mapProjDsp = new MapProjectionDisplayJ3D(MapProjectionDisplay.MODE_2Din3D);
795      mapProjDsp.enableRubberBanding(false);
796      dspMaster = mapProjDsp;
797      mapProjDsp.setMapProjection(mapProj);
798
799      RealType imageRangeType =
800        (((FunctionType)image.getType()).getFlatRange().getRealComponents())[0];
801
802      boolean alphaflag = false;
803      HydraRGBDisplayable imageDsp = new HydraRGBDisplayable("image", imageRangeType, null, alphaflag, null);
804
805      imageDsp.setData(image);
806      dspMaster.addDisplayable(imageDsp);
807      addMapDisplayables(mapProjDsp);
808
809      if (mask_image != null) {
810        RGBDisplayable maskDsp = 
811            new ScatterDisplayable("mask", RealType.Generic, maskColorPalette, false);
812        maskDsp.setData(mask_image);
813        maskDsp.setRangeForColor(0, n_selectors-1);
814        dspMaster.addDisplayable(maskDsp);
815      }
816
817      dspMaster.draw();
818
819      ScalarMap colorMap = imageDsp.getColorMap();
820      colorMap.setRange(imageRange.getMin(), imageRange.getMax());
821      BaseColorControl clrCntrl = (BaseColorControl) colorMap.getControl();
822      float[][] ct = colorTable.getColorTable();
823
824      if ( !(alphaflag) && (ct.length == 4) ) {
825         float[][] new_ct = new float[3][];
826         new_ct[0] = ct[0];
827         new_ct[1] = ct[1];
828         new_ct[2] = ct[2];
829         ct = new_ct;
830      }
831
832      clrCntrl.setTable(ct);
833
834      return dspMaster;
835    }
836
837    public Range getImageRange(FlatField image)
838           throws VisADException, RemoteException {
839      DisplayConventions dc = getDisplayConventions();
840      Range[] range = GridUtil.fieldMinMax(image);
841      Range imageRange = range[0];
842      RealType imageRangeType =
843        (((FunctionType)image.getType()).getFlatRange().getRealComponents())[0];
844      String canonicalName = DataAlias.aliasToCanonical(imageRangeType.getName());
845      Range dfltRange = dc.getParamRange(canonicalName, null);
846
847      if (dfltRange == null) {
848        imageRange = range[0];
849      }
850      else if ((imageRange.getMax() - imageRange.getMin()) < (dfltRange.getMax() - dfltRange.getMin())) {
851      }
852      else {
853        imageRange = dfltRange;
854      }
855      return imageRange;
856    }
857
858    public ColorTable getColorTable(FlatField image) 
859           throws VisADException, RemoteException {
860      RealType imageRangeType =
861        (((FunctionType)image.getType()).getFlatRange().getRealComponents())[0];
862      DisplayConventions dc = getDisplayConventions();
863      return dc.getParamColorTable(imageRangeType.getName());
864    }
865
866
867    public MapProjection getDataProjection(FlatField image) 
868           throws VisADException, RemoteException {
869      MapProjection mp = null;
870      //- get MapProjection from incoming image.  If none, use default method
871      FunctionType fnc_type = (FunctionType) image.getType();
872      RealTupleType rtt = fnc_type.getDomain();
873      CoordinateSystem cs = rtt.getCoordinateSystem();
874      Set domainSet = image.getDomainSet();
875
876      if (cs instanceof visad.CachingCoordinateSystem) {
877        cs = ((visad.CachingCoordinateSystem)cs).getCachedCoordinateSystem();
878      }
879
880      if (cs instanceof MapProjection) {
881        return (MapProjection) cs;
882      }
883      else if (cs instanceof LongitudeLatitudeCoordinateSystem) {
884        Rectangle2D rect = MultiSpectralData.getLonLatBoundingBox(image);
885        try {
886          mp = new LambertAEA(rect);
887        } catch (Exception e) {
888          System.out.println(" getDataProjection"+e);
889        }
890        return mp;
891      }
892
893      float minLon = Float.NaN;
894      float minLat = Float.NaN;
895      float delLon = Float.NaN;
896      float delLat = Float.NaN;
897
898      if (domainSet instanceof LinearLatLonSet) {
899         MathType type0 = ((SetType)domainSet.getType()).getDomain().getComponent(0);
900         int latI = RealType.Latitude.equals(type0) ? 0 : 1;
901         int lonI = (latI == 1) ? 0 : 1;
902
903         float[] min = ((LinearLatLonSet)domainSet).getLow();
904         float[] max = ((LinearLatLonSet)domainSet).getHi();
905         minLon = min[lonI];
906         minLat = min[latI];
907         delLon = max[lonI] - min[lonI];
908         delLat = max[latI] - min[latI];
909
910         try {
911            mp = new TrivialMapProjection(RealTupleType.SpatialEarth2DTuple,
912                    new Rectangle2D.Float(minLon, minLat, delLon, delLat));
913         } catch (Exception e) {
914             logException("MultiSpectralControl.getDataProjection", e);
915         }
916
917         return mp;
918      }
919      else if (domainSet instanceof Gridded2DSet) {
920        rtt = ((SetType)domainSet.getType()).getDomain();
921        rtt = RealTupleType.SpatialEarth2DTuple;
922        if (!(rtt.equals(RealTupleType.SpatialEarth2DTuple) || rtt.equals(RealTupleType.LatitudeLongitudeTuple))) {
923          minLon = -180f;
924          minLat = -90f;
925          delLon = 360f;
926          delLat = 180f;
927        }
928        else {
929          int latI = rtt.equals(RealTupleType.SpatialEarth2DTuple) ? 1 : 0;
930          int lonI = (latI == 1) ? 0 : 1;
931
932          float[] min = ((Gridded2DSet)domainSet).getLow();
933          float[] max = ((Gridded2DSet)domainSet).getHi();
934          minLon = min[lonI];
935          minLat = min[latI];
936          delLon = max[lonI] - min[lonI];
937          delLat = max[latI] - min[latI];
938        }
939      }
940      
941      try {
942         mp = new TrivialMapProjection(RealTupleType.SpatialEarth2DTuple,
943                 new Rectangle2D.Float(minLon, minLat, delLon, delLat));
944      } catch (Exception e) {
945          logException("MultiSpectralControl.getDataProjection", e);
946      }
947
948      return mp;
949    }
950
951    public void addMapDisplayables(MapProjectionDisplayJ3D mapProjDsp) 
952           throws VisADException, RemoteException {
953        MapLines mapLines  = new MapLines("maplines");
954        URL      mapSource =
955        mapProjDsp.getClass().getResource("/auxdata/maps/OUTLSUPU");
956        try {
957            BaseMapAdapter mapAdapter = new BaseMapAdapter(mapSource);
958            mapLines.setMapLines(mapAdapter.getData());
959            mapLines.setColor(java.awt.Color.cyan);
960            mapProjDsp.addDisplayable(mapLines);
961        } catch (Exception excp) {
962            System.out.println("Can't open map file " + mapSource);
963            System.out.println(excp);
964        }
965                                                                                                                                                  
966        mapLines  = new MapLines("maplines");
967        mapSource =
968        mapProjDsp.getClass().getResource("/auxdata/maps/OUTLSUPW");
969        try {
970            BaseMapAdapter mapAdapter = new BaseMapAdapter(mapSource);
971            mapLines.setMapLines(mapAdapter.getData());
972            mapLines.setColor(java.awt.Color.cyan);
973            mapProjDsp.addDisplayable(mapLines);
974        } catch (Exception excp) {
975            System.out.println("Can't open map file " + mapSource);
976            System.out.println(excp);
977        }
978                                                                                                                                                  
979        mapLines  = new MapLines("maplines");
980        mapSource =
981        mapProjDsp.getClass().getResource("/auxdata/maps/OUTLHPOL");
982        try {
983            BaseMapAdapter mapAdapter = new BaseMapAdapter(mapSource);
984            mapLines.setMapLines(mapAdapter.getData());
985            mapLines.setColor(java.awt.Color.cyan);
986            mapProjDsp.addDisplayable(mapLines);
987        } catch (Exception excp) {
988            System.out.println("Can't open map file " + mapSource);
989            System.out.println(excp);
990        }
991    }
992
993    public boolean getSelectByCurve() {
994      return selectByCurve;
995    }
996
997    private FlatField resample(FlatField X_field, FlatField Y_field) throws VisADException, RemoteException {
998
999       RealTupleType X_domainRef = null;
1000       RealTupleType Y_domainRef = null;
1001       float[][] coords = null;
1002       int[] indexes = null;
1003       float[][] Yvalues = Y_field.getFloats(false);
1004       float[][] Xsamples = ((SampledSet)X_field.getDomainSet()).getSamples(false);
1005
1006       CoordinateSystem X_cs = X_field.getDomainCoordinateSystem();
1007       if (X_cs == null) {
1008          RealTupleType X_domain = ((FunctionType)X_field.getType()).getDomain();
1009       }
1010       else {
1011         X_domainRef = X_cs.getReference();
1012       }
1013
1014       CoordinateSystem Y_cs = Y_field.getDomainCoordinateSystem();
1015       if (Y_cs == null) {
1016          RealTupleType Y_domain = ((FunctionType)Y_field.getType()).getDomain();
1017       }
1018       else {
1019         Y_domainRef = Y_cs.getReference();
1020       }
1021
1022       if ( X_domainRef != null && Y_domainRef != null) {
1023         Xsamples = X_cs.toReference(Xsamples);
1024         coords = Y_cs.fromReference(Xsamples);
1025         indexes = ((SampledSet)Y_field.getDomainSet()).valueToIndex(coords);
1026       }
1027       else if ( X_domainRef == null && Y_domainRef != null ) {
1028         Xsamples = Y_cs.fromReference(Xsamples);
1029         indexes = ((SampledSet)Y_field.getDomainSet()).valueToIndex(Xsamples);
1030       }
1031       else if ( X_domainRef != null && Y_domainRef == null) {
1032         Xsamples = X_cs.toReference(Xsamples);
1033         Gridded2DSet domSet = (Gridded2DSet) Y_field.getDomainSet();
1034
1035         // TODO this is a hack for the longitude range problem
1036         float[] hi = domSet.getHi();
1037         if (hi[0] <= 180f) {
1038           for (int t=0; t<Xsamples[0].length; t++) {
1039             if (Xsamples[0][t] > 180f) Xsamples[0][t] -=360;
1040           }
1041         }
1042         
1043         indexes = ((SampledSet)Y_field.getDomainSet()).valueToIndex(Xsamples);
1044       }
1045       else if (X_domainRef == null && Y_domainRef == null) {
1046         Gridded2DSet domSet = (Gridded2DSet) Y_field.getDomainSet();
1047         indexes = domSet.valueToIndex(Xsamples);
1048       }
1049       
1050       float[][] new_values = new float[1][indexes.length];
1051       for (int k=0; k<indexes.length; k++) {
1052          new_values[0][k] = Float.NaN;
1053          if (indexes[k] >= 0) {
1054            new_values[0][k] = Yvalues[0][indexes[k]];
1055          }
1056       }
1057
1058       FunctionType ftype = new FunctionType(((FunctionType)X_field.getType()).getDomain(),
1059                ((FunctionType)Y_field.getType()).getRange());
1060       Y_field = new FlatField(ftype, X_field.getDomainSet());
1061       Y_field.setSamples(new_values);
1062
1063       return Y_field;
1064    }
1065
1066
1067    private class ScatterDisplayable extends RGBDisplayable {
1068       ScatterDisplayable(String name, RealType rgbRealType, float[][] colorPalette, boolean alphaflag) 
1069           throws VisADException, RemoteException {
1070         super(name, rgbRealType, colorPalette, alphaflag);
1071       }
1072    }
1073
1074    private class ImageControl extends DisplayControlImpl {
1075      HydraRGBDisplayable rgbDisp;
1076      DisplayConventions dc;
1077      ColorTableWidget ctw;
1078
1079      ImageControl(HydraRGBDisplayable rgbDisp, DisplayConventions dc) {
1080        super();
1081        this.rgbDisp = rgbDisp;
1082        this.dc = dc;
1083      }
1084
1085      @Override public void setRange(Range r) throws VisADException, RemoteException {
1086          if (r != null) {
1087              rgbDisp.setRangeForColor(r.getMin(), r.getMax());
1088          }
1089      }
1090
1091      @Override public DisplayConventions getDisplayConventions() {
1092        return dc;
1093      }
1094
1095      @Override public void setColorTable(ColorTable ct) {
1096        try {
1097          ctw.setColorTable(ct);
1098          ScalarMap colorMap = rgbDisp.getColorMap();
1099          BaseColorControl clrCntrl = (BaseColorControl) colorMap.getControl();
1100
1101          // Force incoming color dimension to that of the colorMap
1102          //
1103          int numComps = clrCntrl.getNumberOfComponents();
1104          float[][] clrTable = ct.getColorTable();
1105          float[][] newTable = null;
1106          if (numComps != clrTable.length) {
1107            if (numComps < clrTable.length) {
1108              newTable = new float[numComps][clrTable[0].length];
1109              for (int k=0; k<numComps; k++) {
1110                System.arraycopy(clrTable[k], 0, newTable[k], 0, newTable[0].length);
1111              }
1112            }
1113            else if (numComps > clrTable.length) {
1114              newTable = new float[numComps][clrTable[0].length];
1115              for (int k=0; k<clrTable.length; k++) {
1116                System.arraycopy(clrTable[k], 0, newTable[k], 0, newTable[0].length);
1117              }
1118              newTable[3] = new float[clrTable[0].length];
1119            }
1120          } else {
1121              newTable = new float[numComps][clrTable[0].length];
1122              for (int k = 0; k < clrTable.length; k++) {
1123                System.arraycopy(clrTable[k], 0, newTable[k], 0, newTable[0].length);
1124              }
1125          } 
1126          clrCntrl.setTable(newTable);
1127        } 
1128        catch (Exception e) {
1129          LogUtil.logException("Problem changing color table", e);
1130        }
1131      }
1132    }
1133
1134    private class ImageCurveSelector extends CellImpl implements DisplayListener {
1135      boolean init = false;
1136      CurveDrawer curveDraw;
1137      DisplayMaster dspMaster;
1138      Gridded2DSet domainSet;
1139      CoordinateSystem cs;
1140      int domainLen_0;
1141      int domainLen_1;
1142      ImageCurveSelector other;
1143      UnionSet last_uSet = null;
1144      boolean imageLatLon = false;
1145      boolean active = true;
1146      float maskVal;
1147      LineDrawing lastCurve;
1148      StatsTable myTable = null;
1149      int myTableIndex = 0;
1150
1151      ImageCurveSelector(CurveDrawer curveDraw, FlatField image, DisplayMaster master, Color color, float maskVal, StatsTable mst) 
1152           throws VisADException, RemoteException {
1153        this.curveDraw = curveDraw;
1154        this.maskVal = maskVal;
1155        this.myTable = mst;
1156        myTableIndex = 0;
1157        if (color == Color.magenta) myTableIndex = 1;
1158        if (color == Color.green) myTableIndex = 2;
1159        if (color == Color.blue) myTableIndex = 3;
1160        dspMaster = master;
1161        dspMaster.addDisplayListener(this);
1162        domainSet = (Gridded2DSet) image.getDomainSet();
1163        int[] lens = domainSet.getLengths();
1164        domainLen_0 = lens[0];
1165        domainLen_1 = lens[1];
1166        cs = ((FunctionType)image.getType()).getDomain().getCoordinateSystem();
1167        RealTupleType reference = null;
1168        if (cs != null) {
1169          reference = cs.getReference();
1170        }
1171        else {
1172          reference = ((SetType)domainSet.getType()).getDomain();
1173        }
1174        RealType[] rtypes = reference.getRealComponents();
1175        if (rtypes[0].equals(RealType.Latitude)) imageLatLon = true;
1176        lastCurve = new LineDrawing("lastCurve");
1177        lastCurve.setColor(color);
1178        lastCurve.setLineWidth(2);
1179        master.addDisplayable(lastCurve);
1180      }
1181
1182      @Override public void displayChanged(DisplayEvent de)
1183             throws VisADException, RemoteException {
1184         if ((de.getId() == DisplayEvent.MOUSE_RELEASED) && (active)) {
1185           UnionSet uSet = curveDraw.getCurves();
1186           if (uSet == last_uSet) return;
1187           SampledSet[] sets = uSet.getSets();
1188           int s_idx = sets.length-1;
1189           float[][] crv;
1190
1191           if (cs != null) {
1192             crv = sets[s_idx].getSamples();
1193             if (imageLatLon) {
1194                float[] tmp = crv[0];
1195                crv[0] = crv[1];
1196                crv[1] = tmp;
1197             }
1198             crv = cs.fromReference(crv);
1199             crv = domainSet.valueToGrid(crv);
1200           }
1201           else {
1202             crv = sets[s_idx].getSamples();
1203             crv = domainSet.valueToGrid(crv);
1204           }
1205
1206           float[][] onImage = new float[2][crv[0].length];
1207           int cnt = 0;
1208           for (int i=0; i<crv[0].length; i++) {
1209             if ( ((crv[0][i] >= 0)&&(crv[0][i] <= domainLen_0)) &&
1210                  ((crv[1][i] >= 0)&&(crv[1][i] <= domainLen_1)) ) {
1211               onImage[0][cnt] = crv[0][i];
1212               onImage[1][cnt] = crv[1][i];
1213               cnt++;
1214             }
1215           }
1216           uSet = new UnionSet(new SampledSet[] {sets[s_idx]});
1217           last_uSet = uSet;
1218           lastCurve.setData(last_uSet);
1219           curveDraw.setCurves(uSet);
1220           other.updateCurve(sets[s_idx]);
1221
1222           if (cnt == 0) {
1223             return;
1224           }
1225
1226           float[][] tmp = new float[2][cnt];
1227           System.arraycopy(onImage[0], 0, tmp[0], 0, cnt);
1228           System.arraycopy(onImage[1], 0, tmp[1], 0, cnt);
1229           onImage = tmp;
1230
1231           float[] minmaxvals = minmax(onImage[0]);
1232           int low_0 = Math.round(minmaxvals[0]);
1233           int hi_0 = Math.round(minmaxvals[1]);
1234           minmaxvals = minmax(onImage[1]);
1235           int low_1 = Math.round(minmaxvals[0]);
1236           int hi_1 = Math.round(minmaxvals[1]);
1237
1238           int len_0 = (hi_0 - low_0) + 1;
1239           int len_1 = (hi_1 - low_1) + 1;
1240           int len = len_0*len_1;
1241
1242           tmp = new float[3][len];
1243           int[] tmpsel = new int[len];
1244
1245           int num_inside = 0;
1246           for (int j=0; j<len_1; j++) {
1247             for (int i=0; i<len_0; i++) {
1248               int idx = (j+low_1)*domainLen_0 + (i+low_0);
1249               float x = (float) (i + low_0);
1250               float y = (float) (j + low_1);
1251               if (DelaunayCustom.inside(crv, x, y)) {
1252                 tmp[0][num_inside] = scatterFieldRange[0][idx];
1253                 tmp[1][num_inside] = scatterFieldRange[1][idx];
1254                 tmp[2][num_inside] = maskVal;
1255                 tmpsel[num_inside] = idx;
1256                 num_inside++;
1257               }
1258             }
1259           }
1260           len = num_inside;
1261           float[][] markScatter = new float[3][len];
1262           System.arraycopy(tmp[0], 0, markScatter[0], 0, len);
1263           System.arraycopy(tmp[1], 0, markScatter[1], 0, len);
1264           System.arraycopy(tmp[2], 0, markScatter[2], 0, len);
1265
1266
1267           int last_len = 0;
1268           float[][] lastMark = ((FlatField)scatterMarkDsp.getData()).getFloats(false);
1269           tmp = new float[3][lastMark[0].length];
1270           for (int k=0; k<lastMark[0].length; k++) {
1271             if (lastMark[2][k] != maskVal) {
1272               tmp[0][last_len] = lastMark[0][k];
1273               tmp[1][last_len] = lastMark[1][k];
1274               tmp[2][last_len] = lastMark[2][k];
1275               last_len++;
1276             }
1277           }
1278
1279           float[][] newMarkScatter = new float[3][len+last_len];
1280           System.arraycopy(tmp[0], 0, newMarkScatter[0], 0, last_len);
1281           System.arraycopy(tmp[1], 0, newMarkScatter[1], 0, last_len);
1282           System.arraycopy(tmp[2], 0, newMarkScatter[2], 0, last_len);
1283           System.arraycopy(markScatter[0], 0, newMarkScatter[0], last_len, len);
1284           System.arraycopy(markScatter[1], 0, newMarkScatter[1], last_len, len);
1285           System.arraycopy(markScatter[2], 0, newMarkScatter[2], last_len, len);
1286
1287           Integer1DSet dset = new Integer1DSet(len+last_len);
1288           FlatField scatterFieldMark = new FlatField(
1289             new FunctionType(RealType.Generic,
1290                new RealTupleType(RealType.XAxis, RealType.YAxis, RealType.getRealType("mask"))), dset);
1291
1292           scatterFieldMark.setSamples(newMarkScatter, false);
1293           scatterMarkDsp.setData(scatterFieldMark);
1294
1295           if (myTable != null) {
1296             int[] selected = new int[len];
1297             System.arraycopy(tmpsel, 0, selected, 0, len);
1298             total_area = JPythonMethods.computeSum(Area_field, selected);
1299             myTable.setPoints(markScatter, len, myTableIndex, total_area);  
1300           }
1301
1302         }
1303      }
1304
1305      public void setActive(boolean active) {
1306        this.active = active;
1307      }
1308
1309      public void reset() throws VisADException, RemoteException {
1310
1311        float[][] lastMark = ((FlatField)scatterMarkDsp.getData()).getFloats(false);
1312        float[][] tmp = new float[3][lastMark[0].length];
1313        int cnt = 0;
1314        for (int k=0; k<lastMark[0].length; k++) {
1315          if (lastMark[2][k] != maskVal) {
1316             tmp[0][cnt] = lastMark[0][k];
1317             tmp[1][cnt] = lastMark[1][k];
1318             tmp[2][cnt] = lastMark[2][k];
1319             cnt++;
1320          }
1321        }
1322
1323        RealTupleType type = ((SetType)curveDraw.getCurves().getType()).getDomain();
1324        curveDraw.setCurves(new UnionSet(new Gridded2DSet[]{
1325            new Gridded2DSet(type, new float[][] {
1326            { 0.0f }, { 0.0f }}, 1) }));
1327        
1328        lastCurve.setData(new UnionSet(new Gridded2DSet[]{
1329            new Gridded2DSet(type, new float[][] {
1330            { 0.0f }, { 0.0f }}, 1) }));
1331
1332        FlatField scatterFieldMark = null;
1333        if (cnt == 0) {
1334        Integer1DSet dset = new Integer1DSet(2);
1335        scatterFieldMark = new FlatField(
1336        new FunctionType(RealType.Generic,
1337              new RealTupleType(RealType.XAxis, RealType.YAxis, RealType.getRealType("mask"))), dset);
1338        float[][] markScatter = new float[3][2]; 
1339        for (int k=0; k<2; k++) {
1340          markScatter[0][k] = scatterFieldRange[0][k];
1341          markScatter[1][k] = scatterFieldRange[1][k];
1342          markScatter[2][k] = 0;
1343        }
1344        scatterFieldMark.setSamples(markScatter, false);
1345        }
1346        else {
1347          Integer1DSet dset = new Integer1DSet(cnt);
1348          scatterFieldMark = new FlatField(
1349          new FunctionType(RealType.Generic,
1350                new RealTupleType(RealType.XAxis, RealType.YAxis, RealType.getRealType("mask"))), dset);
1351          float[][] markScatter = new float[3][cnt];
1352          for (int k=0; k<cnt; k++) {
1353            markScatter[0][k] = tmp[0][k];
1354            markScatter[1][k] = tmp[1][k];
1355            markScatter[2][k] = tmp[2][k];
1356          }
1357          scatterFieldMark.setSamples(markScatter, false);
1358        }
1359
1360        scatterMarkDsp.setData(scatterFieldMark);
1361      }
1362
1363      public void updateCurve(SampledSet set) throws VisADException, RemoteException {
1364        last_uSet = new UnionSet(new SampledSet[] {set});
1365        curveDraw.setCurves(last_uSet);
1366        lastCurve.setData(last_uSet);
1367      }
1368
1369      public void setOther(ImageCurveSelector other) {
1370        this.other = other;
1371      }
1372
1373      @Override public void doAction()
1374           throws VisADException, RemoteException {
1375        if (!init) {
1376          init = true;
1377          return;
1378        }
1379      }
1380
1381      public void setVisible(boolean visible) throws VisADException, RemoteException {
1382        curveDraw.setVisible(visible);
1383      }
1384
1385    }
1386
1387    private class ImageBoxSelector extends CellImpl {
1388        boolean init = false;
1389        boolean active = true;
1390        SubsetRubberBandBox subsetBox;
1391        Set imageDomain;
1392        int domainLen_0;
1393        LineDrawing lastBox;
1394        ImageBoxSelector other;
1395        float maskVal;
1396        boolean earthCoordDomain = false;
1397        StatsTable myTable = null;
1398        int myTableIndex = 0;
1399
1400        ImageBoxSelector(SubsetRubberBandBox subsetBox, Set imageDomain, DisplayMaster master, Color color, float maskVal, StatsTable mst)
1401            throws VisADException, RemoteException {
1402            super();
1403            this.myTable = mst;
1404            myTableIndex = 0;
1405            if (color == Color.magenta) myTableIndex = 1;
1406            if (color == Color.green) myTableIndex = 2;
1407            if (color == Color.blue) myTableIndex = 3;
1408            this.subsetBox = subsetBox;
1409            this.imageDomain = imageDomain;
1410            int[] lens = ((Gridded2DSet)imageDomain).getLengths();
1411            this.maskVal = maskVal;
1412            domainLen_0 = lens[0];
1413            lastBox = new LineDrawing("last_box");
1414            lastBox.setColor(color);
1415            master.addDisplayable(lastBox);
1416            subsetBox.addAction(this);
1417            master.addDisplayable(subsetBox);
1418            RealTupleType rtt = ((SetType)imageDomain.getType()).getDomain();
1419            if (rtt.equals(RealTupleType.SpatialEarth2DTuple) ||
1420                rtt.equals(RealTupleType.LatitudeLongitudeTuple)) {
1421                earthCoordDomain = true;
1422            }
1423        }
1424
1425        @Override public void doAction()
1426            throws VisADException, RemoteException
1427        {
1428            if (!init) {
1429                init = true;
1430                return;
1431            }
1432
1433            if (!active) {
1434                return;
1435            }
1436
1437            Gridded2DSet set = subsetBox.getBounds();
1438            float[][] corners = set.getSamples(false);
1439            float[][] coords = corners;
1440            if (corners == null) return;
1441
1442            if ((imageDomain instanceof Linear2DSet) || !earthCoordDomain) {
1443                coords = ((Gridded2DSet)imageDomain).valueToGrid(corners);
1444            }
1445
1446            float[] coords_0 = coords[0];
1447            float[] coords_1 = coords[1];
1448
1449            int low_0 = Math.round(Math.min(coords_0[0], coords_0[1]));
1450            int low_1 = Math.round(Math.min(coords_1[0], coords_1[1]));
1451            int hi_0  = Math.round(Math.max(coords_0[0], coords_0[1]));
1452            int hi_1  = Math.round(Math.max(coords_1[0], coords_1[1]));
1453
1454            int len_0 = (hi_0 - low_0) + 1;
1455            int len_1 = (hi_1 - low_1) + 1;
1456            int len = len_0*len_1;
1457
1458            float[][] markScatter = new float[3][len];
1459            int[] selected = new int[len];
1460
1461            for (int j=0; j<len_1; j++) {
1462                for (int i=0; i<len_0; i++) {
1463                    int idx = (j+low_1)*domainLen_0 + (i+low_0);
1464                    int k = j*len_0 + i;
1465                    markScatter[0][k] = scatterFieldRange[0][idx];
1466                    markScatter[1][k] = scatterFieldRange[1][idx];
1467                    markScatter[2][k] = maskVal;
1468                    selected[k] = idx;
1469                }
1470            }
1471
1472            int last_len = 0;
1473            float[][] lastMark = ((FlatField)scatterMarkDsp.getData()).getFloats(false);
1474            float[][] tmp = new float[3][lastMark[0].length];
1475            for (int k=0; k<lastMark[0].length; k++) {
1476                if (lastMark[2][k] != maskVal) {
1477                    tmp[0][last_len] = lastMark[0][k];
1478                    tmp[1][last_len] = lastMark[1][k];
1479                    tmp[2][last_len] = lastMark[2][k];
1480                    last_len++;
1481                }
1482            }
1483
1484            float[][] newMarkScatter = new float[3][len+last_len];
1485            System.arraycopy(tmp[0], 0, newMarkScatter[0], 0, last_len);
1486            System.arraycopy(tmp[1], 0, newMarkScatter[1], 0, last_len);
1487            System.arraycopy(tmp[2], 0, newMarkScatter[2], 0, last_len);
1488            System.arraycopy(markScatter[0], 0, newMarkScatter[0], last_len, len);
1489            System.arraycopy(markScatter[1], 0, newMarkScatter[1], last_len, len);
1490            System.arraycopy(markScatter[2], 0, newMarkScatter[2], last_len, len);
1491
1492            Integer1DSet dset = new Integer1DSet(len+last_len);
1493            FlatField scatterFieldMark = new FlatField(
1494                new FunctionType(RealType.Generic,
1495                    new RealTupleType(RealType.XAxis, RealType.YAxis, RealType.getRealType("mask"))), dset);
1496
1497            scatterFieldMark.setSamples(newMarkScatter, false);
1498            scatterMarkDsp.setData(scatterFieldMark);
1499
1500            if (myTable != null) {
1501                total_area = JPythonMethods.computeSum(Area_field, selected);
1502                myTable.setPoints(markScatter, len, myTableIndex, total_area);
1503            }
1504
1505            updateBox();
1506        }
1507
1508        public void setActive(boolean active) {
1509            this.active = active;
1510        }
1511
1512        public void setVisible(boolean visible) throws VisADException, RemoteException {
1513            subsetBox.setVisible(visible);
1514            if (visible) {
1515                lastBox.setVisible(visible);
1516            }
1517        }
1518
1519        public void reset() throws VisADException, RemoteException {
1520            Gridded2DSet set2D =
1521                new Gridded2DSet(RealTupleType.SpatialCartesian2DTuple,
1522                    new float[][] {{0},{0}}, 1);
1523            lastBox.setVisible(false);
1524            lastBox.setData(set2D);
1525
1526            float[][] lastMark = ((FlatField)scatterMarkDsp.getData()).getFloats(false);
1527            float[][] tmp = new float[3][lastMark[0].length];
1528            int cnt = 0;
1529            for (int k=0; k<lastMark[0].length; k++) {
1530                if (lastMark[2][k] != maskVal) {
1531                    tmp[0][cnt] = lastMark[0][k];
1532                    tmp[1][cnt] = lastMark[1][k];
1533                    tmp[2][cnt] = lastMark[2][k];
1534                    cnt++;
1535                }
1536            }
1537
1538            FlatField scatterFieldMark;
1539            if (cnt == 2) {
1540                Integer1DSet dset = new Integer1DSet(2);
1541                scatterFieldMark = new FlatField(
1542                    new FunctionType(RealType.Generic,
1543                        new RealTupleType(RealType.XAxis, RealType.YAxis, RealType.getRealType("mask"))), dset);
1544                float[][] markScatter = new float[3][2];
1545                for (int k=0; k<2; k++) {
1546                    markScatter[0][k] = scatterFieldRange[0][k];
1547                    markScatter[1][k] = scatterFieldRange[1][k];
1548                    markScatter[2][k] = 0;
1549                }
1550                scatterFieldMark.setSamples(markScatter, false);
1551            }
1552            else {
1553                Integer1DSet dset = new Integer1DSet(cnt);
1554                scatterFieldMark = new FlatField(
1555                    new FunctionType(RealType.Generic,
1556                        new RealTupleType(RealType.XAxis, RealType.YAxis, RealType.getRealType("mask"))), dset);
1557                float[][] markScatter = new float[3][cnt];
1558                for (int k=0; k<cnt; k++) {
1559                    markScatter[0][k] = tmp[0][k];
1560                    markScatter[1][k] = tmp[1][k];
1561                    markScatter[2][k] = tmp[2][k];
1562                }
1563                scatterFieldMark.setSamples(markScatter, false);
1564            }
1565
1566            scatterMarkDsp.setData(scatterFieldMark);
1567        }
1568
1569        public void setOther(ImageBoxSelector other) {
1570            this.other = other;
1571        }
1572
1573        public void updateBox() throws VisADException, RemoteException {
1574            Gridded3DSet set3D = subsetBox.getLastBox();
1575            float[][] samples = set3D.getSamples(false);
1576            Gridded2DSet set2D =
1577                new Gridded2DSet(RealTupleType.SpatialCartesian2DTuple,
1578                    new float[][] {samples[0], samples[1]}, samples[0].length);
1579            lastBox.setData(set2D);
1580            other.updateBox(set2D);
1581        }
1582
1583        public void updateBox(Gridded2DSet set2D) throws VisADException, RemoteException {
1584            lastBox.setData(set2D);
1585        }
1586
1587    }
1588
1589    private class ScatterBoxSelector extends CellImpl {
1590       boolean init = false;
1591       double[] x_coords = new double[2];
1592       double[] y_coords = new double[2];
1593       RubberBandBox rbb;
1594       LineDrawing selectBox;
1595       boolean active = true;
1596       float maskVal = 0;
1597
1598       ScatterBoxSelector(DisplayMaster master, Color color, float maskVal) throws VisADException, RemoteException {
1599         selectBox = new LineDrawing("select");
1600         selectBox.setColor(color);
1601
1602         rbb = new RubberBandBox(RealType.XAxis, RealType.YAxis, 1);
1603         rbb.setColor(color);
1604         rbb.addAction(this);
1605
1606         master.addDisplayable(rbb);
1607         master.addDisplayable(selectBox);
1608         this.maskVal = maskVal;
1609       }
1610
1611
1612       @Override public void doAction() throws VisADException, RemoteException {
1613         if (!init) {
1614           init = true;
1615           return;
1616         }
1617
1618         if (!active) {
1619           return;
1620         }
1621
1622         Gridded2DSet set = rbb.getBounds();
1623         float[] low = set.getLow();
1624         float[] hi = set.getHi();
1625         x_coords[0] = low[0];
1626         x_coords[1] = hi[0];
1627         y_coords[0] = low[1];
1628         y_coords[1] = hi[1];
1629                                                                                                                                                  
1630         SampledSet[] sets = new SampledSet[4];
1631         sets[0] = new Gridded2DSet(RealTupleType.SpatialCartesian2DTuple, new float[][] {{low[0], hi[0]}, {low[1], low[1]}}, 2);
1632         sets[1] = new Gridded2DSet(RealTupleType.SpatialCartesian2DTuple, new float[][] {{hi[0], hi[0]}, {low[1], hi[1]}}, 2);
1633         sets[2] = new Gridded2DSet(RealTupleType.SpatialCartesian2DTuple, new float[][] {{hi[0], low[0]}, {hi[1], hi[1]}}, 2);
1634         sets[3] = new Gridded2DSet(RealTupleType.SpatialCartesian2DTuple, new float[][] {{low[0], low[0]}, {hi[1], low[1]}}, 2);
1635         UnionSet uset = new UnionSet(sets);
1636         selectBox.setData(uset);
1637
1638         try {
1639           histoField.markMaskFieldByRange(x_coords, y_coords, maskVal);
1640         } catch (Exception e) {
1641           logger.error("Problem changing histogram", e);
1642         }
1643       }
1644
1645       public void setVisible(boolean visible) throws VisADException, RemoteException {
1646         rbb.setVisible(visible);
1647         if (visible) {
1648           selectBox.setVisible(visible);
1649         }
1650       }
1651
1652       public void setActive(boolean active) {
1653         this.active = active;
1654       }
1655
1656       public void reset() throws Exception {
1657         if (!active) return;
1658         selectBox.setVisible(false);
1659         selectBox.setData(new Gridded2DSet(RealTupleType.SpatialCartesian2DTuple, new float[][] {{0f, 0f}, {0f, 0f}}, 2));
1660         histoField.resetMaskField(maskVal);
1661       }
1662   }
1663
1664   private class ScatterCurveSelector extends CellImpl implements DisplayListener {
1665     CurveDrawer curveDraw;
1666     boolean init = false;
1667     UnionSet last_uSet = null;
1668     boolean active = true;
1669     float maskVal = 0;
1670     LineDrawing selectCurve;
1671
1672     ScatterCurveSelector(DisplayMaster master, Color color, float maskVal) throws VisADException, RemoteException {
1673       curveDraw = new CurveDrawer(RealType.XAxis, RealType.YAxis, 1);
1674       curveDraw.setColor(color);
1675       curveDraw.setLineWidth(2);
1676       curveDraw.setData(new UnionSet(new Gridded2DSet[]{
1677            new Gridded2DSet(RealTupleType.SpatialCartesian2DTuple, new float[][] {
1678            { scatterFieldRange[0][0] }, { scatterFieldRange[1][0]}
1679        }, 1) }));
1680
1681       selectCurve = new LineDrawing("select");
1682       selectCurve.setColor(color);
1683       selectCurve.setLineWidth(2);
1684       master.addDisplayable(curveDraw);
1685       master.addDisplayable(selectCurve);
1686       this.maskVal = maskVal;
1687
1688       curveDraw.addAction(this);
1689       master.addDisplayListener(this);
1690     }
1691
1692     @Override public void displayChanged(DisplayEvent de)
1693            throws VisADException, RemoteException {
1694       if ((de.getId() == DisplayEvent.MOUSE_RELEASED) && (active)) {
1695         UnionSet uSet = curveDraw.getCurves();
1696         if (uSet == last_uSet) return;
1697         SampledSet[] sets = uSet.getSets();
1698         int s_idx = sets.length-1;
1699         float[][] crv;
1700                                                                                                                                                  
1701         crv = sets[s_idx].getSamples();
1702         last_uSet = new UnionSet(new SampledSet[] {sets[s_idx]});
1703         curveDraw.setCurves(last_uSet);
1704         selectCurve.setData(last_uSet);
1705
1706         try {
1707           histoField.clearMaskField(maskVal);
1708           histoField.markMaskFieldByCurve(crv, maskVal);
1709         } catch (Exception e) {
1710           logger.error("Problem handling displayChange", e);
1711         }
1712       }
1713     }
1714
1715     @Override public  void doAction() throws VisADException, RemoteException {
1716       if (!init) {
1717         init = true;
1718         return;
1719       }
1720     }
1721
1722     public void setVisible(boolean visible) throws VisADException, RemoteException {
1723       curveDraw.setVisible(visible);
1724     }
1725
1726     public void setActive(boolean active) {
1727       this.active = active;
1728     }
1729
1730     public void reset() throws Exception {
1731       if (!active) return;
1732       curveDraw.setData(new UnionSet(new Gridded2DSet[]{
1733            new Gridded2DSet(RealTupleType.SpatialCartesian2DTuple, new float[][] {
1734            { scatterFieldRange[0][0] }, { scatterFieldRange[1][0]}
1735        }, 1) }));
1736       selectCurve.setData(new UnionSet(new Gridded2DSet[]{
1737            new Gridded2DSet(RealTupleType.SpatialCartesian2DTuple, new float[][] {
1738            { scatterFieldRange[0][0] }, { scatterFieldRange[1][0]}
1739        }, 1) }));
1740       histoField.resetMaskField(maskVal);
1741     }
1742   }
1743
1744    private class BoxCurveSwitch implements ActionListener {
1745
1746        public BoxCurveSwitch() {
1747        }
1748
1749        @Override public void actionPerformed(ActionEvent ae) {
1750            String cmd = ae.getActionCommand();
1751            try {
1752                if (cmd.equals("Box")) {
1753                    selectByCurve = false;
1754                } else if (cmd.equals("Curve")) {
1755                    selectByCurve = true;
1756                }
1757            } catch (Exception e) {
1758                logger.error("Problem switching curve type", e);
1759            }
1760        }
1761    }
1762
1763    public static float[] minmax(float[] values) {
1764        float min =  Float.MAX_VALUE;
1765        float max = -Float.MAX_VALUE;
1766        for (int k = 0; k < values.length; k++) {
1767            float val = values[k];
1768            if ((val == val) && (val < Float.POSITIVE_INFINITY) && (val > Float.NEGATIVE_INFINITY)) {
1769                if (val < min) min = val;
1770                if (val > max) max = val;
1771            }
1772        }
1773        return new float[] {min, max};
1774    }
1775
1776    public boolean getIsLatLon(FlatField field) throws VisADException, RemoteException {
1777        boolean isLL = false;
1778        FunctionType fnc_type = (FunctionType) field.getType();
1779        RealTupleType rtt = fnc_type.getDomain();
1780        if (rtt.equals(RealTupleType.LatitudeLongitudeTuple)) {
1781            isLL = true;
1782        } else if (!rtt.equals(RealTupleType.SpatialEarth2DTuple)) {
1783            rtt = fnc_type.getDomain().getCoordinateSystem().getReference();
1784            if ( rtt.equals(RealTupleType.LatitudeLongitudeTuple)) {
1785                isLL = true;
1786            }
1787        }
1788        return isLL;
1789    }
1790}