Re: Image plot colour scale

Hello Oliver,

at the risk of confusing you, I have simplified one of our classes
to provide you with a signal-strength scale (SignalStrengthScale.java)

I haven't tested the code (at least you don't have to worry about
compile-time errors), so I can't guarantee that it will work for you.

It assumes that your function type is:
(scanNumber, frequency) --> (signal-strength)

and the constructor takes a scalar map to each of these.

This was based on a rain-rate scale that we have written. We
can change the colour of the rain-rate via a LabeledColorWidget,
and when we do, the data as well as the rain-rate scale update.

Even if the code doesn't work for you, I hope you can get some
ideas from it.

Hope this helps,
Jim.
---
Jim Koutsovasilis
Bureau of Meteorology, Australia
jimk@xxxxxxxxxx



Oliver Mather wrote:

Hi there,
I have an image plot which shows the magnitude of the data using an RGB
map. http://mcba5.phys.unsw.edu.au:8180/~oliverm/ImagePlot.jpg

The user can modify the ranges of the plot scales, thus dynamically
changing the value each colour represents.
I?d like to add a colour scale next to the plot so that it always shows
the correct values the colous represents.This is because in most cases
the plot is saved as a GIF and printed for analysis later?
Is there a builtin feature of visad to do this ?


Thanks,
Oliver Mather
Software Engineer
Ph 04040 71153
omather@xxxxxxxxxx






// SignalStrengthScale.java

// Java
import java.awt.Component;
import java.awt.Font;
import java.awt.Rectangle;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.geom.Point2D;
import java.rmi.RemoteException;
import java.util.Vector;
import java.util.prefs.Preferences;
import javax.swing.Icon;
import javax.swing.JComponent;

// VisAD
import visad.ColorAlphaControl;
import visad.ConstantMap;
import visad.DataReferenceImpl;
import visad.Display;
import visad.DisplayEvent;
import visad.DisplayImpl;
import visad.DisplayListener;
import visad.DisplayRenderer;
import visad.FieldImpl;
import visad.FunctionType;
import visad.Gridded2DDoubleSet;
import visad.Linear1DSet;
import visad.MouseBehavior;
import visad.Real;
import visad.RealType;
import visad.RealTupleType;
import visad.ScalarMap;
import visad.Set;
import visad.ShapeControl;
import visad.Text;
import visad.TextControl;
import visad.TextType;
import visad.VisADException;
import visad.VisADRay;
import visad.VisADGeometryArray;
import visad.VisADTriangleArray;
import visad.java3d.DefaultRendererJ3D;
import visad.java3d.DisplayRendererJ3D;
import visad.java3d.MouseBehaviorJ3D;
import visad.java3d.ProjectionControlJ3D;
import visad.util.ColorChangeEvent;
import visad.util.ColorChangeListener;
import visad.util.ColorMap;
import visad.util.LabeledColorWidget;


/**
 * Provides a signal strength scale for the function type:
 * (scan number, frequency)->(signal strength)
 *
 * The scale is drawn in the lower-left corner of the display.
 */
public final class SignalStrengthScale
{

        /**
         * The z value (height) of this layer.
         */
        private double zValue = -0.01;

        /**
         * The visad display we'll be adding this layer to.
         */
        private DisplayImpl display;

        /**
         * This data renderer for the scale.
         */
        private DefaultRendererJ3D scaleRenderer;

        /**
         * Maintains a reference to the visad data.
         */
        private DataReferenceImpl dataRef;

        /**
         * The frequency scalar map.
         */
        private ScalarMap frequencyMap;

        /**
         * The scan-number scalar map.
         */
        private ScalarMap scanNumberMap;

        /**
         * The signal-strength scalar map.
         */
        private ScalarMap signalStrengthMap;

        /**
         * The shape scalar map
         */
        private ScalarMap shapeMap;

        /**
         * The domain type (lat,lon)
         */
        private RealTupleType domainType;

        /**
         * The range type (shapeType)
         */
        private RealType shapeType;

        /**
         * If true, the first Display.TRANSFORM_DONE event has occurred.
         */
        private boolean firstTransformDone = false;

        /**
         * If true, the first Display.FRAME_DONE event has occurred.
         */
        private boolean firstFrameDone = false;

        /**
         * The width of the scale, as a percentage of the screen's width.
         */
        private float widthRatio = 0.35f;

