// // ScalarMap.java // /* VisAD system for interactive analysis and visualization of numerical data. Copyright (C) 1996 - 2017 Bill Hibbard, Curtis Rueden, Tom Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and Tommy Jasmin. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ package visad; import java.rmi.*; import java.util.*; /** A ScalarMap object defines a mapping from a RealType to a DisplayRealType. A set of ScalarMap objects define how data are dislayed.<P> The mapping of values is linear. Any non-linear mapping must be handled by Display CoordinateSystem-s.<P> */ public class ScalarMap extends Object implements Cloneable, java.io.Serializable, Comparable { // WLH 31 Aug 2000 // display Unit to use rather than Scalar default Unit private Unit overrideUnit = null; // scale and offset for converting from Scalar default Unit to overrideUnit private double override_scale, override_offset; private ScalarType Scalar; private DisplayRealType DisplayScalar; // index into Display.RealTypeVector private int ScalarIndex; // index into Display.DisplayRealTypeVector private int DisplayScalarIndex; // index into ValueArray int ValueIndex; // control associated with DisplayScalar, or null private transient Control control; // unique Display this ScalarMap is part of private transient DisplayImpl display; /** true if dataRange set by application; disables automatic setting */ private boolean isManual; /** true if Scalar values need to be scaled */ boolean isScaled; /** ranges of values of DisplayScalar */ double[] displayRange = new double[2]; /** ranges of values of Scalar */ private double[] dataRange = new double[2]; /** ranges of values of Scalar in default units*/ private double[] defaultUnitRange = new double[2]; /** scale and offset */ private double scale, offset; /** incremented by incTick */ private long NewTick; /** value of NewTick at last setTicks call */ private long OldTick; /** set by setTicks if OldTick < NewTick; cleared by resetTicks */ private boolean tickFlag; private String scalarName = null; /** location of axis scale if DisplayScalar is XAxis, YAxis or ZAxis private int axis = -1; private int axis_ordinal = -1; < removed for AxisScale 10-Oct-2000 > */ private boolean scale_flag = false; private boolean back_scale_flag = false; //private float[] scale_color = {1.0f, 1.0f, 1.0f}; <DRM 10-Oct-2000> private boolean scale_on = true; private boolean underscore_to_blank = false; /** Vector of ScalarMapListeners */ private transient Vector ListenerVector = new Vector(); /** AxisScale */ private AxisScale axisScale = null; // added DRM 10-Oct-2000 /** * Construct a <CODE>ScalarMap</CODE> that maps the scalar to * the display_scalar. * @param scalar ScalarType (must be RealType at present) * @param display_scalar DisplayScalar to map to. If the * display_scalar is one of the spatial * axes (X, Y, Z) an AxisScale will be * created. * @throws VisADException VisAD error */ public ScalarMap(ScalarType scalar, DisplayRealType display_scalar) throws VisADException { this(scalar, display_scalar, true); } ScalarMap(ScalarType scalar, DisplayRealType display_scalar, boolean needNonNullScalar) throws VisADException { if (scalar == null && needNonNullScalar) { throw new DisplayException("ScalarMap: scalar is null"); } if (display_scalar == null) { throw new DisplayException("ScalarMap: display_scalar is null"); } if (display_scalar.equals(Display.List)) { throw new DisplayException("ScalarMap: display_scalar may not be List"); } boolean text = display_scalar.getText(); if (scalar != null) { /* WLH 15 June 2000 if (text && !(scalar instanceof TextType)) { throw new DisplayException("ScalarMap: RealType scalar cannot be " + "used with TextType display_scalar"); } */ if (!text && !(scalar instanceof RealType)) { throw new DisplayException("ScalarMap: TextType scalar cannot be " + "used with RealType display_scalar"); } } control = null; Scalar = scalar; DisplayScalar = display_scalar; display = null; ScalarIndex = -1; DisplayScalarIndex = -1; isScaled = DisplayScalar.getRange(displayRange); isManual = false; dataRange[0] = Double.NaN; dataRange[1] = Double.NaN; defaultUnitRange[0] = dataRange[0]; defaultUnitRange[1] = dataRange[1]; OldTick = Long.MIN_VALUE; NewTick = Long.MIN_VALUE + 1; tickFlag = false; if (Scalar != null) scalarName = Scalar.getName(); if (DisplayScalar.equals(Display.XAxis) || DisplayScalar.equals(Display.YAxis) || DisplayScalar.equals(Display.ZAxis)) { axisScale = new AxisScale(this); } } /** re-enable auto-scaling for this ScalarMap */ public void resetAutoScale() { isManual = false; } /** disable auto-scaling for this ScalarMap */ public void disableAutoScale() { isManual = true; } /** determine whether this ScalarMap is auto-scaled */ public boolean isAutoScale() { return !isManual; } // WLH 22 August 2001 public boolean doInitialize() { if (DisplayScalar.equals(Display.IsoContour)) { if (control != null) { float[] lowhibase = new float[3]; boolean[] dashes = new boolean[1]; float[] levs = ((ContourControl) control).getLevels(lowhibase, dashes); return (levs == null); } else { return false; } } else { return isScaled && !isManual; } } // WLH 31 Aug 2000 /** * Set display Unit to override default Unit of Scalar; * MUST be called before any data are displayed * @param unit unit that data will be displayed with * @throws VisADException <CODE>unit</CODE> is not convertable with * the default unit or scalar is not a RealType. */ public void setOverrideUnit(Unit unit) throws VisADException { if (!(Scalar instanceof RealType)) { throw new UnitException("Scalar is not RealType"); } Unit rtunit = ((RealType) Scalar).getDefaultUnit(); if (!Unit.canConvert(unit, rtunit)) { throw new UnitException("unit not convertable with RealType default"); } if (unit != null) { overrideUnit = unit; override_offset = overrideUnit.toThis(0.0, rtunit); override_scale = overrideUnit.toThis(1.0, rtunit) - override_offset; } } // WLH 31 Aug 2000 /** * Return the override unit. * @return Unit being used in the display. */ public Unit getOverrideUnit() { return overrideUnit; } /** * Get the name being used on the axis scale. * @return name of the scale - either the default or the one set by * <CODE>setScalarName</CODE> * @see #setScalarName(String name) */ public String getScalarName() { return scalarName; } /** * Set the name being used on the axis scale. * @param name new name for the scalar. * @see AxisScale#setTitle(String name) */ public void setScalarName(String name) { scalarName = name; if (axisScale != null) axisScale.setTitle(scalarName); } /** invoke incTick on every application call to setRange */ public long incTick() { // WLH 19 Feb 2001 - move to after increment NewTick // if (display != null) display.controlChanged(); NewTick += 1; if (NewTick == Long.MAX_VALUE) NewTick = Long.MIN_VALUE + 1; /* System.out.println(Scalar + " -> " + DisplayScalar + " incTick = " + NewTick); */ if (display != null) display.controlChanged(); return NewTick; } /** set tickFlag according to OldTick and NewTick */ public synchronized void setTicks() { tickFlag = (OldTick < NewTick || (NewTick < 0 && 0 < OldTick)); /* System.out.println(Scalar + " -> " + DisplayScalar + " set tickFlag = " + tickFlag); */ OldTick = NewTick; if (control != null) control.setTicks(); } public synchronized boolean peekTicks(DataRenderer r, DataDisplayLink link) { if (control == null) { /* boolean flag = (OldTick < NewTick || (NewTick < 0 && 0 < OldTick)); if (flag) { System.out.println(Scalar + " -> " + DisplayScalar + " peek flag = " + flag); } */ return (OldTick < NewTick || (NewTick < 0 && 0 < OldTick)); } else { /* boolean flag = (OldTick < NewTick || (NewTick < 0 && 0 < OldTick)); boolean cflag = control.peekTicks(r, link); if (flag || cflag) { System.out.println(Scalar + " -> " + DisplayScalar + " peek flag = " + flag + " cflag = " + cflag); } */ return (OldTick < NewTick || (NewTick < 0 && 0 < OldTick)) || control.peekTicks(r, link); } } /** return true if application called setRange */ public synchronized boolean checkTicks(DataRenderer r, DataDisplayLink link) { if (control == null) { /* System.out.println(Scalar + " -> " + DisplayScalar + " check tickFlag = " + tickFlag); */ return tickFlag; } else { /* boolean cflag = control.checkTicks(r, link); System.out.println(Scalar + " -> " + DisplayScalar + " check tickFlag = " + tickFlag + " cflag = " + cflag); */ return tickFlag || control.checkTicks(r, link); } } /** reset tickFlag */ synchronized void resetTicks() { // System.out.println(Scalar + " -> " + DisplayScalar + " reset"); tickFlag = false; if (control != null) control.resetTicks(); } /** * Get the ScalarType that is the map domain * @return ScalarType of map domain */ public ScalarType getScalar() { return Scalar; } /** * Get the DisplayRealType that is the map range * @return DisplayRealType of map range */ public DisplayRealType getDisplayScalar() { return DisplayScalar; } /** * Get the DisplayImpl this ScalarMap is linked to * @return display that this ScalarMap is linked to */ public DisplayImpl getDisplay() { return display; } /** * Clear the link to the VisAD display. This will subsequently * cause {@link #getDisplay()} and {@link #getControl()} to return * <code>null</code>; consequently, information stored in the Control * might have to be reestablished. This method invokes the method {@link * ScalarMapListener#controlChanged(ScalarMapControlEvent)} on all registered * {@link ScalarMapListener}s with this instance as the event source, {@link * ScalarMapEvent#CONTROL_REMOVED} as the event ID, and the control as the * event control. * * @throws RemoteException Java RMI failure * @throws VisADException VisAD failure */ synchronized void nullDisplay() throws RemoteException, VisADException { if (control != null) { control.nullControl(); ScalarMapControlEvent evt; evt = new ScalarMapControlEvent(this, ScalarMapEvent.CONTROL_REMOVED, control); notifyCtlListeners(evt); } control = null; display = null; ScalarIndex = -1; DisplayScalarIndex = -1; scale_flag = back_scale_flag; if (axisScale != null) axisScale.setAxisOrdinal(-1); } /** * Set the DisplayImpl this ScalarMap is linked to * @param d display to link to * @throws VisADException map is already linked to a DisplayImpl or * other VisAD error */ synchronized void setDisplay(DisplayImpl d) throws VisADException { if (d.equals(display)) return; if (display != null) { throw new DisplayException("ScalarMap.setDisplay: ScalarMap cannot belong" + " to two Displays"); } display = d; if (scale_flag) makeScale(); // System.out.println("setDisplay " + Scalar + " -> " + DisplayScalar); // WLH 27 Nov 2000 if (!(this instanceof ConstantMap)) { ProjectionControl pcontrol = display.getProjectionControl(); try { setAspectCartesian(pcontrol.getAspectCartesian()); } catch (RemoteException e) { } } } /** * Gets the Control for the DisplayScalar. The Control is constructed when * this ScalarMap is linked to a Display via an invocation of the {@link * Display#addMap(ScalarMap)} method. Not all ScalarMaps have Controls, * generally depending on the ScalarMap's DisplayRealType. If a ScalarMap is * removed from a Display (via the {@link Display#clearMaps()} method, then, * in general, any information in the ScalarMap's control will be lost and * must be reestablished. * * @return The Control for the DisplayScalar or <code> * null</code> if one has not yet been set. */ public Control getControl() { return control; } /** * Creates the Control for the associated DisplayScalar. This method invokes * the method {@link ScalarMapListener#controlChanged(ScalarMapControlEvent)} * on all registered {@link ScalarMapListener}s with this instance as * the event source and {@link ScalarMapEvent#CONTROL_ADDED} or {@link * ScalarMapEvent#CONTROL_REPLACED} as the event ID -- depending on whether * this is the first control or not. The event control is the previous * control if the event ID is {@link ScalarMapEvent#CONTROL_REPLACED}. If the * event ID is {@link ScalarMapEvent#CONTROL_ADDED}, then the event control is * the created control or <code>null</code> -- depending on whether or not the * control was successfully created. * * @throws RemoteException Java RMI failure * @throws VisADException VisAD failure */ synchronized void setControl() throws VisADException, RemoteException { int evtID; Control evtCtl; if (control != null) { evtID = ScalarMapEvent.CONTROL_REPLACED; evtCtl = control; } else { evtID = ScalarMapEvent.CONTROL_ADDED; evtCtl = null; } if (display == null) { throw new DisplayException("ScalarMap.setControl: not part of " + "any Display"); } control = display.getDisplayRenderer().makeControl(this); if (control != null) { display.addControl(control); if (evtCtl == null) { evtCtl = control; } } if (control != null || evtCtl != null) { notifyCtlListeners(new ScalarMapControlEvent(this, evtID, evtCtl)); } } /** return value is true if data (RealType) values are linearly * scaled to display (DisplayRealType) values; * if so, then values are scaled by: * display_value = data_value * so[0] + so[1]; * (data[0], data[1]) defines range of data values (either passed * in to setRange or computed by autoscaling logic) and * (display[0], display[1]) defines range of display values; * so, data, display must each be passed in as double[2] arrays; * note if overrideUnit != null, so and data are in overrideUnit * @param so array to contain scale and offset * @param data array to contain the data range * @param display array to contain the display range * @return true if data are linearly scaled */ public boolean getScale(double[] so, double[] data, double[] display) { // WLH 31 Aug 2000 if (overrideUnit != null) { so[0] = scale * override_scale; so[1] = scale * override_offset + offset; } else { so[0] = scale; so[1] = offset; } data[0] = dataRange[0]; data[1] = dataRange[1]; display[0] = displayRange[0]; display[1] = displayRange[1]; return isScaled; } /** * Returns the current range of the {@link RealType} data. The range is * implicitly set by autoscaling logic or may be explicitly set by the {@link * #setRange(double,double)} method. Note that if overrideUnit != null, * then dataRange is in overrideUnit. * * @return The current range of the {@link RealType} data. * The array is new and may be safely modified. */ public double[] getRange() { double[] range = {dataRange[0], dataRange[1]}; return range; } /** explicitly set the range of data (RealType) values according * to Unit conversion between this ScalarMap's RealType and * DisplayRealType (both must have Units and they must be * convertable; if neither this nor setRange is invoked, then * the range will be computed from the initial values of Data * objects linked to the Display by autoscaling logic. * @throws VisADException VisAD error * @throws RemoteException Java RMI error */ public void setRangeByUnits() throws VisADException, RemoteException { isManual = true; setRange(null, 0.0, 0.0, true); if (scale == scale && offset == offset) { incTick(); // did work, so wake up Display } else { isManual = false; // didn't work, so don't lock out auto-scaling } } /** * Explicitly sets the range of {@link RealType} data values that is mapped to * the natural range of {@link DisplayRealType} display values. This method * is used to define a linear map from Scalar to DisplayScalar values. If * neither this nor {@link #setRangeByUnits()} is invoked, then the range will * be computed by autoscaling logic from the initial values of Data objects * linked to the Display. If the range of data values is (0.0, 1.0), for * example, this method may be invoked with low = 1.0 and hi = 0.0 to invert * the display scale. * * @param low One end of the range of applicable data. * @param hi The other end of the range of applicable data. * @throws VisADException VisAD failure. * @throws RemoteException Java RMI failure. */ public void setRange(double low, double hi) throws VisADException, RemoteException { setRange(low, hi, VisADEvent.LOCAL_SOURCE); } /** explicitly set the range of data (RealType) values; used for * linear map from Scalar to DisplayScalar values; * if neither this nor setRangeByUnits is invoked, then the * range will be computed from the initial values of Data * objects linked to the Display by autoscaling logic; * if the range of data values is (0.0, 1.0), for example, this * method may be invoked with low = 1.0 and hi = 0.0 to invert * the display scale . * @param low lower range value (see notes above) * @param hi upper range value (see notes above) * @param remoteId id of remote scale * @throws VisADException VisAD error * @throws RemoteException Java RMI error */ public void setRange(double low, double hi, int remoteId) throws VisADException, RemoteException { if (DisplayScalar.equals(Display.Animation)) { System.err.println("Warning: setRange on " + "ScalarMap to Display.Animation has no effect."); return; } isManual = true; setRange(null, low, hi, false, remoteId); if (scale == scale && offset == offset) { incTick(); // did work, so wake up Display } else { isManual = false; // didn't work, so don't lock out auto-scaling } } /** set range used for linear map from Scalar to DisplayScalar values; this is the call for automatic scaling */ public void setRange(DataShadow shadow) throws VisADException, RemoteException { if (!isManual) setRange(shadow, 0.0, 0.0, false, VisADEvent.LOCAL_SOURCE); } /** set range used for linear map from Scalar to DisplayScalar values */ private synchronized void setRange(DataShadow shadow, double low, double hi, boolean unit_flag) throws VisADException, RemoteException { setRange(shadow, low, hi, unit_flag, VisADEvent.LOCAL_SOURCE); } /** set range used for linear map from Scalar to DisplayScalar values */ private synchronized void setRange(DataShadow shadow, double low, double hi, boolean unit_flag, int remoteId) throws VisADException, RemoteException { int i = ScalarIndex; if (shadow != null) { // WLH - 23 Sept 99 if (DisplayScalar.equals(Display.Latitude) || DisplayScalar.equals(Display.Longitude)) { Unit data_unit = (Scalar instanceof RealType) ? ((RealType) Scalar).getDefaultUnit() : null; Unit display_unit = DisplayScalar.getDefaultUnit(); if (data_unit != null && display_unit != null && Unit.canConvert(data_unit, display_unit)) { dataRange[0] = data_unit.toThis(displayRange[0], display_unit); dataRange[1] = data_unit.toThis(displayRange[1], display_unit); } else { if (i < 0 || i >= shadow.ranges[0].length) return; dataRange[0] = shadow.ranges[0][i]; dataRange[1] = shadow.ranges[1][i]; } } else { if (i < 0 || i >= shadow.ranges[0].length) return; dataRange[0] = shadow.ranges[0][i]; dataRange[1] = shadow.ranges[1][i]; } } else if (unit_flag) { Unit data_unit = (Scalar instanceof RealType) ? ((RealType) Scalar).getDefaultUnit() : null; Unit display_unit = DisplayScalar.getDefaultUnit(); if (data_unit == null || display_unit == null) { throw new UnitException("ScalarMap.setRangeByUnits: null Unit"); } dataRange[0] = data_unit.toThis(displayRange[0], display_unit); dataRange[1] = data_unit.toThis(displayRange[1], display_unit); /* System.out.println("data_unit = " + data_unit + " display_unit = " + display_unit); System.out.println("dataRange = " + dataRange[0] + " " + dataRange[1] + " displayRange = " + displayRange[0] + " " + displayRange[1]); */ } else { dataRange[0] = low; dataRange[1] = hi; // WLH 31 Aug 2000 // manual range is in overrideUnit. so convert to Scalar default Unit if (overrideUnit != null) { dataRange[0] = (dataRange[0] - override_offset) / override_scale; dataRange[1] = (dataRange[1] - override_offset) / override_scale; } } /* if (shadow != null || remoteId != VisADEvent.LOCAL_SOURCE) { System.out.println(Scalar + " -> " + DisplayScalar + " range: " + dataRange[0] + " to " + dataRange[1] + " " + display.getName()); } */ // at this point dataRange is range for Scalar default Unit // even if (overrideUnit != null) // DRM 17 Feb 2006 - so set the defaultUnitRange to be these values. defaultUnitRange[0] = dataRange[0]; defaultUnitRange[1] = dataRange[1]; if (defaultUnitRange[0] == defaultUnitRange[1]) { double half = defaultUnitRange[0] / 2000.0; if (half < 0.5) half = 0.5; defaultUnitRange[0] -= half; defaultUnitRange[1] += half; } if (isScaled) { computeScaleAndOffset(); } else { // if (!isScaled) if (dataRange[0] == Double.MAX_VALUE || dataRange[1] == -Double.MAX_VALUE) { dataRange[0] = Double.NaN; dataRange[1] = Double.NaN; } // WLH 31 Aug 2000 if (overrideUnit != null) { // now convert dataRange to overrideUnit dataRange[0] = defaultUnitRange[0] * override_scale + override_offset; dataRange[1] = defaultUnitRange[1] * override_scale + override_offset; } } /* System.out.println(Scalar + " -> " + DisplayScalar + " range: " + dataRange[0] + " to " + dataRange[1] + " scale: " + scale + " " + offset); */ if (DisplayScalar.equals(Display.Animation) && shadow != null) { if (control != null && ((AnimationControl)control).getComputeSet()) { Set set = shadow.animationSampling; /* DRM: 04 Jan 2003 if (set == null) { return; } */ ((AnimationControl) control).setSet(set, true); } } else if (DisplayScalar.equals(Display.IsoContour)) { if (control != null) { // WLH 10 July 2002 // don't set if application has called control.setLevels() float[] lowhibase = new float[3]; boolean[] dashes = new boolean[1]; boolean public_set = ((ContourControl) control).getPublicSet(); if (!public_set) { boolean[] bvalues = new boolean[2]; float[] values = new float[5]; ((ContourControl) control).getMainContours(bvalues, values); if (shadow == null) { // don't set surface value for auto-scale values[0] = (float) dataRange[0]; // surfaceValue } // CTR: 29 Jul 1999: interval should never be zero float f = (float) (dataRange[1] - dataRange[0]) / 10.0f; if (f != 0.0f) values[1] = f; // contourInterval values[2] = (float) dataRange[0]; // lowLimit values[3] = (float) dataRange[1]; // hiLimit values[4] = (float) dataRange[0]; // base ((ContourControl) control).setMainContours(bvalues, values, true, true); } } } else if (DisplayScalar.equals(Display.XAxis) || DisplayScalar.equals(Display.YAxis) || DisplayScalar.equals(Display.ZAxis)) { if (dataRange[0] != Double.MAX_VALUE && dataRange[1] != -Double.MAX_VALUE && dataRange[0] == dataRange[0] && dataRange[1] == dataRange[1] && dataRange[0] != dataRange[1] && scale == scale && offset == offset) { if (display != null) { makeScale(); } else { scale_flag = true; } back_scale_flag = true; } } if (dataRange[0] == dataRange[0] && dataRange[1] == dataRange[1] && ListenerVector != null) { ScalarMapEvent evt; evt = new ScalarMapEvent(this, (shadow == null ? ScalarMapEvent.MANUAL : ScalarMapEvent.AUTO_SCALE), remoteId); Vector listeners_clone = null; synchronized (ListenerVector) { listeners_clone = (Vector) ListenerVector.clone(); } Enumeration listeners = listeners_clone.elements(); while (listeners.hasMoreElements()) { ScalarMapListener listener = (ScalarMapListener) listeners.nextElement(); listener.mapChanged(evt); } } } private void computeScaleAndOffset() { if (dataRange[0] == Double.MAX_VALUE || dataRange[1] == -Double.MAX_VALUE) { dataRange[0] = Double.NaN; dataRange[1] = Double.NaN; scale = Double.NaN; offset = Double.NaN; } else { if (dataRange[0] == dataRange[1]) { // WLH 11 April 2000 double half = dataRange[0] / 2000.0; if (half < 0.5) half = 0.5; dataRange[0] -= half; dataRange[1] += half; } // WLH 31 Aug 2000 if (overrideUnit != null) { // now convert dataRange to overrideUnit dataRange[0] = defaultUnitRange[0] * override_scale + override_offset; dataRange[1] = defaultUnitRange[1] * override_scale + override_offset; } scale = (displayRange[1] - displayRange[0]) / (dataRange[1] - dataRange[0]); offset = displayRange[0] - scale * dataRange[0]; } if (Double.isInfinite(scale) || Double.isInfinite(offset) || scale != scale || offset != offset) { dataRange[0] = Double.NaN; dataRange[1] = Double.NaN; scale = Double.NaN; offset = Double.NaN; } } /** add a ScalarMapListener, to be notified whenever setRange is * invoked * @param listener <CODE>ScalarMapListener</CODE> to recieve notification * of changes. */ public void addScalarMapListener(ScalarMapListener listener) { if (ListenerVector == null) { ListenerVector = new Vector(); } ListenerVector.addElement(listener); if (dataRange[0] == dataRange[0] && dataRange[1] == dataRange[1]) { try { listener.mapChanged(new ScalarMapEvent(this, ScalarMapEvent.MANUAL)); } catch (VisADException e) { } catch (RemoteException e) { } } } /** remove a ScalarMapListener * @param listener <CODE>ScalarMapListener</CODE> to remove from the list */ public void removeScalarMapListener(ScalarMapListener listener) { if (listener != null && ListenerVector != null) { ListenerVector.removeElement(listener); } } /** Send a <CODE>ScalarMapEvent</CODE> to all control listeners */ private void notifyCtlListeners(ScalarMapControlEvent evt) throws RemoteException, VisADException { if (ListenerVector != null) { Vector listeners_clone = null; synchronized (ListenerVector) { listeners_clone = (Vector) ListenerVector.clone(); } Enumeration listeners = listeners_clone.elements(); while (listeners.hasMoreElements()) { ScalarMapListener listener = (ScalarMapListener) listeners.nextElement(); listener.controlChanged(evt); } } } /** * Change underscore characters (_) in the Scalar name to blanks. * Can be used to change the displayed scalar name on the axis. * @param u2b true to change, false to change back * @see #setScalarName as an alternative */ public void setUnderscoreToBlank(boolean u2b) { underscore_to_blank = u2b; if (Scalar != null) { scalarName = Scalar.getName(); if (underscore_to_blank) { scalarName = scalarName.replace('_', ' '); } // set the label on the scale as well. DRM 17-Nov-2000 if (axisScale != null) axisScale.setTitle(scalarName); } } private static final double SCALE = 0.06; private static final double OFFSET = 1.05; /** * Create the scale that is displayed. This is called automatically * when <CODE>setRange(lo, hi)</CODE> and <CODE>setDisplay</CODE> are * called. It makes a call to <CODE>AxisScale.makeScale()</CODE> where * the actual hard work is done. * @throws VisADException VisAD error. */ public void makeScale() throws VisADException { if (axisScale != null) { DisplayRenderer displayRenderer = null; if (display == null) return; displayRenderer = display.getDisplayRenderer(); if (displayRenderer == null) return; boolean scaleMade = axisScale.makeScale(); if (scaleMade) { //displayRenderer.setScale(axis, axis_ordinal, array, scale_color); if (scale_on) { displayRenderer.setScale(axisScale); } else { displayRenderer.clearScale(axisScale); } scale_flag = false; } } } /** * Enable the display of the scale for this map. This can be used * to selectively turn on or off the scales in a display. Must be * used in conjunction with <CODE>GraphicsModeControl.setScaleEnable()</CODE> * or <CODE>DisplayRenderer.setScaleOn(boolean on)</CODE>. * @param on true will enable display of axis, false will disable display * @see visad.GraphicsModeControl#setScaleEnable(boolean enable) * @see visad.DisplayRenderer#setScaleOn(boolean on) * @see visad.AxisScale#setVisible(boolean visible) */ public void setScaleEnable(boolean on) { scale_on = on; if (axisScale != null) axisScale.setVisible(on); } /** * See if the AxisScale is visible or not. * @return true if the AxisScale is visible */ public boolean getScaleEnable() { return scale_on; } /** * Set color of axis scales; color must be float[3] with red, * green and blue components; DisplayScalar must be XAxis, * YAxis or ZAxis. Preferred method is to use <CODE>AxisScale.setColor<CODE> * methods. * @param color array of R,G,B values of color. * @throws VisADException non-spatial DisplayScalar or wrong length * of color array * @see #getAxisScale() * @see visad.AxisScale#setColor(Color color) * @see visad.AxisScale#setColor(float[] color) */ public void setScaleColor(float[] color) throws VisADException { if (!DisplayScalar.equals(Display.XAxis) && !DisplayScalar.equals(Display.YAxis) && !DisplayScalar.equals(Display.ZAxis)) { throw new DisplayException("ScalarMap.setScaleColor: DisplayScalar " + "must be XAxis, YAxis or ZAxis"); } if (color == null || color.length != 3) { throw new DisplayException("ScalarMap.setScaleColor: color is " + "null or wrong length"); } // DRM 10-Oct 2000 axisScale.setColor(color); } public boolean badRange() { // WLH 15 Feb 2002 boolean bad = (isScaled && (scale != scale || offset != offset)); if (DisplayScalar.equals(Display.Animation)) { if (control != null) { Set set = ((AnimationControl) control).getSet(); bad |= (set == null); } else { bad = true; } } return bad; // return (isScaled && (scale != scale || offset != offset)); } /** return an array of display (DisplayRealType) values by * linear scaling (if applicable) the data_values array * (RealType values) * @param values to scale as doubles * @return array of display values */ public float[] scaleValues(double[] values) { /* WLH 23 June 99 if (values == null || badRange()) return null; */ if (values == null) return null; float[] new_values = new float[values.length]; if (badRange()) { for (int i=0; i<values.length; i++) new_values[i] = Float.NaN; } else { // double[] old_values = values; // WLH 31 Aug 2000 //if (overrideUnit != null) { // DRM 11 Jun 2003 if (overrideUnit != null && !overrideUnit.equals(((RealType) Scalar).getDefaultUnit())) { try { values = overrideUnit.toThis(values, ((RealType) Scalar).getDefaultUnit()); } catch (UnitException e) { } } if (isScaled) { for (int i=0; i<values.length; i++) { new_values[i] = (float) (offset + scale * values[i]); } } else { for (int i=0; i<values.length; i++) { new_values[i] = (float) values[i]; } } /* if (overrideUnit != null) { System.out.println("values = " + old_values[0] + " " + values[0] + " " + new_values[0]); } */ } /* SRE 27 Oct 99 System.out.println( "ScalarMap.scaleValues(double[]): values[0] = " + values[0] + "; new_values[0] = " + new_values[0]); */ return new_values; } /** return an array of display (DisplayRealType) values by * linear scaling (if applicable) the data_values array * (RealType values) * @param values to scale as floats * @return array of display values */ public float[] scaleValues(float[] values) { return scaleValues(values, true); } /** return an array of display (DisplayRealType) values by * linear scaling (if applicable) the data_values array * (RealType values) * @param values to scale as floats * @param newArray false to scale in place * @return array of display values */ public float[] scaleValues(float[] values, boolean newArray) { /* WLH 23 June 99 if (values == null || badRange()) return null; */ if (values == null) return null; float[] new_values = null; if (badRange()) { new_values = (newArray) ? new float[values.length] : values; for (int i=0; i<values.length; i++) new_values[i] = Float.NaN; } else { // float[] old_values = values; // WLH 31 Aug 2000 //if (overrideUnit != null) { // DRM 11 Jun 2003 if (overrideUnit != null && !overrideUnit.equals(((RealType) Scalar).getDefaultUnit())) { try { values = overrideUnit.toThis(values, ((RealType) Scalar).getDefaultUnit(), newArray); } catch (UnitException e) { } } if (isScaled) { new_values = (newArray) ? new float[values.length] : values; for (int i=0; i<values.length; i++) { if (values[i] == values[i]) { new_values[i] = (float) (offset + scale * values[i]); } else { new_values[i] = Float.NaN; } } } else { new_values = values; } /* if (overrideUnit != null) { System.out.println("values = " + old_values[0] + " " + values[0] + " " + new_values[0]); } */ } /* SRE 27 Oct 99 System.out.println( "ScalarMap.scaleValues(double[]): values[0] = " + values[0] + "; new_values[0] = " + new_values[0]); */ return new_values; } /** return an array of display (DisplayRealType) values by * linear scaling (if applicable) the data_values array * (RealType values); results are scaled by the given scale factor * @param values to scale as bytes * @return array of display values */ public byte[] scaleValues(byte[] values, int factor) throws VisADException { if (values == null) return null; byte[] new_values = null; if (badRange()) { new_values = new byte[values.length]; } else { if (overrideUnit != null && !overrideUnit.equals(((RealType) Scalar).getDefaultUnit())) { throw new VisADException( "scaleValues(byte[]): non-default units not supported"); } if (isScaled) { new_values = new byte[values.length]; for (int i=0; i<values.length; i++) { float v = (float) values[i]; if (v < 0) v += 256; v = (float) (factor * (offset + scale * v)); if (v < 0) v = 0; else if (v > 255) v = 255; new_values[i] = (byte) v; } } else { new_values = values; } } return new_values; } /** return an array of data (RealType) values by inverse * linear scaling (if applicable) the display_values array * (DisplayRealType values); this is useful for direct * manipulation and cursor labels * @param values display values * @return data values */ public float[] inverseScaleValues(float[] values) { return inverseScaleValues(values, true); } /** return an array of data (RealType) values by inverse * linear scaling (if applicable) the display_values array * (DisplayRealType values); this is useful for direct * manipulation and cursor labels * @param values display values * @param newArray false to transform in place * @return data values */ public float[] inverseScaleValues(float[] values, boolean newArray) { if (values == null) return null; float[] new_values = (newArray) ? new float[values.length] : values; if (isScaled) { for (int i=0; i<values.length; i++) { if (values[i] == values[i]) { new_values[i] = (float) ((values[i] - offset) / scale); } else { new_values[i] = Float.NaN; } } } else { if (newArray) { for (int i=0; i<values.length; i++) { new_values[i] = values[i]; } } } // WLH 31 Aug 2000 if (overrideUnit != null) { // float[] old_values = new_values; try { new_values = overrideUnit.toThat(new_values, ((RealType) Scalar).getDefaultUnit(), false); // already copied above } catch (UnitException e) { } /* System.out.println("inverse values = " + values[0] + " " + old_values[0] + " " + new_values[0]); */ } return new_values; } /** ensure that non-Manual components of flow_tuple have equal dataRanges symmetric about 0.0 */ public static void equalizeFlow(Vector mapVector, DisplayTupleType flow_tuple) throws VisADException, RemoteException { double[] range = new double[2]; double low = Double.MAX_VALUE; double hi = -Double.MAX_VALUE; boolean anyAuto = false; Enumeration maps = mapVector.elements(); while(maps.hasMoreElements()) { ScalarMap map = ((ScalarMap) maps.nextElement()); DisplayRealType dtype = map.getDisplayScalar(); DisplayTupleType tuple = dtype.getTuple(); if (flow_tuple.equals(tuple) && !map.isManual && !map.badRange()) { anyAuto = true; low = Math.min(low, map.dataRange[0]); hi = Math.max(hi, map.dataRange[1]); } } if (!anyAuto) return; hi = Math.max(hi, -low); low = -hi; maps = mapVector.elements(); while(maps.hasMoreElements()) { ScalarMap map = ((ScalarMap) maps.nextElement()); DisplayRealType dtype = map.getDisplayScalar(); DisplayTupleType tuple = dtype.getTuple(); if (flow_tuple.equals(tuple) && !map.isManual && !map.badRange()) { map.setRange(null, low, hi, false); } } } /** Get index of DisplayScalar in display.DisplayRealTypeVector */ int getDisplayScalarIndex() { return DisplayScalarIndex; } /** get index of Scalar in display.RealTypeVector */ int getScalarIndex() { return ScalarIndex; } /** set index of Scalar in display.RealTypeVector */ void setScalarIndex(int index) { ScalarIndex = index; } /** set index of DisplayScalar in display.DisplayRealTypeVector */ void setDisplayScalarIndex(int index) { DisplayScalarIndex = index; } /** set index of DisplayScalar in value array used by ShadowType.doTransform */ public void setValueIndex(int index) { ValueIndex = index; } /** get index of DisplayScalar in value array used by ShadowType.doTransform */ public int getValueIndex() { return ValueIndex; } /** * Compares this ScalarMap with another object. * @param o The other object. * @return A value that is negative, zero, or positive * depending on whether this instance is less * than, equal to, or greater than the other * object, respectively. * @throws ClassCastException if the other object isn't a {@link ScalarMap}. * @throws NullPointerException if the other object is <code>null</code>. */ public int compareTo(Object o) { return -((ScalarMap)o).compareTo(this); } /** * Compares this ScalarMap with another ScalarMap. The ScalarType-s are * first compared; if they compare equal, then the DisplayRealType-s are * compared. * @param that The other ScalarMap. * @return A value that is negative, zero, or positive depending on * whether this ScalarMap is considered less than, equal * to, or greater than the other ScalarMap, respectively. */ protected int compareTo(ScalarMap that) { int comp = getScalar().compareTo(that.getScalar()); if (comp == 0) comp = getDisplayScalar().compareTo(that.getDisplayScalar()); return comp; } /** * Indicates if this ScalarMap is the same as another object. * @param o The other object. * @return <code>true</code> if and only if the other object is a * ScalarMap and compares equal to this ScalarMap. */ public boolean equals(Object o) { return o instanceof ScalarMap && compareTo(o) == 0; } /** * Returns the hash code for this ScalarMap. If <code>scalarMap1.equals( * scalarMap2)</code> is true, then <code>scalarMap1.hashCode() == * scalarMap2.hashCode()</code>. * @return The hash code for this ScalarMap. */ public int hashCode() { ScalarType s = getScalar(); DisplayRealType ds = getDisplayScalar(); int hash = 0; if (s != null) { if (ds != null) { hash = s.hashCode() ^ ds.hashCode(); } else { hash = s.hashCode(); } } else if (ds != null) { hash = ds.hashCode(); } return hash; } /** * Create and return a copy of this ScalarMap. * @return copy of the ScalarMap or <CODE>null</CODE> if a copy couldn't * be created. */ public Object clone() { try { ScalarMap sm = new ScalarMap(Scalar, DisplayScalar); copy(sm); return sm; } catch (Exception e) { return null; } } protected void copy(ScalarMap map) throws VisADException, RemoteException { map.isScaled = isScaled; map.isManual = isManual; map.dataRange[0] = dataRange[0]; map.dataRange[1] = dataRange[1]; map.defaultUnitRange[0] = defaultUnitRange[0]; map.defaultUnitRange[1] = defaultUnitRange[1]; map.displayRange[0] = displayRange[0]; map.displayRange[1] = displayRange[1]; map.scale = scale; map.offset = offset; map.axisScale = (axisScale != null) ? axisScale.clone(map) : null; map.scale_flag = scale_flag; map.back_scale_flag = back_scale_flag; if (map.display != null) { map.setControl(); } } /** * Returns a string representation of the ScalarMap. * @return a string that "textually represents" this ScalarMap. */ public String toString() { return toString(""); } /** * Returns a string representation of the ScalarMap with the specified * prefix prepended. * @param pre prefix to prepend to the representation * @return a string that "textually represents" this ScalarMap with * <CODE>pre</CODE> prepended. */ public String toString(String pre) { return pre + "ScalarMap: " + Scalar.toString() + " -> " + DisplayScalar.toString() + "\n"; } /** * Get the AxisScale associated with this ScalarMap. * @return the AxisScale or null if not a spatial ScalarMap */ public AxisScale getAxisScale() { return axisScale; } /** * set aspect ratio of XAxis, YAxis & ZAxis in ScalarMaps rather * than matrix (i.e., don't distort text fonts); * won't work for spherical, polar, cylindrical coordinates * @param aspect ratios; 3 elements for Java3D, 2 for Java2D * @throws VisADException a VisAD error occurred * @throws RemoteException an RMI error occurred */ void setAspectCartesian(double[] aspect) throws VisADException, RemoteException { double asp = Double.NaN; if (DisplayScalar.equals(Display.XAxis)) asp = aspect[0]; if (DisplayScalar.equals(Display.YAxis)) asp = aspect[1]; if (DisplayScalar.equals(Display.ZAxis)) asp = aspect[2]; if (asp == asp) { isScaled = DisplayScalar.getRange(displayRange); displayRange[0] *= asp; displayRange[1] *= asp; computeScaleAndOffset(); makeScale(); // needs work in AxisScale **** } // note XAxis, YAxis and ZAxis have no unit, so cannot be setRangeByUnits() } }