001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2017
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 http://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 = LoggerFactory.getLogger(ScatterDisplay.class);
126
127    private Container container;
128    private FlatField X_field;
129    private FlatField Y_field;
130    private FlatField Area_field;
131    private double total_area;
132    private DisplayMaster scatterMaster = null;
133
134    private DisplayMaster dspMasterX;
135    private DisplayMaster dspMasterY;
136
137    private HistogramField histoField;
138
139    private FlatField mask_field;
140    private float[][] mask_range;
141    private float[][] scatterFieldRange;
142    private Data X_data;
143    private Data Y_data;
144    private String X_name;
145    private String Y_name;
146    
147    private boolean cancel = false;
148
149    private ScatterDisplayable scatterMarkDsp;
150
151    private BoxCurveSwitch boxCurveSwitch;
152
153    public DataChoice dataChoiceX = null;
154
155    public DataChoice dataChoiceY = null;
156
157    public DataSelection dataSelectionX = null;
158
159    public DataSelection dataSelectionY = null;
160
161    JComponent ctwCompX;
162
163    JComponent ctwCompY;
164
165    ColorTableWidget ctw;
166
167    int n_selectors = 3;
168
169    List<ScatterBoxSelector> scatterBoxSelectors = new ArrayList<>();
170
171    List<ScatterCurveSelector> scatterCurveSelectors = new ArrayList<>();
172
173    List<ImageBoxSelector> imageXBoxSelectors = new ArrayList<>();
174
175    List<ImageBoxSelector> imageYBoxSelectors = new ArrayList<>();
176
177    List<ImageCurveSelector> imageXCurveSelectors = new ArrayList<>();
178
179    List<ImageCurveSelector> imageYCurveSelectors = new ArrayList<>();
180
181    JToggleButton[] selectorToggleButtons = new JToggleButton[n_selectors];
182
183    Color[] selectorColors = new Color[] {
184        Color.magenta,
185        Color.green,
186        Color.blue
187    };
188
189    float[][] maskColorPalette = new float[][] {
190        { 0.8f, 0.0f, 0.0f },
191        { 0.0f, 0.8f, 0.0f },
192        { 0.8f, 0.0f, 0.8f }
193    };
194
195    float[][] markPaletteBlackBackground = new float[][] {
196        { 1.0f, 0.8f, 0.0f, 0.0f },
197        { 1.0f, 0.0f, 0.8f, 0.0f },
198        { 1.0f, 0.8f, 0.0f, 0.8f }
199    };
200
201    float[][] markPaletteWhiteBackground = new float[][] {
202        { 0.0f, 0.8f, 0.0f, 0.0f },
203        { 0.0f, 0.0f, 0.8f, 0.0f },
204        { 0.0f, 0.8f, 0.0f, 0.8f }
205    };
206
207    /** used for persistence */
208    private boolean blackBackground = true;
209    
210    JRadioButton bgColorBlack;
211    
212    JRadioButton bgColorWhite;
213    
214    ButtonGroup bgColorGroup;
215    
216    JButton computeStatsButton;
217    
218    StatsTable statsTable;
219   
220    boolean selectByCurve = false;
221
222    public ScatterDisplay() {
223      super();
224      setHelpUrl("idv.controls.misc.scatteranalysiscontrol");
225    }
226    
227
228    @Override public boolean init(List choices) throws VisADException, RemoteException {
229        bgColorBlack = new JRadioButton("Black");
230        bgColorBlack.addActionListener(e -> {
231            scatterMaster.setForeground(Color.white);
232            scatterMaster.setBackground(Color.black);
233            setBlackBackground(true);
234            try {
235                scatterMarkDsp.setColorPalette(markPaletteBlackBackground);
236            } catch (Exception ex) {
237                logger.error("could not change color palette", ex);
238            }
239        });
240
241        bgColorWhite = new JRadioButton("White");
242        bgColorWhite.addActionListener(e -> {
243            scatterMaster.setForeground(Color.black);
244            scatterMaster.setBackground(Color.white);
245            setBlackBackground(false);
246            try {
247                scatterMarkDsp.setColorPalette(markPaletteWhiteBackground);
248            } catch (Exception ex) {
249                logger.error("could not change color palette", ex);
250            }
251        });
252
253        bgColorGroup = new ButtonGroup();
254        bgColorGroup.add(bgColorBlack);
255        bgColorGroup.add(bgColorWhite);
256
257        bgColorBlack.setSelected(getBlackBackground());
258        bgColorWhite.setSelected(!getBlackBackground());
259        
260        if ((dataChoiceX != null) && (dataChoiceY != null)) {
261          setupFromUnpersistence();
262        } else {
263            try {
264                setup();
265            } catch (VisADException vade) {
266                return false;
267            }
268        }
269
270        mask_field = new FlatField(
271             new FunctionType(((FunctionType)X_field.getType()).getDomain(), RealType.Generic),
272                  X_field.getDomainSet());
273
274        int len = X_field.getDomainSet().getLength();
275        int[] lens = ((Gridded2DSet)X_field.getDomainSet()).getLengths();
276        mask_range = new float[1][len];
277        for (int t=0; t<len; t++) {
278          mask_range[0][t] = Float.NaN;
279        }
280        mask_range[0][0] = 0; //- field should not be all missing
281        mask_field.setSamples(mask_range, false);
282                                                                                                                                                  
283        try {
284          int binSize = ((lens[0]*lens[1]/(256*256))*4)/10;
285          if (binSize < 2) binSize = 2;
286          histoField = new HistogramField(X_field, Y_field, mask_field, 256, binSize);
287        }
288        catch (Exception e) {
289          e.printStackTrace();
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<DataChoice> 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        final List clonedList =
445            DataChoice.cloneDataChoices((List)choices.get(0));
446        dataSelection = ((DataChoice) clonedList.get(0)).getDataSelection();
447        //- don't do this in a separate thread like the IDV does.
448        //- We want the dataChoice list updated before return.
449        try {
450          addNewData(clonedList);
451        } catch (Exception exc) {
452          logException("Selecting new data", exc);
453        }
454    }
455
456
457    @Override public void initDone() {
458       try {
459         DisplayMaster master = makeScatterDisplay();
460         for (int k=0; k<n_selectors; k++) {
461           scatterBoxSelectors.add(new ScatterBoxSelector(master, selectorColors[k], (float)k));
462           scatterCurveSelectors.add(new ScatterCurveSelector(master, selectorColors[k], (float)k));
463         }
464         master.draw();
465
466         for (int k=0; k<n_selectors; k++) {
467           SubsetRubberBandBox X_subsetBox =
468              new SubsetRubberBandBox(getIsLatLon(X_field), X_field,
469                      ((MapProjectionDisplayJ3D)dspMasterX).getDisplayCoordinateSystem(), 1, false);
470           X_subsetBox.setColor(selectorColors[k]);
471
472           ImageBoxSelector markX = new ImageBoxSelector(X_subsetBox, X_field.getDomainSet(), dspMasterX, selectorColors[k], (float)k+1, statsTable);
473
474           SubsetRubberBandBox Y_subsetBox =
475              new SubsetRubberBandBox(getIsLatLon(Y_field), Y_field,
476                 ((MapProjectionDisplayJ3D)dspMasterY).getDisplayCoordinateSystem(), 1, false);
477           Y_subsetBox.setColor(selectorColors[k]);
478           ImageBoxSelector markY = new ImageBoxSelector(Y_subsetBox, Y_field.getDomainSet(), dspMasterY, selectorColors[k], (float)k+1, statsTable);
479
480           markX.setOther(markY);
481           markY.setOther(markX);
482           imageXBoxSelectors.add(markX);
483           imageYBoxSelectors.add(markY);
484         }
485
486         for (int k=0; k<n_selectors; k++) {
487           CurveDrawer curveDraw = new CurveDrawer(RealType.Longitude, RealType.Latitude, 1);
488           curveDraw.setColor(selectorColors[k]);
489           curveDraw.setLineWidth(2);
490           ImageCurveSelector curveX = new ImageCurveSelector(curveDraw, X_field, dspMasterX, selectorColors[k], (float) k+1, statsTable);
491           curveX.setActive(false);
492           curveDraw.addAction(curveX);
493           curveX.setVisible(false);
494           dspMasterX.addDisplayable(curveDraw);
495
496           curveDraw = new CurveDrawer(RealType.Longitude, RealType.Latitude, 1);
497           curveDraw.setColor(selectorColors[k]);
498           curveDraw.setLineWidth(2);
499           ImageCurveSelector curveY = new ImageCurveSelector(curveDraw, Y_field, dspMasterY, selectorColors[k], (float) k+1, statsTable);
500           curveY.setActive(false);
501           curveDraw.addAction(curveY);
502           curveY.setVisible(false);
503           dspMasterY.addDisplayable(curveDraw);
504
505           curveX.setOther(curveY);
506           curveY.setOther(curveX);
507           imageXCurveSelectors.add(curveX);
508           imageYCurveSelectors.add(curveY);
509         }
510
511         for (int k=0; k<n_selectors; k++) {
512           JToggleButton jtog = selectorToggleButtons[k];
513          
514           jtog.addActionListener(e -> {
515              int idx = Integer.valueOf(e.getActionCommand());
516              try {
517                for (int i=0; i<n_selectors; i++) {
518                  ScatterBoxSelector boxSel = (ScatterBoxSelector) scatterBoxSelectors.get(i);
519                  ImageBoxSelector imageXbox = (ImageBoxSelector) imageXBoxSelectors.get(i);
520                  ImageBoxSelector imageYbox = (ImageBoxSelector) imageYBoxSelectors.get(i);
521                  ScatterCurveSelector curveSel = (ScatterCurveSelector) scatterCurveSelectors.get(i);
522                  ImageCurveSelector imageXcurve = (ImageCurveSelector) imageXCurveSelectors.get(i);
523                  ImageCurveSelector imageYcurve = (ImageCurveSelector) imageYCurveSelectors.get(i);
524
525                  if (i == idx) {
526                    if (!selectorToggleButtons[i].isSelected()) {
527
528                      if (statsTable != null) statsTable.resetValues(i);
529
530                      boxSel.reset();
531                      boxSel.setActive(false);
532                      boxSel.setVisible(false);
533
534                      imageXbox.reset();
535                      imageXbox.setActive(false);
536                      imageXbox.setVisible(false);
537
538                      imageYbox.reset();
539                      imageYbox.setActive(false);
540                      imageYbox.setVisible(false);
541
542                      curveSel.reset();
543                      curveSel.setActive(false);
544                      curveSel.setVisible(false);
545
546                      imageXcurve.reset();
547                      imageXcurve.setActive(false);
548                      imageXcurve.setVisible(false);
549                      imageYcurve.reset();
550                      imageYcurve.setActive(false);
551                      imageYcurve.setVisible(false);
552                      selectorToggleButtons[i].setSelected(true);
553                    }
554                    boxSel.setActive(!getSelectByCurve());
555                    boxSel.setVisible(!getSelectByCurve());
556                    imageXbox.setActive(!getSelectByCurve());
557                    imageXbox.setVisible(!getSelectByCurve());
558                    imageYbox.setActive(!getSelectByCurve());
559                    imageYbox.setVisible(!getSelectByCurve());
560
561                    curveSel.setActive(getSelectByCurve());
562                    curveSel.setVisible(getSelectByCurve());
563                    imageXcurve.setActive(getSelectByCurve());
564                    imageXcurve.setVisible(getSelectByCurve());
565                    imageYcurve.setActive(getSelectByCurve());
566                    imageYcurve.setVisible(getSelectByCurve());
567                  }
568                  else {
569                    selectorToggleButtons[i].setSelected(false);
570                    boxSel.setActive(false);
571                    boxSel.setVisible(false);
572                    imageXbox.setActive(false);
573                    imageXbox.setVisible(false);
574                    imageYbox.setActive(false);
575                    imageYbox.setVisible(false);
576                    curveSel.setActive(false);
577                    curveSel.setVisible(false);
578                    imageXcurve.setActive(false);
579                    imageXcurve.setVisible(false);
580                    imageYcurve.setActive(false);
581                    imageYcurve.setVisible(false);
582                  }
583                }
584              }
585              catch (Exception exc) {
586                System.out.println(exc);
587              }
588           });
589
590           ScatterBoxSelector boxSel = (ScatterBoxSelector) scatterBoxSelectors.get(k);
591           ImageBoxSelector imageXbox = (ImageBoxSelector) imageXBoxSelectors.get(k);
592           ImageBoxSelector imageYbox = (ImageBoxSelector) imageYBoxSelectors.get(k);
593           ScatterCurveSelector curveSel = (ScatterCurveSelector) scatterCurveSelectors.get(k);
594           ImageCurveSelector imageXcurve = (ImageCurveSelector) imageXCurveSelectors.get(k);
595           ImageCurveSelector imageYcurve = (ImageCurveSelector) imageYCurveSelectors.get(k);
596
597           if (k == 0) {
598              jtog.setSelected(true);
599              boxSel.setActive(!getSelectByCurve());
600              boxSel.setVisible(!getSelectByCurve());
601              imageXbox.setActive(!getSelectByCurve());
602              imageXbox.setVisible(!getSelectByCurve());
603              imageYbox.setActive(!getSelectByCurve());
604              imageYbox.setVisible(!getSelectByCurve());
605
606              curveSel.setActive(getSelectByCurve());
607              curveSel.setVisible(getSelectByCurve());
608              imageXcurve.setActive(getSelectByCurve());
609              imageXcurve.setVisible(getSelectByCurve());
610              imageYcurve.setActive(getSelectByCurve());
611              imageYcurve.setVisible(getSelectByCurve());
612            }
613            else {
614              boxSel.setActive(false);
615              boxSel.setVisible(false);
616              imageXbox.setActive(false);
617              imageXbox.setVisible(false);
618              imageYbox.setActive(false);
619              imageYbox.setVisible(false);
620              curveSel.setActive(false);
621              curveSel.setVisible(false);
622              imageXcurve.setActive(false);
623              imageXcurve.setVisible(false);
624              imageYcurve.setActive(false);
625              imageYcurve.setVisible(false);
626           }
627         }
628       }
629       catch (Exception e) {
630         e.printStackTrace();
631       }
632    }
633    
634    public DisplayMaster makeScatterDisplay() throws VisADException, RemoteException {
635
636       ScatterDisplayable scatterDsp = new ScatterDisplayable("scatter",
637                   RealType.getRealType("mask"), markPaletteBlackBackground, false);
638       float[] valsX = X_field.getFloats(false)[0];
639       float[] valsY = Y_field.getFloats(false)[0];
640       Integer1DSet set = new Integer1DSet(valsX.length);
641       FlatField scatter = new FlatField(
642           new FunctionType(RealType.Generic,
643               new RealTupleType(RealType.XAxis, RealType.YAxis, RealType.getRealType("mask"))), set);
644       float[] mask = new float[valsX.length];
645       for (int k=0; k<mask.length; k++) {
646           mask[k] = 0;
647       }
648       scatterFieldRange = new float[][] {valsX, valsY, mask};
649       scatter.setSamples(scatterFieldRange);
650       scatterDsp.setPointSize(2f);
651       scatterDsp.setRangeForColor(0,n_selectors);
652
653       float[] xRange = minmax(valsX);
654       float[] yRange = minmax(valsY);
655       
656       scatterDsp.setData(scatter);
657
658       scatterMarkDsp = new ScatterDisplayable("scatter",
659                   RealType.getRealType("mask"), markPaletteBlackBackground, false);
660       set = new Integer1DSet(2);
661       scatter = new FlatField(
662           new FunctionType(RealType.Generic,
663               new RealTupleType(RealType.XAxis, RealType.YAxis, RealType.getRealType("mask"))), set);
664       scatterMarkDsp.setData(scatter);
665       scatterMarkDsp.setPointSize(2f);
666       scatterMarkDsp.setRangeForColor(0,n_selectors);
667
668       DisplayMaster master = scatterMaster;
669       ((XYDisplay)master).showAxisScales(true);
670       AxisScale scaleX = ((XYDisplay)master).getXAxisScale();
671       scaleX.setTitle(X_name);
672       AxisScale scaleY = ((XYDisplay)master).getYAxisScale();
673       scaleY.setTitle(Y_name);
674
675       ((XYDisplay)master).setXRange((double)xRange[0], (double)xRange[1]);
676       ((XYDisplay)master).setYRange((double)yRange[0], (double)yRange[1]);
677       master.addDisplayable(scatterDsp);
678       master.addDisplayable(scatterMarkDsp);
679
680       return master;
681    }
682
683    @Override public Container doMakeContents() {
684        JPanel pane = new JPanel(new GridLayout(1,3));
685
686        Component[] comps = new Component[] {null, null, null};
687        comps[0] = dspMasterX.getComponent();
688        comps[1] = dspMasterY.getComponent();
689        comps[2] = getScatterTabComponent();
690
691        JPanel panelX = new JPanel(new BorderLayout());
692        panelX.setBorder(new EmptyBorder(4,4,4,4));
693        panelX.add(comps[0], BorderLayout.CENTER);
694        panelX.add(ctwCompX, BorderLayout.SOUTH);
695
696        JPanel panelY = new JPanel(new BorderLayout());
697        panelY.setBorder(new EmptyBorder(4,4,4,4));
698        panelY.add(comps[1], BorderLayout.CENTER);
699        panelY.add(ctwCompY, BorderLayout.SOUTH);
700
701        JPanel panelS = new JPanel(new BorderLayout());
702        panelS.setBorder(new EmptyBorder(4,4,4,4));
703        panelS.add(comps[2], BorderLayout.CENTER);
704
705        pane.add(panelX);
706        pane.add(panelY);
707        pane.add(panelS);
708
709        
710        JPanel buttonPanel = new JPanel();
711        buttonPanel.setLayout(new FlowLayout());
712        JRadioButton boxSelect = new JRadioButton("Box");
713        boxSelect.setSelected(true);
714        JRadioButton curveSelect = new JRadioButton("Curve");
715        ButtonGroup buttonGroup = new ButtonGroup();
716        buttonGroup.add(boxSelect);
717        buttonGroup.add(curveSelect);
718        buttonPanel.add(boxSelect);
719        buttonPanel.add(curveSelect);
720
721        boxCurveSwitch = new BoxCurveSwitch();
722        boxSelect.addActionListener(boxCurveSwitch);
723        curveSelect.addActionListener(boxCurveSwitch);
724        
725
726        JPanel toggleButtonPanel = new JPanel(new FlowLayout());
727        for (int k=0; k<n_selectors; k++) {
728          JToggleButton jtog = 
729             new JToggleButton(
730                 new ImageIcon(getClass().getResource("/edu/wisc/ssec/mcidasv/resources/icons/buttons/subset12.jpg")));
731          jtog.setBorder(new CompoundBorder(new LineBorder(selectorColors[k],2), new EmptyBorder(4,4,4,4)));
732          jtog.setActionCommand(String.valueOf(k));
733          toggleButtonPanel.add(jtog);
734          selectorToggleButtons[k] = jtog;
735        }
736
737        buttonPanel.add(toggleButtonPanel);
738
739        JButton computeStatsButton = new JButton("compute statistics");
740
741        computeStatsButton.addActionListener(e -> {
742           if (statsTable == null) {
743             statsTable = new StatsTable();
744           }
745
746           statsTable.setIsShowing();
747           statsTable.setFields(X_field, Y_field,0);
748        });
749        
750        buttonPanel.add(computeStatsButton);
751        buttonPanel.add(new JLabel("Background Color:"));
752        buttonPanel.add(bgColorBlack);
753        buttonPanel.add(bgColorWhite);
754
755        //-container = pane;
756        JPanel new_pane = new JPanel(new BorderLayout());
757        new_pane.add(pane, BorderLayout.CENTER);
758        new_pane.add(buttonPanel, BorderLayout.SOUTH);
759        container = new_pane;
760        return container;
761    }
762
763    public void setBlackBackground(boolean value) {
764        blackBackground = value;
765    }
766    
767    public boolean getBlackBackground() {
768        return blackBackground;
769    }
770
771    protected Component getScatterTabComponent() {
772       try {
773         scatterMaster = new XYDisplay("Scatter", RealType.XAxis, RealType.YAxis);
774       } catch (Exception e) {
775         e.printStackTrace();
776       }
777       return scatterMaster.getComponent();
778    }
779
780    public DisplayMaster makeImageDisplay(MapProjection mapProj, FlatField image, 
781                  FlatField mask_image, Range imageRange, ColorTable colorTable) 
782           throws VisADException, RemoteException {
783      MapProjectionDisplayJ3D mapProjDsp;
784      DisplayMaster dspMaster;
785
786      mapProjDsp = new MapProjectionDisplayJ3D(MapProjectionDisplay.MODE_2Din3D);
787      mapProjDsp.enableRubberBanding(false);
788      dspMaster = mapProjDsp;
789      mapProjDsp.setMapProjection(mapProj);
790
791      RealType imageRangeType =
792        (((FunctionType)image.getType()).getFlatRange().getRealComponents())[0];
793
794      boolean alphaflag = false;
795      HydraRGBDisplayable imageDsp = new HydraRGBDisplayable("image", imageRangeType, null, alphaflag, null);
796
797      imageDsp.setData(image);
798      dspMaster.addDisplayable(imageDsp);
799      addMapDisplayables(mapProjDsp);
800
801      if (mask_image != null) {
802        RGBDisplayable maskDsp = 
803            new ScatterDisplayable("mask", RealType.Generic, maskColorPalette, false);
804        maskDsp.setData(mask_image);
805        maskDsp.setRangeForColor(0, n_selectors-1);
806        dspMaster.addDisplayable(maskDsp);
807      }
808
809      dspMaster.draw();
810
811      ScalarMap colorMap = imageDsp.getColorMap();
812      colorMap.setRange(imageRange.getMin(), imageRange.getMax());
813      BaseColorControl clrCntrl = (BaseColorControl) colorMap.getControl();
814      float[][] ct = colorTable.getColorTable();
815
816      if ( !(alphaflag) && (ct.length == 4) ) {
817         float[][] new_ct = new float[3][];
818         new_ct[0] = ct[0];
819         new_ct[1] = ct[1];
820         new_ct[2] = ct[2];
821         ct = new_ct;
822      }
823
824      clrCntrl.setTable(ct);
825
826      return dspMaster;
827    }
828
829    public Range getImageRange(FlatField image)
830           throws VisADException, RemoteException {
831      DisplayConventions dc = getDisplayConventions();
832      Range[] range = GridUtil.fieldMinMax(image);
833      Range imageRange = range[0];
834      RealType imageRangeType =
835        (((FunctionType)image.getType()).getFlatRange().getRealComponents())[0];
836      String canonicalName = DataAlias.aliasToCanonical(imageRangeType.getName());
837      Range dfltRange = dc.getParamRange(canonicalName, null);
838
839      if (dfltRange == null) {
840        imageRange = range[0];
841      }
842      else if ((imageRange.getMax() - imageRange.getMin()) < (dfltRange.getMax() - dfltRange.getMin())) {
843      }
844      else {
845        imageRange = dfltRange;
846      }
847      return imageRange;
848    }
849
850    public ColorTable getColorTable(FlatField image) 
851           throws VisADException, RemoteException {
852      RealType imageRangeType =
853        (((FunctionType)image.getType()).getFlatRange().getRealComponents())[0];
854      DisplayConventions dc = getDisplayConventions();
855      return dc.getParamColorTable(imageRangeType.getName());
856    }
857
858
859    public MapProjection getDataProjection(FlatField image) 
860           throws VisADException, RemoteException {
861      MapProjection mp = null;
862      //- get MapProjection from incoming image.  If none, use default method
863      FunctionType fnc_type = (FunctionType) image.getType();
864      RealTupleType rtt = fnc_type.getDomain();
865      CoordinateSystem cs = rtt.getCoordinateSystem();
866      Set domainSet = image.getDomainSet();
867
868      if (cs instanceof visad.CachingCoordinateSystem) {
869        cs = ((visad.CachingCoordinateSystem)cs).getCachedCoordinateSystem();
870      }
871
872      if (cs instanceof MapProjection) {
873        return (MapProjection) cs;
874      }
875      else if (cs instanceof LongitudeLatitudeCoordinateSystem) {
876        Rectangle2D rect = MultiSpectralData.getLonLatBoundingBox(image);
877        try {
878          mp = new LambertAEA(rect);
879        } catch (Exception e) {
880          System.out.println(" getDataProjection"+e);
881        }
882        return mp;
883      }
884
885      float minLon = Float.NaN;
886      float minLat = Float.NaN;
887      float delLon = Float.NaN;
888      float delLat = Float.NaN;
889
890      if (domainSet instanceof LinearLatLonSet) {
891         MathType type0 = ((SetType)domainSet.getType()).getDomain().getComponent(0);
892         int latI = RealType.Latitude.equals(type0) ? 0 : 1;
893         int lonI = (latI == 1) ? 0 : 1;
894
895         float[] min = ((LinearLatLonSet)domainSet).getLow();
896         float[] max = ((LinearLatLonSet)domainSet).getHi();
897         minLon = min[lonI];
898         minLat = min[latI];
899         delLon = max[lonI] - min[lonI];
900         delLat = max[latI] - min[latI];
901
902         try {
903            mp = new TrivialMapProjection(RealTupleType.SpatialEarth2DTuple,
904                    new Rectangle2D.Float(minLon, minLat, delLon, delLat));
905         } catch (Exception e) {
906             logException("MultiSpectralControl.getDataProjection", e);
907         }
908
909         return mp;
910      }
911      else if (domainSet instanceof Gridded2DSet) {
912        rtt = ((SetType)domainSet.getType()).getDomain();
913        rtt = RealTupleType.SpatialEarth2DTuple;
914        if (!(rtt.equals(RealTupleType.SpatialEarth2DTuple) || rtt.equals(RealTupleType.LatitudeLongitudeTuple))) {
915          minLon = -180f;
916          minLat = -90f;
917          delLon = 360f;
918          delLat = 180f;
919        }
920        else {
921          int latI = rtt.equals(RealTupleType.SpatialEarth2DTuple) ? 1 : 0;
922          int lonI = (latI == 1) ? 0 : 1;
923
924          float[] min = ((Gridded2DSet)domainSet).getLow();
925          float[] max = ((Gridded2DSet)domainSet).getHi();
926          minLon = min[lonI];
927          minLat = min[latI];
928          delLon = max[lonI] - min[lonI];
929          delLat = max[latI] - min[latI];
930        }
931      }
932      
933      try {
934         mp = new TrivialMapProjection(RealTupleType.SpatialEarth2DTuple,
935                 new Rectangle2D.Float(minLon, minLat, delLon, delLat));
936      } catch (Exception e) {
937          logException("MultiSpectralControl.getDataProjection", e);
938      }
939
940      return mp;
941    }
942
943    public void addMapDisplayables(MapProjectionDisplayJ3D mapProjDsp) 
944           throws VisADException, RemoteException {
945        MapLines mapLines  = new MapLines("maplines");
946        URL      mapSource =
947        mapProjDsp.getClass().getResource("/auxdata/maps/OUTLSUPU");
948        try {
949            BaseMapAdapter mapAdapter = new BaseMapAdapter(mapSource);
950            mapLines.setMapLines(mapAdapter.getData());
951            mapLines.setColor(java.awt.Color.cyan);
952            mapProjDsp.addDisplayable(mapLines);
953        } catch (Exception excp) {
954            System.out.println("Can't open map file " + mapSource);
955            System.out.println(excp);
956        }
957                                                                                                                                                  
958        mapLines  = new MapLines("maplines");
959        mapSource =
960        mapProjDsp.getClass().getResource("/auxdata/maps/OUTLSUPW");
961        try {
962            BaseMapAdapter mapAdapter = new BaseMapAdapter(mapSource);
963            mapLines.setMapLines(mapAdapter.getData());
964            mapLines.setColor(java.awt.Color.cyan);
965            mapProjDsp.addDisplayable(mapLines);
966        } catch (Exception excp) {
967            System.out.println("Can't open map file " + mapSource);
968            System.out.println(excp);
969        }
970                                                                                                                                                  
971        mapLines  = new MapLines("maplines");
972        mapSource =
973        mapProjDsp.getClass().getResource("/auxdata/maps/OUTLHPOL");
974        try {
975            BaseMapAdapter mapAdapter = new BaseMapAdapter(mapSource);
976            mapLines.setMapLines(mapAdapter.getData());
977            mapLines.setColor(java.awt.Color.cyan);
978            mapProjDsp.addDisplayable(mapLines);
979        } catch (Exception excp) {
980            System.out.println("Can't open map file " + mapSource);
981            System.out.println(excp);
982        }
983    }
984
985    public boolean getSelectByCurve() {
986      return selectByCurve;
987    }
988
989    private FlatField resample(FlatField X_field, FlatField Y_field) throws VisADException, RemoteException {
990
991       RealTupleType X_domainRef = null;
992       RealTupleType Y_domainRef = null;
993       float[][] coords = null;
994       int[] indexes = null;
995       float[][] Yvalues = Y_field.getFloats(false);
996       float[][] Xsamples = ((SampledSet)X_field.getDomainSet()).getSamples(false);
997
998       CoordinateSystem X_cs = X_field.getDomainCoordinateSystem();
999       if (X_cs == null) {
1000          RealTupleType X_domain = ((FunctionType)X_field.getType()).getDomain();
1001       }
1002       else {
1003         X_domainRef = X_cs.getReference();
1004       }
1005
1006       CoordinateSystem Y_cs = Y_field.getDomainCoordinateSystem();
1007       if (Y_cs == null) {
1008          RealTupleType Y_domain = ((FunctionType)Y_field.getType()).getDomain();
1009       }
1010       else {
1011         Y_domainRef = Y_cs.getReference();
1012       }
1013
1014       if ( X_domainRef != null && Y_domainRef != null) {
1015         Xsamples = X_cs.toReference(Xsamples);
1016         coords = Y_cs.fromReference(Xsamples);
1017         indexes = ((SampledSet)Y_field.getDomainSet()).valueToIndex(coords);
1018       }
1019       else if ( X_domainRef == null && Y_domainRef != null ) {
1020         Xsamples = Y_cs.fromReference(Xsamples);
1021         indexes = ((SampledSet)Y_field.getDomainSet()).valueToIndex(Xsamples);
1022       }
1023       else if ( X_domainRef != null && Y_domainRef == null) {
1024         Xsamples = X_cs.toReference(Xsamples);
1025         Gridded2DSet domSet = (Gridded2DSet) Y_field.getDomainSet();
1026
1027         // TODO this is a hack for the longitude range problem
1028         float[] hi = domSet.getHi();
1029         if (hi[0] <= 180f) {
1030           for (int t=0; t<Xsamples[0].length; t++) {
1031             if (Xsamples[0][t] > 180f) Xsamples[0][t] -=360;
1032           }
1033         }
1034         
1035         indexes = ((SampledSet)Y_field.getDomainSet()).valueToIndex(Xsamples);
1036       }
1037       else if (X_domainRef == null && Y_domainRef == null) {
1038         Gridded2DSet domSet = (Gridded2DSet) Y_field.getDomainSet();
1039         indexes = domSet.valueToIndex(Xsamples);
1040       }
1041       
1042       float[][] new_values = new float[1][indexes.length];
1043       for (int k=0; k<indexes.length; k++) {
1044          new_values[0][k] = Float.NaN;
1045          if (indexes[k] >= 0) {
1046            new_values[0][k] = Yvalues[0][indexes[k]];
1047          }
1048       }
1049
1050       FunctionType ftype = new FunctionType(((FunctionType)X_field.getType()).getDomain(),
1051                ((FunctionType)Y_field.getType()).getRange());
1052       Y_field = new FlatField(ftype, X_field.getDomainSet());
1053       Y_field.setSamples(new_values);
1054
1055       return Y_field;
1056    }
1057
1058
1059    private class ScatterDisplayable extends RGBDisplayable {
1060       ScatterDisplayable(String name, RealType rgbRealType, float[][] colorPalette, boolean alphaflag) 
1061           throws VisADException, RemoteException {
1062         super(name, rgbRealType, colorPalette, alphaflag);
1063       }
1064    }
1065
1066    private class ImageControl extends DisplayControlImpl {
1067      HydraRGBDisplayable rgbDisp;
1068      DisplayConventions dc;
1069      ColorTableWidget ctw;
1070
1071      ImageControl(HydraRGBDisplayable rgbDisp, DisplayConventions dc) {
1072        super();
1073        this.rgbDisp = rgbDisp;
1074        this.dc = dc;
1075      }
1076
1077      @Override public void setRange(Range r) throws VisADException, RemoteException {
1078          if (r != null) {
1079              rgbDisp.setRangeForColor(r.getMin(), r.getMax());
1080          }
1081      }
1082
1083      @Override public DisplayConventions getDisplayConventions() {
1084        return dc;
1085      }
1086
1087      @Override public void setColorTable(ColorTable ct) {
1088        try {
1089          ctw.setColorTable(ct);
1090          ScalarMap colorMap = rgbDisp.getColorMap();
1091          BaseColorControl clrCntrl = (BaseColorControl) colorMap.getControl();
1092
1093          // Force incoming color dimension to that of the colorMap
1094          //
1095          int numComps = clrCntrl.getNumberOfComponents();
1096          float[][] clrTable = ct.getColorTable();
1097          float[][] newTable = null;
1098          if (numComps != clrTable.length) {
1099            if (numComps < clrTable.length) {
1100              newTable = new float[numComps][clrTable[0].length];
1101              for (int k=0; k<numComps; k++) {
1102                System.arraycopy(clrTable[k], 0, newTable[k], 0, newTable[0].length);
1103              }
1104            }
1105            else if (numComps > clrTable.length) {
1106              newTable = new float[numComps][clrTable[0].length];
1107              for (int k=0; k<clrTable.length; k++) {
1108                System.arraycopy(clrTable[k], 0, newTable[k], 0, newTable[0].length);
1109              }
1110              newTable[3] = new float[clrTable[0].length];
1111            }
1112          } else {
1113              newTable = new float[numComps][clrTable[0].length];
1114              for (int k = 0; k < clrTable.length; k++) {
1115                System.arraycopy(clrTable[k], 0, newTable[k], 0, newTable[0].length);
1116              }
1117          } 
1118          clrCntrl.setTable(newTable);
1119        } 
1120        catch (Exception e) {
1121          LogUtil.logException("Problem changing color table", e);
1122        }
1123      }
1124    }
1125
1126    private class ImageCurveSelector extends CellImpl implements DisplayListener {
1127      boolean init = false;
1128      CurveDrawer curveDraw;
1129      DisplayMaster dspMaster;
1130      Gridded2DSet domainSet;
1131      CoordinateSystem cs;
1132      int domainLen_0;
1133      int domainLen_1;
1134      ImageCurveSelector other;
1135      UnionSet last_uSet = null;
1136      boolean imageLatLon = false;
1137      boolean active = true;
1138      float maskVal;
1139      LineDrawing lastCurve;
1140      StatsTable myTable = null;
1141      int myTableIndex = 0;
1142
1143      ImageCurveSelector(CurveDrawer curveDraw, FlatField image, DisplayMaster master, Color color, float maskVal, StatsTable mst) 
1144           throws VisADException, RemoteException {
1145        this.curveDraw = curveDraw;
1146        this.maskVal = maskVal;
1147        this.myTable = mst;
1148        myTableIndex = 0;
1149        if (color == Color.magenta) myTableIndex = 1;
1150        if (color == Color.green) myTableIndex = 2;
1151        if (color == Color.blue) myTableIndex = 3;
1152        dspMaster = master;
1153        dspMaster.addDisplayListener(this);
1154        domainSet = (Gridded2DSet) image.getDomainSet();
1155        int[] lens = domainSet.getLengths();
1156        domainLen_0 = lens[0];
1157        domainLen_1 = lens[1];
1158        cs = ((FunctionType)image.getType()).getDomain().getCoordinateSystem();
1159        RealTupleType reference = null;
1160        if (cs != null) {
1161          reference = cs.getReference();
1162        }
1163        else {
1164          reference = ((SetType)domainSet.getType()).getDomain();
1165        }
1166        RealType[] rtypes = reference.getRealComponents();
1167        if (rtypes[0].equals(RealType.Latitude)) imageLatLon = true;
1168        lastCurve = new LineDrawing("lastCurve");
1169        lastCurve.setColor(color);
1170        lastCurve.setLineWidth(2);
1171        master.addDisplayable(lastCurve);
1172      }
1173
1174      @Override public void displayChanged(DisplayEvent de)
1175             throws VisADException, RemoteException {
1176         if ((de.getId() == DisplayEvent.MOUSE_RELEASED) && (active)) {
1177           UnionSet uSet = curveDraw.getCurves();
1178           if (uSet == last_uSet) return;
1179           SampledSet[] sets = uSet.getSets();
1180           int s_idx = sets.length-1;
1181           float[][] crv;
1182
1183           if (cs != null) {
1184             crv = sets[s_idx].getSamples();
1185             if (imageLatLon) {
1186                float[] tmp = crv[0];
1187                crv[0] = crv[1];
1188                crv[1] = tmp;
1189             }
1190             crv = cs.fromReference(crv);
1191             crv = domainSet.valueToGrid(crv);
1192           }
1193           else {
1194             crv = sets[s_idx].getSamples();
1195             crv = domainSet.valueToGrid(crv);
1196           }
1197
1198           float[][] onImage = new float[2][crv[0].length];
1199           int cnt = 0;
1200           for (int i=0; i<crv[0].length; i++) {
1201             if ( ((crv[0][i] >= 0)&&(crv[0][i] <= domainLen_0)) &&
1202                  ((crv[1][i] >= 0)&&(crv[1][i] <= domainLen_1)) ) {
1203               onImage[0][cnt] = crv[0][i];
1204               onImage[1][cnt] = crv[1][i];
1205               cnt++;
1206             }
1207           }
1208           uSet = new UnionSet(new SampledSet[] {sets[s_idx]});
1209           last_uSet = uSet;
1210           lastCurve.setData(last_uSet);
1211           curveDraw.setCurves(uSet);
1212           other.updateCurve(sets[s_idx]);
1213
1214           if (cnt == 0) {
1215             return;
1216           }
1217
1218           float[][] tmp = new float[2][cnt];
1219           System.arraycopy(onImage[0], 0, tmp[0], 0, cnt);
1220           System.arraycopy(onImage[1], 0, tmp[1], 0, cnt);
1221           onImage = tmp;
1222
1223           float[] minmaxvals = minmax(onImage[0]);
1224           int low_0 = Math.round(minmaxvals[0]);
1225           int hi_0 = Math.round(minmaxvals[1]);
1226           minmaxvals = minmax(onImage[1]);
1227           int low_1 = Math.round(minmaxvals[0]);
1228           int hi_1 = Math.round(minmaxvals[1]);
1229
1230           int len_0 = (hi_0 - low_0) + 1;
1231           int len_1 = (hi_1 - low_1) + 1;
1232           int len = len_0*len_1;
1233
1234           tmp = new float[3][len];
1235           int[] tmpsel = new int[len];
1236
1237           int num_inside = 0;
1238           for (int j=0; j<len_1; j++) {
1239             for (int i=0; i<len_0; i++) {
1240               int idx = (j+low_1)*domainLen_0 + (i+low_0);
1241               float x = (float) (i + low_0);
1242               float y = (float) (j + low_1);
1243               if (DelaunayCustom.inside(crv, x, y)) {
1244                 tmp[0][num_inside] = scatterFieldRange[0][idx];
1245                 tmp[1][num_inside] = scatterFieldRange[1][idx];
1246                 tmp[2][num_inside] = maskVal;
1247                 tmpsel[num_inside] = idx;
1248                 num_inside++;
1249               }
1250             }
1251           }
1252           len = num_inside;
1253           float[][] markScatter = new float[3][len];
1254           System.arraycopy(tmp[0], 0, markScatter[0], 0, len);
1255           System.arraycopy(tmp[1], 0, markScatter[1], 0, len);
1256           System.arraycopy(tmp[2], 0, markScatter[2], 0, len);
1257
1258
1259           int last_len = 0;
1260           float[][] lastMark = ((FlatField)scatterMarkDsp.getData()).getFloats(false);
1261           tmp = new float[3][lastMark[0].length];
1262           for (int k=0; k<lastMark[0].length; k++) {
1263             if (lastMark[2][k] != maskVal) {
1264               tmp[0][last_len] = lastMark[0][k];
1265               tmp[1][last_len] = lastMark[1][k];
1266               tmp[2][last_len] = lastMark[2][k];
1267               last_len++;
1268             }
1269           }
1270
1271           float[][] newMarkScatter = new float[3][len+last_len];
1272           System.arraycopy(tmp[0], 0, newMarkScatter[0], 0, last_len);
1273           System.arraycopy(tmp[1], 0, newMarkScatter[1], 0, last_len);
1274           System.arraycopy(tmp[2], 0, newMarkScatter[2], 0, last_len);
1275           System.arraycopy(markScatter[0], 0, newMarkScatter[0], last_len, len);
1276           System.arraycopy(markScatter[1], 0, newMarkScatter[1], last_len, len);
1277           System.arraycopy(markScatter[2], 0, newMarkScatter[2], last_len, len);
1278
1279           Integer1DSet dset = new Integer1DSet(len+last_len);
1280           FlatField scatterFieldMark = new FlatField(
1281             new FunctionType(RealType.Generic,
1282                new RealTupleType(RealType.XAxis, RealType.YAxis, RealType.getRealType("mask"))), dset);
1283
1284           scatterFieldMark.setSamples(newMarkScatter, false);
1285           scatterMarkDsp.setData(scatterFieldMark);
1286
1287           if (myTable != null) {
1288             int[] selected = new int[len];
1289             System.arraycopy(tmpsel, 0, selected, 0, len);
1290             total_area = JPythonMethods.computeSum(Area_field, selected);
1291             myTable.setPoints(markScatter, len, myTableIndex, total_area);  
1292           }
1293
1294         }
1295      }
1296
1297      public void setActive(boolean active) {
1298        this.active = active;
1299      }
1300
1301      public void reset() throws VisADException, RemoteException {
1302
1303        float[][] lastMark = ((FlatField)scatterMarkDsp.getData()).getFloats(false);
1304        float[][] tmp = new float[3][lastMark[0].length];
1305        int cnt = 0;
1306        for (int k=0; k<lastMark[0].length; k++) {
1307          if (lastMark[2][k] != maskVal) {
1308             tmp[0][cnt] = lastMark[0][k];
1309             tmp[1][cnt] = lastMark[1][k];
1310             tmp[2][cnt] = lastMark[2][k];
1311             cnt++;
1312          }
1313        }
1314
1315        RealTupleType type = ((SetType)curveDraw.getCurves().getType()).getDomain();
1316        curveDraw.setCurves(new UnionSet(new Gridded2DSet[]{
1317            new Gridded2DSet(type, new float[][] {
1318            { 0.0f }, { 0.0f }}, 1) }));
1319        
1320        lastCurve.setData(new UnionSet(new Gridded2DSet[]{
1321            new Gridded2DSet(type, new float[][] {
1322            { 0.0f }, { 0.0f }}, 1) }));
1323
1324        FlatField scatterFieldMark = null;
1325        if (cnt == 0) {
1326        Integer1DSet dset = new Integer1DSet(2);
1327        scatterFieldMark = new FlatField(
1328        new FunctionType(RealType.Generic,
1329              new RealTupleType(RealType.XAxis, RealType.YAxis, RealType.getRealType("mask"))), dset);
1330        float[][] markScatter = new float[3][2]; 
1331        for (int k=0; k<2; k++) {
1332          markScatter[0][k] = scatterFieldRange[0][k];
1333          markScatter[1][k] = scatterFieldRange[1][k];
1334          markScatter[2][k] = 0;
1335        }
1336        scatterFieldMark.setSamples(markScatter, false);
1337        }
1338        else {
1339          Integer1DSet dset = new Integer1DSet(cnt);
1340          scatterFieldMark = new FlatField(
1341          new FunctionType(RealType.Generic,
1342                new RealTupleType(RealType.XAxis, RealType.YAxis, RealType.getRealType("mask"))), dset);
1343          float[][] markScatter = new float[3][cnt];
1344          for (int k=0; k<cnt; k++) {
1345            markScatter[0][k] = tmp[0][k];
1346            markScatter[1][k] = tmp[1][k];
1347            markScatter[2][k] = tmp[2][k];
1348          }
1349          scatterFieldMark.setSamples(markScatter, false);
1350        }
1351
1352        scatterMarkDsp.setData(scatterFieldMark);
1353      }
1354
1355      public void updateCurve(SampledSet set) throws VisADException, RemoteException {
1356        last_uSet = new UnionSet(new SampledSet[] {set});
1357        curveDraw.setCurves(last_uSet);
1358        lastCurve.setData(last_uSet);
1359      }
1360
1361      public void setOther(ImageCurveSelector other) {
1362        this.other = other;
1363      }
1364
1365      @Override public void doAction()
1366           throws VisADException, RemoteException {
1367        if (!init) {
1368          init = true;
1369          return;
1370        }
1371      }
1372
1373      public void setVisible(boolean visible) throws VisADException, RemoteException {
1374        curveDraw.setVisible(visible);
1375      }
1376
1377    }
1378
1379    private class ImageBoxSelector extends CellImpl {
1380        boolean init = false;
1381        boolean active = true;
1382        SubsetRubberBandBox subsetBox;
1383        Set imageDomain;
1384        int domainLen_0;
1385        LineDrawing lastBox;
1386        ImageBoxSelector other;
1387        float maskVal;
1388        boolean earthCoordDomain = false;
1389        StatsTable myTable = null;
1390        int myTableIndex = 0;
1391
1392        ImageBoxSelector(SubsetRubberBandBox subsetBox, Set imageDomain, DisplayMaster master, Color color, float maskVal, StatsTable mst)
1393            throws VisADException, RemoteException {
1394            super();
1395            this.myTable = mst;
1396            myTableIndex = 0;
1397            if (color == Color.magenta) myTableIndex = 1;
1398            if (color == Color.green) myTableIndex = 2;
1399            if (color == Color.blue) myTableIndex = 3;
1400            this.subsetBox = subsetBox;
1401            this.imageDomain = imageDomain;
1402            int[] lens = ((Gridded2DSet)imageDomain).getLengths();
1403            this.maskVal = maskVal;
1404            domainLen_0 = lens[0];
1405            lastBox = new LineDrawing("last_box");
1406            lastBox.setColor(color);
1407            master.addDisplayable(lastBox);
1408            subsetBox.addAction(this);
1409            master.addDisplayable(subsetBox);
1410            RealTupleType rtt = ((SetType)imageDomain.getType()).getDomain();
1411            if (rtt.equals(RealTupleType.SpatialEarth2DTuple) ||
1412                rtt.equals(RealTupleType.LatitudeLongitudeTuple)) {
1413                earthCoordDomain = true;
1414            }
1415        }
1416
1417        @Override public void doAction()
1418            throws VisADException, RemoteException
1419        {
1420            if (!init) {
1421                init = true;
1422                return;
1423            }
1424
1425            if (!active) {
1426                return;
1427            }
1428
1429            Gridded2DSet set = subsetBox.getBounds();
1430            float[][] corners = set.getSamples(false);
1431            float[][] coords = corners;
1432            if (corners == null) return;
1433
1434            if ((imageDomain instanceof Linear2DSet) || !earthCoordDomain) {
1435                coords = ((Gridded2DSet)imageDomain).valueToGrid(corners);
1436            }
1437
1438            float[] coords_0 = coords[0];
1439            float[] coords_1 = coords[1];
1440
1441            int low_0 = Math.round(Math.min(coords_0[0], coords_0[1]));
1442            int low_1 = Math.round(Math.min(coords_1[0], coords_1[1]));
1443            int hi_0  = Math.round(Math.max(coords_0[0], coords_0[1]));
1444            int hi_1  = Math.round(Math.max(coords_1[0], coords_1[1]));
1445
1446            int len_0 = (hi_0 - low_0) + 1;
1447            int len_1 = (hi_1 - low_1) + 1;
1448            int len = len_0*len_1;
1449
1450            float[][] markScatter = new float[3][len];
1451            int[] selected = new int[len];
1452
1453            for (int j=0; j<len_1; j++) {
1454                for (int i=0; i<len_0; i++) {
1455                    int idx = (j+low_1)*domainLen_0 + (i+low_0);
1456                    int k = j*len_0 + i;
1457                    markScatter[0][k] = scatterFieldRange[0][idx];
1458                    markScatter[1][k] = scatterFieldRange[1][idx];
1459                    markScatter[2][k] = maskVal;
1460                    selected[k] = idx;
1461                }
1462            }
1463
1464            int last_len = 0;
1465            float[][] lastMark = ((FlatField)scatterMarkDsp.getData()).getFloats(false);
1466            float[][] tmp = new float[3][lastMark[0].length];
1467            for (int k=0; k<lastMark[0].length; k++) {
1468                if (lastMark[2][k] != maskVal) {
1469                    tmp[0][last_len] = lastMark[0][k];
1470                    tmp[1][last_len] = lastMark[1][k];
1471                    tmp[2][last_len] = lastMark[2][k];
1472                    last_len++;
1473                }
1474            }
1475
1476            float[][] newMarkScatter = new float[3][len+last_len];
1477            System.arraycopy(tmp[0], 0, newMarkScatter[0], 0, last_len);
1478            System.arraycopy(tmp[1], 0, newMarkScatter[1], 0, last_len);
1479            System.arraycopy(tmp[2], 0, newMarkScatter[2], 0, last_len);
1480            System.arraycopy(markScatter[0], 0, newMarkScatter[0], last_len, len);
1481            System.arraycopy(markScatter[1], 0, newMarkScatter[1], last_len, len);
1482            System.arraycopy(markScatter[2], 0, newMarkScatter[2], last_len, len);
1483
1484            Integer1DSet dset = new Integer1DSet(len+last_len);
1485            FlatField scatterFieldMark = new FlatField(
1486                new FunctionType(RealType.Generic,
1487                    new RealTupleType(RealType.XAxis, RealType.YAxis, RealType.getRealType("mask"))), dset);
1488
1489            scatterFieldMark.setSamples(newMarkScatter, false);
1490            scatterMarkDsp.setData(scatterFieldMark);
1491
1492            if (myTable != null) {
1493                total_area = JPythonMethods.computeSum(Area_field, selected);
1494                myTable.setPoints(markScatter, len, myTableIndex, total_area);
1495            }
1496
1497            updateBox();
1498        }
1499
1500        public void setActive(boolean active) {
1501            this.active = active;
1502        }
1503
1504        public void setVisible(boolean visible) throws VisADException, RemoteException {
1505            subsetBox.setVisible(visible);
1506            if (visible) {
1507                lastBox.setVisible(visible);
1508            }
1509        }
1510
1511        public void reset() throws VisADException, RemoteException {
1512            Gridded2DSet set2D =
1513                new Gridded2DSet(RealTupleType.SpatialCartesian2DTuple,
1514                    new float[][] {{0},{0}}, 1);
1515            lastBox.setVisible(false);
1516            lastBox.setData(set2D);
1517
1518            float[][] lastMark = ((FlatField)scatterMarkDsp.getData()).getFloats(false);
1519            float[][] tmp = new float[3][lastMark[0].length];
1520            int cnt = 0;
1521            for (int k=0; k<lastMark[0].length; k++) {
1522                if (lastMark[2][k] != maskVal) {
1523                    tmp[0][cnt] = lastMark[0][k];
1524                    tmp[1][cnt] = lastMark[1][k];
1525                    tmp[2][cnt] = lastMark[2][k];
1526                    cnt++;
1527                }
1528            }
1529
1530            FlatField scatterFieldMark;
1531            if (cnt == 2) {
1532                Integer1DSet dset = new Integer1DSet(2);
1533                scatterFieldMark = new FlatField(
1534                    new FunctionType(RealType.Generic,
1535                        new RealTupleType(RealType.XAxis, RealType.YAxis, RealType.getRealType("mask"))), dset);
1536                float[][] markScatter = new float[3][2];
1537                for (int k=0; k<2; k++) {
1538                    markScatter[0][k] = scatterFieldRange[0][k];
1539                    markScatter[1][k] = scatterFieldRange[1][k];
1540                    markScatter[2][k] = 0;
1541                }
1542                scatterFieldMark.setSamples(markScatter, false);
1543            }
1544            else {
1545                Integer1DSet dset = new Integer1DSet(cnt);
1546                scatterFieldMark = new FlatField(
1547                    new FunctionType(RealType.Generic,
1548                        new RealTupleType(RealType.XAxis, RealType.YAxis, RealType.getRealType("mask"))), dset);
1549                float[][] markScatter = new float[3][cnt];
1550                for (int k=0; k<cnt; k++) {
1551                    markScatter[0][k] = tmp[0][k];
1552                    markScatter[1][k] = tmp[1][k];
1553                    markScatter[2][k] = tmp[2][k];
1554                }
1555                scatterFieldMark.setSamples(markScatter, false);
1556            }
1557
1558            scatterMarkDsp.setData(scatterFieldMark);
1559        }
1560
1561        public void setOther(ImageBoxSelector other) {
1562            this.other = other;
1563        }
1564
1565        public void updateBox() throws VisADException, RemoteException {
1566            Gridded3DSet set3D = subsetBox.getLastBox();
1567            float[][] samples = set3D.getSamples(false);
1568            Gridded2DSet set2D =
1569                new Gridded2DSet(RealTupleType.SpatialCartesian2DTuple,
1570                    new float[][] {samples[0], samples[1]}, samples[0].length);
1571            lastBox.setData(set2D);
1572            other.updateBox(set2D);
1573        }
1574
1575        public void updateBox(Gridded2DSet set2D) throws VisADException, RemoteException {
1576            lastBox.setData(set2D);
1577        }
1578
1579    }
1580
1581    private class ScatterBoxSelector extends CellImpl {
1582       boolean init = false;
1583       double[] x_coords = new double[2];
1584       double[] y_coords = new double[2];
1585       RubberBandBox rbb;
1586       LineDrawing selectBox;
1587       boolean active = true;
1588       float maskVal = 0;
1589
1590       ScatterBoxSelector(DisplayMaster master, Color color, float maskVal) throws VisADException, RemoteException {
1591         selectBox = new LineDrawing("select");
1592         selectBox.setColor(color);
1593
1594         rbb = new RubberBandBox(RealType.XAxis, RealType.YAxis, 1);
1595         rbb.setColor(color);
1596         rbb.addAction(this);
1597
1598         master.addDisplayable(rbb);
1599         master.addDisplayable(selectBox);
1600         this.maskVal = maskVal;
1601       }
1602
1603
1604       @Override public void doAction() throws VisADException, RemoteException {
1605         if (!init) {
1606           init = true;
1607           return;
1608         }
1609
1610         if (!active) {
1611           return;
1612         }
1613
1614         Gridded2DSet set = rbb.getBounds();
1615         float[] low = set.getLow();
1616         float[] hi = set.getHi();
1617         x_coords[0] = low[0];
1618         x_coords[1] = hi[0];
1619         y_coords[0] = low[1];
1620         y_coords[1] = hi[1];
1621                                                                                                                                                  
1622         SampledSet[] sets = new SampledSet[4];
1623         sets[0] = new Gridded2DSet(RealTupleType.SpatialCartesian2DTuple, new float[][] {{low[0], hi[0]}, {low[1], low[1]}}, 2);
1624         sets[1] = new Gridded2DSet(RealTupleType.SpatialCartesian2DTuple, new float[][] {{hi[0], hi[0]}, {low[1], hi[1]}}, 2);
1625         sets[2] = new Gridded2DSet(RealTupleType.SpatialCartesian2DTuple, new float[][] {{hi[0], low[0]}, {hi[1], hi[1]}}, 2);
1626         sets[3] = new Gridded2DSet(RealTupleType.SpatialCartesian2DTuple, new float[][] {{low[0], low[0]}, {hi[1], low[1]}}, 2);
1627         UnionSet uset = new UnionSet(sets);
1628         selectBox.setData(uset);
1629
1630         try {
1631           histoField.markMaskFieldByRange(x_coords, y_coords, maskVal);
1632         } catch (Exception e) {
1633           e.printStackTrace();
1634         }
1635       }
1636
1637       public void setVisible(boolean visible) throws VisADException, RemoteException {
1638         rbb.setVisible(visible);
1639         if (visible) {
1640           selectBox.setVisible(visible);
1641         }
1642       }
1643
1644       public void setActive(boolean active) {
1645         this.active = active;
1646       }
1647
1648       public void reset() throws Exception {
1649         if (!active) return;
1650         selectBox.setVisible(false);
1651         selectBox.setData(new Gridded2DSet(RealTupleType.SpatialCartesian2DTuple, new float[][] {{0f, 0f}, {0f, 0f}}, 2));
1652         histoField.resetMaskField(maskVal);
1653       }
1654   }
1655
1656   private class ScatterCurveSelector extends CellImpl implements DisplayListener {
1657     CurveDrawer curveDraw;
1658     boolean init = false;
1659     UnionSet last_uSet = null;
1660     boolean active = true;
1661     float maskVal = 0;
1662     LineDrawing selectCurve;
1663
1664     ScatterCurveSelector(DisplayMaster master, Color color, float maskVal) throws VisADException, RemoteException {
1665       curveDraw = new CurveDrawer(RealType.XAxis, RealType.YAxis, 1);
1666       curveDraw.setColor(color);
1667       curveDraw.setLineWidth(2);
1668       curveDraw.setData(new UnionSet(new Gridded2DSet[]{
1669            new Gridded2DSet(RealTupleType.SpatialCartesian2DTuple, new float[][] {
1670            { scatterFieldRange[0][0] }, { scatterFieldRange[1][0]}
1671        }, 1) }));
1672
1673       selectCurve = new LineDrawing("select");
1674       selectCurve.setColor(color);
1675       selectCurve.setLineWidth(2);
1676       master.addDisplayable(curveDraw);
1677       master.addDisplayable(selectCurve);
1678       this.maskVal = maskVal;
1679
1680       curveDraw.addAction(this);
1681       master.addDisplayListener(this);
1682     }
1683
1684     @Override public void displayChanged(DisplayEvent de)
1685            throws VisADException, RemoteException {
1686       if ((de.getId() == DisplayEvent.MOUSE_RELEASED) && (active)) {
1687         UnionSet uSet = curveDraw.getCurves();
1688         if (uSet == last_uSet) return;
1689         SampledSet[] sets = uSet.getSets();
1690         int s_idx = sets.length-1;
1691         float[][] crv;
1692                                                                                                                                                  
1693         crv = sets[s_idx].getSamples();
1694         last_uSet = new UnionSet(new SampledSet[] {sets[s_idx]});
1695         curveDraw.setCurves(last_uSet);
1696         selectCurve.setData(last_uSet);
1697
1698         try {
1699           histoField.clearMaskField(maskVal);
1700           histoField.markMaskFieldByCurve(crv, maskVal);
1701         } catch (Exception e) {
1702           e.printStackTrace();
1703         }
1704       }
1705     }
1706
1707     @Override public  void doAction() throws VisADException, RemoteException {
1708       if (!init) {
1709         init = true;
1710         return;
1711       }
1712     }
1713
1714     public void setVisible(boolean visible) throws VisADException, RemoteException {
1715       curveDraw.setVisible(visible);
1716     }
1717
1718     public void setActive(boolean active) {
1719       this.active = active;
1720     }
1721
1722     public void reset() throws Exception {
1723       if (!active) return;
1724       curveDraw.setData(new UnionSet(new Gridded2DSet[]{
1725            new Gridded2DSet(RealTupleType.SpatialCartesian2DTuple, new float[][] {
1726            { scatterFieldRange[0][0] }, { scatterFieldRange[1][0]}
1727        }, 1) }));
1728       selectCurve.setData(new UnionSet(new Gridded2DSet[]{
1729            new Gridded2DSet(RealTupleType.SpatialCartesian2DTuple, new float[][] {
1730            { scatterFieldRange[0][0] }, { scatterFieldRange[1][0]}
1731        }, 1) }));
1732       histoField.resetMaskField(maskVal);
1733     }
1734   }
1735
1736    private class BoxCurveSwitch implements ActionListener {
1737
1738        public BoxCurveSwitch() {
1739        }
1740
1741        @Override public void actionPerformed(ActionEvent ae) {
1742            String cmd = ae.getActionCommand();
1743            try {
1744                if (cmd.equals("Box")) {
1745                    selectByCurve = false;
1746                } else if (cmd.equals("Curve")) {
1747                    selectByCurve = true;
1748                }
1749            }
1750            catch (Exception e) {
1751                e.printStackTrace();
1752            }
1753        }
1754    }
1755
1756    public static float[] minmax(float[] values) {
1757        float min =  Float.MAX_VALUE;
1758        float max = -Float.MAX_VALUE;
1759        for (int k = 0; k < values.length; k++) {
1760            float val = values[k];
1761            if ((val == val) && (val < Float.POSITIVE_INFINITY) && (val > Float.NEGATIVE_INFINITY)) {
1762                if (val < min) min = val;
1763                if (val > max) max = val;
1764            }
1765        }
1766        return new float[] {min, max};
1767    }
1768
1769    public boolean getIsLatLon(FlatField field) throws VisADException, RemoteException {
1770        boolean isLL = false;
1771        FunctionType fnc_type = (FunctionType) field.getType();
1772        RealTupleType rtt = fnc_type.getDomain();
1773        if (rtt.equals(RealTupleType.LatitudeLongitudeTuple)) {
1774            isLL = true;
1775        } else if (!rtt.equals(RealTupleType.SpatialEarth2DTuple)) {
1776            rtt = fnc_type.getDomain().getCoordinateSystem().getReference();
1777            if ( rtt.equals(RealTupleType.LatitudeLongitudeTuple)) {
1778                isLL = true;
1779            }
1780        }
1781        return isLL;
1782    }
1783}