        /**
         * The height of the scale, as a percentage of the screen's height.
         */
        private float heightRatio = 0.03f;

        /**
         * The offset from the screen corner for the signal-strength scale
         */
        private int offset = 10;

        /**
         * Holds the data coordinates (lat/lon) for the scale and its
         * labels.
         */
        private DataCoords dataCoords;

        /**
         * The font size to use for the scale's labels.
         */
        private int fontSize = 6;

        /**
         * The font to use for the scale's labels.
         */
        private String fontName = "Fast Font";

        /**
         * Holds data relating to the left-aligned labels.
         */
        private LabelType leftAlignedLabelType;

        /**
         * Holds data relating to the centred labels.
         */
        private LabelType centredLabelType;

        /**
         * Holds data relating to the right-aligned labels.
         */
        private LabelType rightAlignedLabelType;

        /**
         * Listens for changes to the Display.RGBA scalar map.
         */
        private ColourListener colourChangeListener;

        /**
         * This triangle array defines the signal-strength scale.
         */
        private VisADTriangleArray triangleArray;

        /**
         * Listens for changes to the visad window's size.
         */
        private CanvasResizeListener canvasResizeListener;

        /**
         * If true, the visad window has been resized.
         */
        private boolean resizedCanvas = false;

        /**
         * The number of canvas resize events we are going to process.
         */
        private int numCanvasResizeEvents = 0;


        /**
         * Constructor.
         *
         * @param display the visad display that this layer will be added to
         * @param frequencyMap the frequency scalar map
         * @param scanNumberMap the scan-number scalar map
         * @param signalStrenthMap the signal-strength scalar map
         *
         * @throws VisADException there's a problem with VisAD
         * @throws RemoteException there's a problem with VisAD
         */
        public SignalStrengthScale(final Display display,
                final ScalarMap scanNumberMap, final ScalarMap frequencyMap,
                final ScalarMap signalStrengthMap)
                throws VisADException, RemoteException
        {

                this.display = (DisplayImpl) display;
                this.frequencyMap = frequencyMap;
                this.scanNumberMap = scanNumberMap;
                this.signalStrengthMap = signalStrengthMap;

                domainType = RealTupleType.LatitudeLongitudeTuple;
                shapeType = RealType.getRealType("colour_shape");

                leftAlignedLabelType = LabelType.createLeftAligned();
                centredLabelType = LabelType.createCentred();
                rightAlignedLabelType = LabelType.createRightAligned();

                dataRef = new DataReferenceImpl("signalstrengthscale_data_ref");

                colourChangeListener = new ColourListener();
                LabeledColorWidget colourWidget = 
                        new LabeledColorWidget(signalStrengthMap);
                final ColorMap colourMap = colourWidget.getBaseMap();
                colourMap.addColorChangeListener(colourChangeListener);
                colourWidget = null;

                final DisplayRendererJ3D displayRenderer = (DisplayRendererJ3D) 
                        this.display.getDisplayRenderer();
                final Component canvas = displayRenderer.getCanvas();
                canvasResizeListener = new CanvasResizeListener();
                canvas.addComponentListener(canvasResizeListener);

        } // SignalStrengthScale.SignalStrengthScale()


        /**
         * Add scalar maps to the display
         *
         * @param display scalar maps are added to this display.
         *
         * @throws RemoteException there's a problem with VisAD
         * @throws VisADException there's a problem with VisAD
         */
        public void addScalarMaps(Display display)
                throws RemoteException, VisADException
        {

                shapeMap = new ScalarMap(shapeType, Display.Shape);
                display.addMap(shapeMap);

                display.addMap(leftAlignedLabelType.map);
                display.addMap(centredLabelType.map);
                display.addMap(rightAlignedLabelType.map);

                TextControl control = (TextControl) 
                        leftAlignedLabelType.map.getControl();
                control.setVerticalJustification(TextControl.Justification.TOP);
                control.setJustification(TextControl.Justification.LEFT);

                control = (TextControl) centredLabelType.map.getControl();
                control.setVerticalJustification(TextControl.Justification.TOP);
                control.setJustification(TextControl.Justification.CENTER);

                control = (TextControl) rightAlignedLabelType.map.getControl();
                control.setVerticalJustification(TextControl.Justification.TOP);
                control.setJustification(TextControl.Justification.RIGHT);

                ((DisplayImpl) display).addDisplayListener(
                        new DisplayChangedListener());

        } // SignalStrengthScale.addScalarMaps()


