001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2015
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
029package edu.wisc.ssec.mcidasv.display.hydra;
030
031import java.awt.Color;
032import java.awt.Component;
033import java.awt.event.ActionEvent;
034import java.awt.event.ActionListener;
035import java.rmi.RemoteException;
036import java.util.ArrayList;
037import java.util.Enumeration;
038import java.util.HashMap;
039import java.util.Hashtable;
040import java.util.List;
041import java.util.Map;
042
043import javax.swing.JComboBox;
044
045import org.slf4j.Logger;
046import org.slf4j.LoggerFactory;
047
048import visad.CellImpl;
049import visad.ConstantMap;
050import visad.DataReference;
051import visad.DataReferenceImpl;
052import visad.Display;
053import visad.DisplayEvent;
054import visad.DisplayListener;
055import visad.FlatField;
056import visad.FunctionType;
057import visad.Gridded1DSet;
058import visad.Gridded2DSet;
059import visad.LocalDisplay;
060import visad.Real;
061import visad.RealTuple;
062import visad.RealTupleType;
063import visad.RealType;
064import visad.ScalarMap;
065import visad.VisADException;
066import visad.bom.RubberBandBoxRendererJ3D;
067
068import ucar.unidata.data.DirectDataChoice;
069import ucar.unidata.idv.ViewManager;
070import ucar.unidata.util.LogUtil;
071import ucar.visad.display.DisplayableData;
072import ucar.visad.display.XYDisplay;
073
074import edu.wisc.ssec.mcidasv.control.HydraCombo;
075import edu.wisc.ssec.mcidasv.control.HydraControl;
076import edu.wisc.ssec.mcidasv.control.LinearCombo;
077import edu.wisc.ssec.mcidasv.control.MultiSpectralControl;
078import edu.wisc.ssec.mcidasv.data.HydraDataSource;
079import edu.wisc.ssec.mcidasv.data.hydra.GrabLineRendererJ3D;
080import edu.wisc.ssec.mcidasv.data.hydra.HydraRGBDisplayable;
081import edu.wisc.ssec.mcidasv.data.hydra.MultiDimensionSubset;
082import edu.wisc.ssec.mcidasv.data.hydra.MultiSpectralData;
083import edu.wisc.ssec.mcidasv.data.hydra.MultiSpectralDataSource;
084import edu.wisc.ssec.mcidasv.data.hydra.SuomiNPPDataSource;
085
086public class MultiSpectralDisplay implements DisplayListener {
087
088    private static final Logger logger = LoggerFactory.getLogger(MultiSpectralDisplay.class);
089    
090    private static final String DISP_NAME = "Spectrum";
091    private static int cnt = 1;
092
093    private DirectDataChoice dataChoice;
094
095    private ViewManager viewManager;
096
097    private float[] initialRangeX;
098    private float[] initialRangeY = { 180f, 320f };
099
100    private RealType domainType;
101    private RealType rangeType;
102    private RealType uniqueRangeType;
103
104    private ScalarMap xmap;
105    private ScalarMap ymap;
106
107    private LocalDisplay display;
108
109    private FlatField image;
110
111    private FlatField spectrum = null;
112
113    private boolean imageExpired = true;
114
115    private MultiSpectralData data;
116
117    private float waveNumber;
118
119    private List<DataReference> displayedThings = new ArrayList<DataReference>();
120    private HashMap<String, DataReference> idToRef = new HashMap<String, DataReference>();
121    private HashMap<DataReference, ConstantMap[]> colorMaps = 
122        new HashMap<DataReference, ConstantMap[]>();
123
124    private HydraControl displayControl;
125
126    private DisplayableData imageDisplay = null;
127
128    private XYDisplay master;
129
130    private Gridded1DSet domainSet;
131
132    private JComboBox bandSelectComboBox = null;
133
134    public MultiSpectralDisplay(final HydraControl control) 
135        throws VisADException, RemoteException 
136    {
137        displayControl = control;
138        dataChoice = (DirectDataChoice)displayControl.getDataChoice();
139
140        init();
141    }
142
143    public MultiSpectralDisplay(final DirectDataChoice dataChoice) 
144        throws VisADException, RemoteException 
145    {
146        this.dataChoice = dataChoice;
147        init();
148    }
149
150    // TODO: generalize this so that you can grab the image data for any
151    // channel
152    public FlatField getImageData() {
153        try {
154            if ((imageExpired) || (image == null)) {
155                imageExpired = false;
156
157              MultiDimensionSubset select = null;
158              Hashtable table = dataChoice.getProperties();
159              Enumeration keys = table.keys();
160              while (keys.hasMoreElements()) {
161                Object key = keys.nextElement();
162                if (key instanceof MultiDimensionSubset) {
163                  select = (MultiDimensionSubset) table.get(key);
164                }
165              }
166              HashMap subset = select.getSubset();
167              image = data.getImage(waveNumber, subset);
168              image = changeRangeType(image, uniqueRangeType);
169            }
170        } catch (Exception e) {
171            LogUtil.logException("MultiSpectralDisplay.getImageData", e);
172        }
173
174        return image;
175    }
176
177    public FlatField getImageDataFrom(final float channel) {
178        FlatField imageData = null;
179        try {
180            MultiDimensionSubset select = null;
181            Hashtable table = dataChoice.getProperties();
182            Enumeration keys = table.keys();
183            while (keys.hasMoreElements()) {
184              Object key = keys.nextElement();
185              if (key instanceof MultiDimensionSubset) {
186                select = (MultiDimensionSubset) table.get(key);
187              }
188            }
189            HashMap subset = select.getSubset();
190            imageData = data.getImage(channel, subset);
191            uniqueRangeType = RealType.getRealType(rangeType.getName()+"_"+cnt++);
192            imageData = changeRangeType(imageData, uniqueRangeType);
193        } catch (Exception e) {
194            LogUtil.logException("MultiSpectralDisplay.getImageDataFrom", e);
195        }
196        return imageData;
197    }
198
199    private FlatField changeRangeType(FlatField image, RealType newRangeType) throws VisADException, RemoteException {
200      FunctionType ftype = (FunctionType)image.getType();
201      FlatField new_image = new FlatField(
202         new FunctionType(ftype.getDomain(), newRangeType), image.getDomainSet());
203      new_image.setSamples(image.getFloats(false), false);
204      return new_image;
205    }
206   
207
208    public LocalDisplay getDisplay() {
209        return display;
210    }
211
212    public Component getDisplayComponent() {
213      return master.getDisplayComponent();
214    }
215
216    public RealType getDomainType() {
217        return domainType;
218    }
219
220    public RealType getRangeType() {
221        return rangeType;
222    }
223
224    public ViewManager getViewManager() {
225        return viewManager;
226    }
227
228    public MultiSpectralData getMultiSpectralData() {
229        return data;
230    }
231
232    public Gridded1DSet getDomainSet() {
233        return domainSet;
234    }
235
236    private void init() throws VisADException, RemoteException {
237        
238        HydraDataSource source = 
239              (HydraDataSource) dataChoice.getDataSource();
240
241        // TODO revisit this, may want to move method up to base class HydraDataSource
242        if (source instanceof SuomiNPPDataSource) {
243                data = ((SuomiNPPDataSource) source).getMultiSpectralData(dataChoice);
244        }
245        
246        if (source instanceof MultiSpectralDataSource) {
247                data = ((MultiSpectralDataSource) source).getMultiSpectralData(dataChoice);
248        }
249
250        waveNumber = data.init_wavenumber;
251
252        try {
253            spectrum = data.getSpectrum(new int[] { 1, 1 });
254        } catch (Exception e) {
255            LogUtil.logException("MultiSpectralDisplay.init", e);
256        }
257
258        domainSet = (Gridded1DSet)spectrum.getDomainSet();
259        initialRangeX = getXRange(domainSet);
260        initialRangeY = data.getDataRange();
261
262        domainType = getDomainType(spectrum);
263        rangeType = getRangeType(spectrum);
264
265        master = new XYDisplay(DISP_NAME, domainType, rangeType);
266
267        setDisplayMasterAttributes(master);
268
269        // set up the x- and y-axis
270        xmap = new ScalarMap(domainType, Display.XAxis);
271        ymap = new ScalarMap(rangeType, Display.YAxis);
272
273        xmap.setRange(initialRangeX[0], initialRangeX[1]);
274        ymap.setRange(initialRangeY[0], initialRangeY[1]);
275
276        display = master.getDisplay();
277        display.addMap(xmap);
278        display.addMap(ymap);
279        display.addDisplayListener(this);
280
281        new RubberBandBox(this, xmap, ymap);
282
283        if (displayControl == null) { //- add in a ref for the default spectrum, ie no DisplayControl
284            DataReferenceImpl spectrumRef = new DataReferenceImpl(hashCode() + "_spectrumRef");
285            spectrumRef.setData(spectrum);
286            addRef(spectrumRef, Color.WHITE);
287        }
288
289        if (data.hasBandNames()) {
290            bandSelectComboBox = new JComboBox(data.getBandNames().toArray());
291            bandSelectComboBox.setSelectedItem(data.init_bandName);
292            bandSelectComboBox.addActionListener(new ActionListener() {
293                public void actionPerformed(ActionEvent e) {
294                    String bandName = (String)bandSelectComboBox.getSelectedItem();
295                    if (bandName == null)
296                        return;
297
298                    HashMap<String, Float> bandMap = data.getBandNameMap();
299                    if (bandMap == null)
300                        return;
301
302                    if (!bandMap.containsKey(bandName))
303                        return;
304
305                    setWaveNumber(bandMap.get(bandName));
306                }
307            });
308        }
309    }
310
311    public JComboBox getBandSelectComboBox() {
312      return bandSelectComboBox;
313    }
314
315    // TODO: HACK!!
316    public void setDisplayControl(final HydraControl control) {
317        displayControl = control;
318    }
319
320    public void displayChanged(final DisplayEvent e) throws VisADException, RemoteException {
321        // TODO: write a method like isChannelUpdate(EVENT_ID)? or maybe just 
322        // deal with a super long if-statement and put an "OR MOUSE_RELEASED" 
323        // up here?
324        if (e.getId() == DisplayEvent.MOUSE_RELEASED_CENTER) {
325            float val = (float)display.getDisplayRenderer().getDirectAxisValue(domainType);
326            setWaveNumber(val);
327            if (displayControl != null)
328                displayControl.handleChannelChange(val);
329        }
330        else if (e.getId() == DisplayEvent.MOUSE_PRESSED_LEFT) {
331            if (e.getInputEvent().isControlDown()) {
332                xmap.setRange(initialRangeX[0], initialRangeX[1]);
333                ymap.setRange(initialRangeY[0], initialRangeY[1]);
334            }
335        }
336        else if (e.getId() == DisplayEvent.MOUSE_RELEASED) {
337            float val = getSelectorValue(channelSelector);
338            if (val != waveNumber) {
339                // TODO: setWaveNumber needs to be rethought, as it calls
340                // setSelectorValue which is redundant in the cases of dragging
341                // or clicking
342                setWaveNumber(val);
343                if (displayControl != null)
344                    displayControl.handleChannelChange(val);
345            }
346        }
347    }
348
349    public DisplayableData getImageDisplay() {
350        if (imageDisplay == null) {
351            try {
352                uniqueRangeType = RealType.getRealType(rangeType.getName()+"_"+cnt++);
353                imageDisplay = new HydraRGBDisplayable("image", uniqueRangeType, null, true, displayControl);
354            } catch (Exception e) {
355                LogUtil.logException("MultiSpectralDisplay.getImageDisplay", e);
356            }
357        }
358        return imageDisplay;
359    }
360
361    public float getWaveNumber() {
362        return waveNumber;
363    }
364
365    public int getChannelIndex() throws Exception {
366      return data.getChannelIndexFromWavenumber(waveNumber);
367    }
368
369    public void refreshDisplay() throws VisADException, RemoteException {
370        if (display == null)
371            return;
372
373        synchronized (displayedThings) {
374            for (DataReference ref : displayedThings) {
375                display.removeReference(ref);
376                display.addReference(ref, colorMaps.get(ref));
377            }
378        }
379    }
380
381    public boolean hasNullData() {
382        try {
383            synchronized (displayedThings) {
384                for (DataReference ref : displayedThings) {
385                    if (ref.getData() == null)
386                        return true;
387                }
388            }
389        } catch (Exception e) { }
390        return false;
391    }
392
393    /** ID of the selector that controls the displayed channel. */
394    private final String channelSelector = hashCode() + "_chanSelect";
395
396    /** The map of selector IDs to selectors. */
397    private final Map<String, DragLine> selectors = 
398        new HashMap<String, DragLine>();
399
400    public void showChannelSelector() {
401        try {
402            createSelector(channelSelector, Color.GREEN);
403        } catch (Exception e) {
404            LogUtil.logException("MultiSpectralDisplay.showChannelSelector", e);
405        }
406    }
407
408    public void hideChannelSelector() {
409        try {
410            DragLine selector = removeSelector(channelSelector);
411            selector = null;
412        } catch (Exception e) {
413            LogUtil.logException("MultiSpectralDisplay.hideChannelSelector", e);
414        }
415    }
416
417    public DragLine createSelector(final String id, final Color color) throws Exception {
418        if (id == null)
419            throw new NullPointerException("selector id cannot be null");
420        if (color == null)
421            throw new NullPointerException("selector color cannot be null");
422        return createSelector(id, makeColorMap(color));
423    }
424
425    public DragLine createSelector(final String id, final ConstantMap[] color) throws Exception {
426        if (id == null)
427            throw new NullPointerException("selector id cannot be null");
428        if (color == null)
429            throw new NullPointerException("selector color cannot be null");
430
431        if (selectors.containsKey(id))
432            return selectors.get(id);
433
434        DragLine selector = new DragLine(this, id, color, initialRangeY);
435        selector.setHydraControl(displayControl);
436        selector.setSelectedValue(waveNumber);
437        selectors.put(id, selector);
438        return selector;
439    }
440
441    public DragLine getSelector(final String id) {
442        return selectors.get(id);
443    }
444
445    public float getSelectorValue(final String id) {
446        DragLine selector = selectors.get(id);
447        if (selector == null)
448            return Float.NaN;
449        return selector.getSelectedValue();
450    }
451
452    public void setSelectorValue(final String id, final float value) 
453        throws VisADException, RemoteException 
454    {
455        DragLine selector = selectors.get(id);
456        if (selector != null)
457            selector.setSelectedValue(value);
458    }
459
460    // BAD BAD BAD BAD
461    public void updateControlSelector(final String id, final float value) {
462        if (displayControl == null)
463            return;
464        if (displayControl instanceof LinearCombo) {
465            ((LinearCombo)displayControl).updateSelector(id, value);
466        } else if (displayControl instanceof HydraCombo) {
467            ((HydraCombo)displayControl).updateComboPanel(id, value);
468        }
469    }
470
471    public DragLine removeSelector(final String id) {
472        DragLine selector = selectors.remove(id);
473        if (selector == null)
474            return null;
475        selector.annihilate();
476        return selector;
477    }
478
479    public List<DragLine> getSelectors() {
480        return new ArrayList<DragLine>(selectors.values());
481    }
482
483    /**
484     * @return Whether or not the channel selector is being displayed.
485     */
486    public boolean displayingChannel() {
487        return (getSelector(channelSelector) != null);
488    }
489
490    public void removeRef(final DataReference thing) throws VisADException, 
491        RemoteException 
492    {
493        if (display == null)
494            return;
495
496        synchronized (displayedThings) {
497            displayedThings.remove(thing);
498            colorMaps.remove(thing);
499            idToRef.remove(thing.getName());
500            display.removeReference(thing);
501        }
502    }
503
504    public void addRef(final DataReference thing, final Color color) 
505        throws VisADException, RemoteException 
506    {
507        if (display == null)
508            return;
509
510        synchronized (displayedThings) {
511            ConstantMap[] colorMap = makeColorMap(color);
512
513            displayedThings.add(thing);
514            idToRef.put(thing.getName(), thing);
515            ConstantMap[] constMaps;
516            if (data.hasBandNames()) {
517                constMaps = new ConstantMap[colorMap.length+2];
518                System.arraycopy(colorMap, 0, constMaps, 0, colorMap.length);
519                constMaps[colorMap.length] = new ConstantMap(1f, Display.PointMode);
520                constMaps[colorMap.length+1] = new ConstantMap(5f, Display.PointSize);
521            } else {
522                constMaps = colorMap;
523            }
524            colorMaps.put(thing, constMaps);
525
526            display.addReference(thing, constMaps);
527        }
528    }
529
530    public void updateRef(final DataReference thing, final Color color)
531        throws VisADException, RemoteException 
532    {
533        ConstantMap[] colorMap = makeColorMap(color);
534        ConstantMap[] constMaps;
535        if (data.hasBandNames()) {
536            constMaps = new ConstantMap[colorMap.length+2];
537            System.arraycopy(colorMap, 0, constMaps, 0, colorMap.length);
538            constMaps[colorMap.length] = new ConstantMap(1f, Display.PointMode);
539            constMaps[colorMap.length+1] = new ConstantMap(5f, Display.PointSize);
540        } else {
541            constMaps = colorMap;
542        }
543        colorMaps.put(thing, constMaps);
544        idToRef.put(thing.getName(), thing);
545        refreshDisplay();
546    }
547
548    public void reorderDataRefsById(final List<String> dataRefIds) {
549        if (dataRefIds == null)
550            throw new NullPointerException("");
551
552        synchronized (displayedThings) {
553            try {
554                displayedThings.clear();
555                for (String refId : dataRefIds) {
556                    DataReference ref = idToRef.get(refId);
557                    ConstantMap[] color = colorMaps.get(ref);
558                    display.removeReference(ref);
559                    display.addReference(ref, color);
560                }
561            } catch (Exception e) { }
562        }
563    }
564
565    // TODO: needs work
566    public boolean setWaveNumber(final float val) {
567        if (data == null)
568            return false;
569
570        if (waveNumber == val)
571            return true;
572
573        try {
574            if (spectrum == null) { 
575              spectrum = data.getSpectrum(new int[] { 1, 1 });
576            }
577
578            Gridded1DSet domain = (Gridded1DSet)spectrum.getDomainSet();
579            int[] idx = domain.valueToIndex(new float[][] { { val } });
580            float[][] tmp = domain.indexToValue(idx);
581            float channel = tmp[0][0];
582
583            setSelectorValue(channelSelector, channel);
584
585            imageExpired = true;
586        } catch (Exception e) {
587            LogUtil.logException("MultiSpectralDisplay.setDisplayedWaveNum", e);
588            return false;
589        }
590
591        waveNumber = val;
592
593        if (data.hasBandNames()) {
594            String name = data.getBandNameFromWaveNumber(waveNumber);
595            bandSelectComboBox.setSelectedItem(name);
596        }
597
598        return true;
599    }
600
601    /**
602     * @return The ConstantMap representation of {@code color}.
603     */
604    public static ConstantMap[] makeColorMap(final Color color)
605        throws VisADException, RemoteException 
606    {
607        float r = color.getRed() / 255f;
608        float g = color.getGreen() / 255f;
609        float b = color.getBlue() / 255f;
610        float a = color.getAlpha() / 255f;
611        return new ConstantMap[] { new ConstantMap(r, Display.Red),
612                                   new ConstantMap(g, Display.Green),
613                                   new ConstantMap(b, Display.Blue),
614                                   new ConstantMap(a, Display.Alpha) };
615    }
616
617    /**
618     * Provides {@code master} some sensible default attributes.
619     */
620    private static void setDisplayMasterAttributes(final XYDisplay master) 
621        throws VisADException, RemoteException 
622    {
623        master.showAxisScales(true);
624        master.setAspect(2.5, 0.75);
625
626        double[] proj = master.getProjectionMatrix();
627        proj[0] = 0.35;
628        proj[5] = 0.35;
629        proj[10] = 0.35;
630
631        master.setProjectionMatrix(proj);
632    }
633
634    /**
635     * @return The minimum and maximum values found on the x-axis.
636     */
637    private static float[] getXRange(final Gridded1DSet domain) {
638        return new float[] { domain.getLow()[0], domain.getHi()[0] };
639    }
640
641    public static RealType getRangeType(final FlatField spectrum) {
642        return (((FunctionType)spectrum.getType()).getFlatRange().getRealComponents())[0];
643    }
644
645    private static RealType getDomainType(final FlatField spectrum) {
646        return (((FunctionType)spectrum.getType()).getDomain().getRealComponents())[0];
647    }
648
649    private static class RubberBandBox extends CellImpl {
650
651        private static final String RBB = "_rubberband";
652        
653        private DataReference rubberBand;
654
655        private boolean init = false;
656
657        private ScalarMap xmap;
658
659        private ScalarMap ymap;
660
661        public RubberBandBox(final MultiSpectralDisplay msd,
662            final ScalarMap x, final ScalarMap y) throws VisADException,
663            RemoteException 
664        {
665            RealType domainType = msd.getDomainType();
666            RealType rangeType = msd.getRangeType();
667
668            LocalDisplay display = msd.getDisplay();
669
670            rubberBand = new DataReferenceImpl(hashCode() + RBB);
671            rubberBand.setData(new RealTuple(new RealTupleType(domainType,
672                rangeType), new double[] { Double.NaN, Double.NaN }));
673
674            display.addReferences(new RubberBandBoxRendererJ3D(domainType,
675                rangeType, 1, 1), new DataReference[] { rubberBand }, null);
676
677            xmap = x;
678            ymap = y;
679
680            this.addReference(rubberBand);
681        }
682
683        public void doAction() throws VisADException, RemoteException {
684            if (!init) {
685                init = true;
686                return;
687            }
688
689            Gridded2DSet set = (Gridded2DSet)rubberBand.getData();
690
691            float[] low = set.getLow();
692            float[] high = set.getHi();
693
694            xmap.setRange(low[0], high[0]);
695            ymap.setRange(low[1], high[1]);
696        }
697    }
698
699    public static class DragLine extends CellImpl {
700        private final String selectorId = hashCode() + "_selector";
701        private final String lineId = hashCode() + "_line";
702        private final String controlId;
703
704        private ConstantMap[] mappings = new ConstantMap[5];
705
706        private DataReference line;
707
708        private DataReference selector;
709
710        private MultiSpectralDisplay multiSpectralDisplay;
711        
712        private HydraControl hydraControl;
713
714        private RealType domainType;
715        private RealType rangeType;
716
717        private RealTupleType tupleType;
718
719        private LocalDisplay display;
720
721        private float[] YRANGE;
722
723        private float lastSelectedValue;
724
725        public DragLine(final MultiSpectralDisplay msd, final String controlId, final Color color) throws Exception {
726            this(msd, controlId, makeColorMap(color));
727        }
728
729        public DragLine(final MultiSpectralDisplay msd, final String controlId, final Color color, float[] YRANGE) throws Exception {
730            this(msd, controlId, makeColorMap(color), YRANGE);
731        }
732
733        public DragLine(final MultiSpectralDisplay msd, final String controlId,
734            final ConstantMap[] color) throws Exception
735        {
736            this(msd, controlId, color, new float[] {180f, 320f});
737        }
738
739        public DragLine(final MultiSpectralDisplay msd, final String controlId, 
740            final ConstantMap[] color, float[] YRANGE) throws Exception 
741        {
742            if (msd == null)
743                throw new NullPointerException("must provide a non-null MultiSpectralDisplay");
744            if (controlId == null)
745                throw new NullPointerException("must provide a non-null control ID");
746            if (color == null)
747                throw new NullPointerException("must provide a non-null color");
748
749            this.controlId = controlId;
750            this.multiSpectralDisplay = msd;
751            this.YRANGE = YRANGE;
752            lastSelectedValue = multiSpectralDisplay.getWaveNumber();
753
754            for (int i = 0; i < color.length; i++) {
755                mappings[i] = (ConstantMap)color[i].clone();
756            }
757            mappings[4] = new ConstantMap(-0.5, Display.YAxis);
758
759            Gridded1DSet domain = multiSpectralDisplay.getDomainSet();
760
761            domainType = multiSpectralDisplay.getDomainType();
762            rangeType = multiSpectralDisplay.getRangeType();
763            tupleType = new RealTupleType(domainType, rangeType);
764
765            selector = new DataReferenceImpl(selectorId);
766            line = new DataReferenceImpl(lineId);
767
768            display = multiSpectralDisplay.getDisplay();
769
770            display.addReferences(new GrabLineRendererJ3D(domain), new DataReference[] { selector }, new ConstantMap[][] { mappings });
771            display.addReference(line, cloneMappedColor(color));
772
773            addReference(selector);
774        }
775
776        private static ConstantMap[] cloneMappedColor(final ConstantMap[] color) throws Exception {
777            assert color != null && color.length >= 3 : color;
778            return new ConstantMap[] { 
779                (ConstantMap)color[0].clone(),
780                (ConstantMap)color[1].clone(),
781                (ConstantMap)color[2].clone(),
782            };
783        }
784
785        public void annihilate() {
786            try {
787                display.removeReference(selector);
788                display.removeReference(line);
789            } catch (Exception e) {
790                LogUtil.logException("DragLine.annihilate", e);
791            }
792        }
793
794        public String getControlId() {
795            return controlId;
796        }
797
798        /**
799         * Handles drag and drop updates.
800         */
801        public void doAction() throws VisADException, RemoteException {
802            setSelectedValue(getSelectedValue());
803        }
804
805        public float getSelectedValue() {
806            float val = (float)display.getDisplayRenderer().getDirectAxisValue(domainType);
807            if (Float.isNaN(val))
808                val = lastSelectedValue;
809            return val;
810        }
811
812        public void setSelectedValue(final float val) throws VisADException,
813            RemoteException 
814        {
815            // don't do work for stupid values
816            if ((Float.isNaN(val)) 
817                || (selector.getThing() != null && val == lastSelectedValue))
818                return;
819
820            line.setData(new Gridded2DSet(tupleType,
821                new float[][] { { val, val }, { YRANGE[0], YRANGE[1] } }, 2));
822
823            selector.setData(new Real(domainType, val));
824            lastSelectedValue = val;
825            
826            if (hydraControl instanceof MultiSpectralControl) {
827                ((MultiSpectralControl) hydraControl).setWavelengthLabel
828                (
829                                MultiSpectralControl.WAVENUMLABEL + val
830                );
831            }
832            multiSpectralDisplay.updateControlSelector(controlId, val);
833        }
834
835                /**
836                 * Set the display control so we can call back and update
837                 * wavelength readout in real time.
838                 * 
839                 * @param hydraControl the display control to set
840                 */
841        
842                public void setHydraControl(HydraControl hydraControl) {
843                        this.hydraControl = hydraControl;
844                }
845    }
846}