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