        /**
         * Add data references from the display.
         *
         * @param display data references are added to this display.
         *
         * @throws RemoteException there's a problem with VisAD
         * @throws VisADException there's a problem with VisAD
         */
        public void addDataReferences(Display display)
                throws RemoteException, VisADException
        {

                // Add the scale data reference to the display.
                ConstantMap [] maps = new ConstantMap [] {
                        new ConstantMap(zValue, Display.ZAxis)
                        };
                scaleRenderer = new DefaultRendererJ3D();
                display.addReferences(scaleRenderer, dataRef, maps);

                // Add the scale labels data reference to the display.
                maps = new ConstantMap [] {
                        new ConstantMap(zValue, Display.ZAxis),
                        new ConstantMap(0, Display.Red),
                        new ConstantMap(0, Display.Green),
                        new ConstantMap(0, Display.Blue),
                        };
                leftAlignedLabelType.renderer = 
                        new DefaultRendererJ3D();
                display.addReferences(leftAlignedLabelType.renderer,
                        leftAlignedLabelType.dataRef, maps);

                maps = new ConstantMap [] {
                        new ConstantMap(zValue, Display.ZAxis),
                        new ConstantMap(0, Display.Red),
                        new ConstantMap(0, Display.Green),
                        new ConstantMap(0, Display.Blue),
                        };
                centredLabelType.renderer = 
                        new DefaultRendererJ3D();
                display.addReferences(centredLabelType.renderer,
                        centredLabelType.dataRef, maps);

                maps = new ConstantMap [] {
                        new ConstantMap(zValue, Display.ZAxis),
                        new ConstantMap(0, Display.Red),
                        new ConstantMap(0, Display.Green),
                        new ConstantMap(0, Display.Blue),
                        };
                rightAlignedLabelType.renderer = 
                        new DefaultRendererJ3D();
                display.addReferences(rightAlignedLabelType.renderer,
                        rightAlignedLabelType.dataRef, maps);

        } // SignalStrengthScale.addDataReferences()


        /**
         * Retrieves the data values that correspond to the specified
         * screen location.
         *
         * @param mouseBehaviour the display renderer's mouse behavior
         * @param screenX the screen X coordinate
         * @param screenY the screen Y coordinate
         * @param latlon used to store the result; can be null.
         *
         * @return the data values that correspond to the specified
         *      screen location.
         */
         private Point2D.Double screenToDataCoords(
                final MouseBehavior mouseBehaviour, final int screenX, 
                final int screenY, Point2D.Double latLon)
        {
        
                final VisADRay ray = mouseBehaviour.findRay(screenX, screenY);
                final double [] cursor = new double[3]; 
                
                // X
                cursor[0] = ray.position[0];

                // Y
                cursor[1] = ray.position[1];

                // Z
                cursor[2] = ray.position[2];

                final double [] dummy1 = new double[2];
                final double [] dummy2 = new double[2];
                final double [] scaleOffset = new double[2];

                scanNumberMap.getScale(scaleOffset, dummy1, dummy2);
                final double lon = (cursor[0] - scaleOffset[1]) /
                        scaleOffset[0];

                frequencyMap.getScale(scaleOffset, dummy1, dummy2);
                final double lat = (cursor[1] - scaleOffset[1]) /
                        scaleOffset[0];

                if (null == latLon) {
                        latLon = new Point2D.Double();
                }

                latLon.x = lon;
                latLon.y = lat;

                return latLon;

        } // SignalStrengthScale.screenToDataCoords()


        /**
         * Creates a visad triangle array for the signal strength scale.
         *
         * @param height the height of the scale
         * @param width the width of the scale
         *
         * @return a visad triangle array that makes up the signal strength
         *      scale
         * @throws VisADException there's a problem with VisAD
         * @throws RemoteException there's a problem with VisAD
         */
        private VisADTriangleArray createTriangles(final float height, 
                final float width) throws VisADException, RemoteException
        {

                // Get the colour table used by the signalStrength scalar map.
                final ColorAlphaControl colourControl = 
                        (ColorAlphaControl) signalStrengthMap.getControl();
                final float [][] colourTable = colourControl.getTable();
                final int numColours = colourTable[0].length;

                final float delta = width / (float) numColours;
                final int numPointsPerTriangle = 3;
                final int numValuesPerPoint = 3;
                final int numTriangles = numColours * 2;
                final float [] triangles = new float[numTriangles *
                        numPointsPerTriangle * numValuesPerPoint];
                final byte [] colours = new byte[numTriangles *
                        numPointsPerTriangle * numValuesPerPoint];

                int index = 0;
                int colourIndex = 0;

                for (int i = 0; i < numColours; ++i) {

                        final byte red = (byte) (colourTable[0][i] * 255);
                        final byte green = (byte) (colourTable[1][i] * 255);
                        final byte blue = (byte) (colourTable[2][i] * 255);


                        // The right-half triangle
                        // 1     2
                        // .......
                        //  .    .
                        //   .   .
                        //    .  .
                        //     . .
                        //      ..
                        //       .
                        //       3

                        // First point in triangle
                        triangles[index++] = delta * i;
                        triangles[index++] = 0.0f;
                        triangles[index++] = 0.0f;

                        colours[colourIndex++] = red;
                        colours[colourIndex++] = green;
                        colours[colourIndex++] = blue; 

                        // Second point in triangle
                        triangles[index++] = (delta * i) + delta;
                        triangles[index++] = 0.0f;
                        triangles[index++] = 0.0f;

                        colours[colourIndex++] = red;
                        colours[colourIndex++] = green;
                        colours[colourIndex++] = blue;

                        // Third point in triangle
                        triangles[index++] = (delta * i) + delta;
                        triangles[index++] = height;
                        triangles[index++] = 0.0f;

                        colours[colourIndex++] = red;
                        colours[colourIndex++] = green;
                        colours[colourIndex++] = blue;


                        // The left-half triangle
                        // 1
                        // .
                        // ..
                        // . .
                        // .  .
                        // .   .
                        // .    .
                        // .......
                        // 3     2

                        // First point in triangle
                        triangles[index++] = delta * i;
                        triangles[index++] = 0.0f;
                        triangles[index++] = 0.0f;

                        colours[colourIndex++] = red;
                        colours[colourIndex++] = green;
                        colours[colourIndex++] = blue;

                        // Second point in triangle
                        triangles[index++] = (delta * i) + delta;
                        triangles[index++] = height;
                        triangles[index++] = 0.0f;

                        colours[colourIndex++] = red;
                        colours[colourIndex++] = green;
                        colours[colourIndex++] = blue; 

                        // Third point in triangle
                        triangles[index++] = delta * i;
                        triangles[index++] = height;
                        triangles[index++] = 0.0f;

                        colours[colourIndex++] = red;
                        colours[colourIndex++] = green;
                        colours[colourIndex++] = blue;

                } // for (i<numColours)


                // Set all the normal vectors to (0,0,1) for each
                // vertex of each triangle.

                final float [] normals = new float[numTriangles *
                        numPointsPerTriangle * numValuesPerPoint];

                index = 0;
                for (int i = 0; i < numTriangles; ++i) {

                        // First point in triangle.
                        normals[index++] = 0.0f;
                        normals[index++] = 0.0f;
                        normals[index++] = 1.0f;

                        // Second point in triangle.
                        normals[index++] = 0.0f;
                        normals[index++] = 0.0f;
                        normals[index++] = 1.0f;

                        // Third point in triangle.
                        normals[index++] = 0.0f;
                        normals[index++] = 0.0f;
                        normals[index++] = 1.0f;

                } // for (i<numTriangles)

                final VisADTriangleArray triangleArray
                        new VisADTriangleArray();
                triangleArray.coordinates = triangles;
                triangleArray.normals = normals;
                triangleArray.colors = colours;
                triangleArray.vertexCount = numTriangles * numPointsPerTriangle;

                return triangleArray;

        } // SignalStrengthScale.createTriangles()

        
        /**
         * Allows us to listen to Display events.
         */
        private class DisplayChangedListener implements DisplayListener
        {

                /**
                 * A display changed event has occurred, so determine
                 * if we can draw the scale.
                 *
                 * @param event contains information about this event
                 *
                 * @throws VisADException there's a problem with VisAD
                 * @throws RemoteException there's a problem with VisAD
                 */
                public void displayChanged(DisplayEvent event)
                        throws VisADException, RemoteException
                {

                        switch (event.getId()) {

                        case DisplayEvent.FRAME_DONE:
                                if (!firstTransformDone) {
                                        return;
                                }

                                if (!firstFrameDone) {
                                        redraw();
                                        firstFrameDone = true;
                                        return;
                                }

                                if (resizedCanvas) {

                                        // The canvas has been resized
                                        // and there are more events to
                                        // process.
                                        --numCanvasResizeEvents;
                                        if (0 == numCanvasResizeEvents) {
                                                resizedCanvas = false;
                                        }
                                        redraw();

                                } // if (resizedCanvas)

                                break;

                        case DisplayEvent.TRANSFORM_DONE:
                                firstTransformDone = true;
                                break;

                        case DisplayEvent.MOUSE_RELEASED_LEFT:
                                // The user has zoomed or panned, so
                                // redraw the scale.
                                redraw();
                                break;

                        } // switch()

                } // DisplayChangedListener.displayChanged()

        } // class SignalStrengthScale.DisplayChangedListener


        /**
         * Creates the visad field and all the data that is used
         * by the signal strength scale.
         *
         * @return a visad field that contains all the scale data
         *
         * @throws VisADException there's a problem with VisAD
         * @throws RemoteException there's a problem with VisAD
         */
        private FieldImpl createField() throws VisADException, RemoteException
        {

                final double deltaLat = Math.abs(
                        dataCoords.bottomRightOfScale.y -
                        dataCoords.topLeftOfScale.y);
                final double deltaLon = Math.abs(
                        dataCoords.bottomRightOfScale.x -
                        dataCoords.topLeftOfScale.x);

                // This scale was found experimentally, and it seems
                // to work well for all occassions.
                final float scale = 0.0055f;

                /*
                final VisADTriangleArray triangleArray
                        createTriangles((float) deltaLat * scale, 
                                (float) deltaLon * scale);
                */
                triangleArray = createTriangles((float) deltaLat * scale, 
                                (float) deltaLon * scale);

                // Setup the shape control with the new triangle array shape.
                final ShapeControl shapeControl = 
                        (ShapeControl) shapeMap.getControl();
                shapeControl.setShapeSet(new Linear1DSet(0, 1, 1));
                final VisADGeometryArray [] shapes = new VisADGeometryArray[] {
                        triangleArray };
                shapeControl.setShapes(shapes);

                // Place the shape at the lower left corner of the screen.
                final double [][] domainSamples = new double[2][1];
                domainSamples[0][0] = dataCoords.bottomRightOfScale.y;
                domainSamples[1][0] = dataCoords.topLeftOfScale.x;
                final Set domainSet = new Gridded2DDoubleSet(domainType,
                        domainSamples, 1);

                final FunctionType functionType = new FunctionType(
                        domainType, shapeType);
                final FieldImpl field = new FieldImpl(functionType, domainSet);
                field.setSample(0, new Real(shapeType, 0));

                return field;

        } // SignalStrengthScale.createField()


        /**
         * Creates the field and data for the scale labels.
         *
         * @throws VisADException there's a problem in VisAD
         * @throws RemoteException there's a problem in VisAD
         */
        private void setupLabelData()
                throws VisADException, RemoteException
        {

                double [][] domainSamples = new double[2][1];
                domainSamples[0][0] = dataCoords.leftLabel.y;
                domainSamples[1][0] = dataCoords.leftLabel.x;
                Gridded2DDoubleSet domainSet = new Gridded2DDoubleSet(
                        domainType, domainSamples, 1);
                FunctionType functionType = new FunctionType(domainType,
                        leftAlignedLabelType.type);
                FieldImpl field = new FieldImpl(functionType, domainSet);
                field.setSample(0, new Text(leftAlignedLabelType.type,
                        "Light"));
                leftAlignedLabelType.dataRef.setData(field);

                domainSamples[0][0] = dataCoords.centreLabel.y;
                domainSamples[1][0] = dataCoords.centreLabel.x;
                domainSet = new Gridded2DDoubleSet(domainType,
                        domainSamples, 1);
                functionType = new FunctionType(domainType,
                        centredLabelType.type);
                field = new FieldImpl(functionType, domainSet);
                field.setSample(0, new Text(centredLabelType.type,
                        "Moderate"));
                centredLabelType.dataRef.setData(field);

                domainSamples[0][0] = dataCoords.rightLabel.y;
                domainSamples[1][0] = dataCoords.rightLabel.x;
                domainSet = new Gridded2DDoubleSet(domainType,
                        domainSamples, 1);
                functionType = new FunctionType(domainType,
                        rightAlignedLabelType.type);
                field = new FieldImpl(functionType, domainSet);
                field.setSample(0, new Text(rightAlignedLabelType.type,
                        "Heavy"));
                rightAlignedLabelType.dataRef.setData(field);

                formatLabels();

        } // SignalStrengthScale.setupLabelData()


        /**
         * Setup the data coordinates for the scale and its labels.
         */
        private DataCoords setupDataCoords()
        {

                // ........................................ 
                // .                                      .
                // ........................................ 
                // |                  |                   |
                // Light           Moderate           Heavy
                //
                // 
                final Rectangle screenRect = display.getComponent().getBounds();
                final int labelOffset = 5;

                final int scaleWidth = (int) (screenRect.width * widthRatio);
                final int scaleHeight = (int) (screenRect.height * heightRatio);

                final int left = offset;
                final int right = left + scaleWidth;

                final int labelTop = screenRect.height - offset -
                        (2 * fontSize); 
                final int labelCentre = left + ((right - left) / 2);
                final int labelLeft = left;
                final int labelRight = right;

                final int bottom = labelTop - labelOffset;
                final int top = bottom - scaleHeight;

                final DisplayRenderer displayRenderer = 
                        display.getDisplayRenderer();
                final MouseBehavior mouseBehaviour = 
                        displayRenderer.getMouseBehavior();
                final Point2D.Double topLeft = screenToDataCoords(
                        mouseBehaviour, 
                        left, top, null);
                final Point2D.Double bottomRight = screenToDataCoords(
                        mouseBehaviour, right, bottom, null);

                final DataCoords coordinates = new DataCoords();

                coordinates.topLeftOfScale = screenToDataCoords(mouseBehaviour,
                        left, top, null);
                coordinates.bottomRightOfScale = screenToDataCoords(
                        mouseBehaviour, right, bottom, null);
                        
                coordinates.leftLabel = screenToDataCoords(mouseBehaviour,
                        labelLeft, labelTop, null);
                coordinates.centreLabel = screenToDataCoords(mouseBehaviour,
                        labelCentre, labelTop, null);
                coordinates.rightLabel = screenToDataCoords(mouseBehaviour,
                        labelRight, labelTop, null);

                return coordinates;

        } // SignalStrengthScale.setupDataCoords()


        /**
         * Wraps up the data coordinates (lat,lon) of the scale
         * and its labels.
         */
        private static class DataCoords
        {

                /**
                 * The top left corner of the scale.
                 */
                private Point2D.Double topLeftOfScale;

                /**
                 * The bottom right corner of the scale.
                 */
                private Point2D.Double bottomRightOfScale;

                /**
                 * The coordinate of the "Light" label. 
                 */
                private Point2D.Double leftLabel;

                /**
                 * The corrdinate of the "Moderate" label.
                 */
                private Point2D.Double centreLabel;

                /**
                 * The coordinate of the "Heavy" label.
                 */
                private Point2D.Double rightLabel;

        } // class SignalStrengthScale.DataCoords


        /**
         * Formats the labels so they are centred, top justified, and
         * have a small font size.
         *
         * @throws VisADException there's a problem with VisAD
         * @throws RemoteException there's a problem with VisAD
         */
        private void formatLabels() throws VisADException, RemoteException
        {


                // To set the correct font size, we have to undo
                // the current level of zoom, and then set our
                // font size.
                final ProjectionControlJ3D projectionControl = 
                        (ProjectionControlJ3D)
                        this.display.getProjectionControl();
                final double [] scale = new double[1];
                final double [] translation = new double[3];
                final double [] rotation = new double[3];
                MouseBehaviorJ3D.unmake_matrix(rotation, scale,
                        translation, projectionControl.getMatrix());

                final double baseFontSize = 14;
                Font font = null;
                if (!fontName.equals("Fast Font")) {
                        font = new Font(fontName, Font.PLAIN, fontSize);
                }

                TextControl textControl = (TextControl)
                        leftAlignedLabelType.map.getControl();
                textControl.setSize((fontSize / baseFontSize) / scale[0]);
                textControl.setFont(font);

                textControl = (TextControl)
                        centredLabelType.map.getControl();
                textControl.setSize((fontSize / baseFontSize) / scale[0]);
                textControl.setFont(font);

                textControl = (TextControl)
                        rightAlignedLabelType.map.getControl();
                textControl.setSize((fontSize / baseFontSize) / scale[0]);
                textControl.setFont(font);

        } // SignalStrengthScale.formatLabels()


        /**
         * Wraps up data for a particularly-aligned Text control.
         */
        private static class LabelType 
        {

                /**
                 * Creates a left-aligned label type.
                 *
                 * @throws VisAException there's a problem with VisAD
                 */
                public static LabelType createLeftAligned()
                        throws VisADException
                {
                        
                        return new LabelType(TextControl.Justification.LEFT);

                } // LabelType.createLeftAligned()


                /**
                 * Creates a centred label type.
                 *
                 * @throws VisAException there's a problem with VisAD
                 */
                public static LabelType createCentred()
                        throws VisADException
                {

                        return new LabelType(TextControl.Justification.CENTER);

                } // LabelType.createCentred()


                /**
                 * Creates a right-aligned label type.
                 *
                 * @throws VisAException there's a problem with VisAD
                 */
                public static LabelType createRightAligned()
                        throws VisADException
                {

                        return new LabelType(TextControl.Justification.RIGHT);

                } // LabelType.createRightAligned()


                /**
                 * Private constructor means only factory methods can
                 * create an instance of this class.
                 *
                 * @param justification the horizontal justification for
                 *      this label.
                 * @throws VisADException there's a problem with VisAD
                 */
                private LabelType(TextControl.Justification justification)
                        throws VisADException
                {

                        String name = null;

                        if (TextControl.Justification.LEFT == justification) {
                                name = "left-aligned";
                        } else if (TextControl.Justification.CENTER =
                                justification)
                        {
                                name = "centred";
                        } else if (TextControl.Justification.RIGHT =
                                justification)
                        {
                                name = "right-aligned";
                        } else {
                                throw new IllegalArgumentException(
                                        "Bad type of horizontal justification");
                        }

                        type = TextType.getTextType(name);
                        map = new ScalarMap(type, Display.Text);
                        dataRef = new DataReferenceImpl(name);

                } // LabelTypel.LabelType()


                /**
                 * The text type for this type of label. 
                 */
                private TextType type;

                /**
                 * The scalar map that maps "type" to Display.Text
                 */
                private ScalarMap map;

                /**
                 * The data reference.
                 */
                private DataReferenceImpl dataRef;

                /**
                 * The label's renderer.
                 */
                private DefaultRendererJ3D renderer;

        } // class SignalStrengthScale.LabelType


        /**
         * Listens for colour changes in the scalar map:
         * RealType.signalStrength --> Display.RGBA
         */
        private class ColourListener implements ColorChangeListener
        {

                /**
                 * The colour table has changed, so update the signal-strength
                 * scale.
                 */
                public synchronized void colorChanged(ColorChangeEvent event)
                {
                
                        if (firstFrameDone) {

                                Exception exception = null;

                                try {
                                        redraw();
                                } catch (VisADException ex) {
                                        exception = ex;
                                } catch (RemoteException ex) {
                                        exception = ex;
                                }

                                if (exception != null) {
                                        System.err.println(
                                                "SignalStrengthScale." +
                                                "ColourListener.colorChanged:" +
                                                " could not update colours");
                                }

                        } // if (firstFrameDone)

                } // ColourListener.colorChanged()

        } // class SignalStrengthScale.ColourListener

        
        /**
         * When the visad window is resized, we need to redraw the
         * signal-strength scale.
         */
        private class CanvasResizeListener extends ComponentAdapter
        {
        
                /**
                 * The visad window has been resized, so redraw.
                 */
                public synchronized void componentResized(ComponentEvent event)
                {
                        
                        if (!firstFrameDone) {
                                // We're not ready to handle resize events
                                // just yet.
                                return;
                        }

                        // We'll process the first two FRAME_DONE events
                        numCanvasResizeEvents = 2;
                        resizedCanvas = true;

                } // CanvasResizeListener.componentResized()

        } // class SignalStrengthScale.CanvasResizeListener

        
        /**
         * Redraws the signal-strength scale and the labels.
         *
         * @throws VisADException there's a problem in VisAD
         * @throws RemoteException there's a problem in VisAD
         */
        private synchronized void redraw()
                throws VisADException, RemoteException
        {

                dataCoords = setupDataCoords();
                dataRef.setData(createField());
                setupLabelData();

        } // SignalStrengthScale.redraw()

} // class SignalStrengthScale