001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2003-2006, Geotools Project Managment Committee (PMC)
005: * (C) 2000, Institut de Recherche pour le Développement
006: * (C) 1999, Pêches et Océans Canada
007: *
008: * This library is free software; you can redistribute it and/or
009: * modify it under the terms of the GNU Lesser General Public
010: * License as published by the Free Software Foundation; either
011: * version 2.1 of the License, or (at your option) any later version.
012: *
013: * This library is distributed in the hope that it will be useful,
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
016: * Lesser General Public License for more details.
017: */
018: package org.geotools.gui.swing.image;
019:
020: // Graphical user interface
021: import javax.swing.JComponent;
022: import javax.swing.SwingConstants;
023: import javax.swing.plaf.ComponentUI;
024:
025: // Graphics
026: import java.awt.Font;
027: import java.awt.Color;
028: import java.awt.Graphics;
029: import java.awt.Graphics2D;
030: import java.awt.RenderingHints;
031: import java.awt.image.BufferedImage;
032: import java.awt.image.IndexColorModel;
033: import java.awt.font.FontRenderContext;
034: import java.awt.font.GlyphVector;
035:
036: // Geometry
037: import java.awt.Dimension;
038: import java.awt.Rectangle;
039: import java.awt.geom.Rectangle2D;
040:
041: // Miscellaneous
042: import java.util.Arrays;
043: import java.util.logging.Level;
044: import java.util.logging.Logger;
045: import java.beans.PropertyChangeEvent;
046: import java.beans.PropertyChangeListener;
047: import javax.units.Unit;
048:
049: // OpenGIS dependencies
050: import org.opengis.coverage.Coverage;
051: import org.opengis.coverage.SampleDimension;
052: import org.opengis.coverage.PaletteInterpretation;
053: import org.opengis.referencing.operation.MathTransform1D;
054: import org.opengis.referencing.operation.TransformException;
055:
056: // Axis
057: import org.geotools.axis.Graduation;
058: import org.geotools.axis.TickIterator;
059: import org.geotools.axis.NumberGraduation;
060: import org.geotools.axis.AbstractGraduation;
061: import org.geotools.axis.LogarithmicNumberGraduation;
062:
063: // Geotools dependencies
064: import org.geotools.coverage.GridSampleDimension;
065: import org.geotools.resources.Utilities;
066: import org.geotools.resources.image.CoverageUtilities;
067: import org.geotools.resources.i18n.Errors;
068: import org.geotools.resources.i18n.ErrorKeys;
069: import org.geotools.resources.i18n.Logging;
070: import org.geotools.resources.i18n.LoggingKeys;
071:
072: /**
073: * A color ramp with a graduation. The colors can be specified with a {@link SampleDimension},
074: * an array of {@link Color}s or an {@link IndexColorModel} object, and the graduation is
075: * specified with a {@link Graduation} object. The resulting {@code ColorRamp} object
076: * is usually painted together with a remote sensing image, for example in a
077: * {@link org.geotools.gui.swing.MapPane} object.
078: *
079: * <p> </p>
080: * <p align="center"><img src="doc-files/ColorRamp.png"></p>
081: * <p> </p>
082: *
083: * @since 2.3
084: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/extension/widgets-swing/src/main/java/org/geotools/gui/swing/image/ColorRamp.java $
085: * @version $Id: ColorRamp.java 27862 2007-11-12 19:51:19Z desruisseaux $
086: * @author Martin Desruisseaux
087: */
088: public class ColorRamp extends JComponent {
089: /**
090: * Margin (in pixel) on each sides: top, left, right and bottom of the color ramp.
091: */
092: private static final int MARGIN = 10;
093:
094: /**
095: * An empty list of colors.
096: */
097: private static final Color[] EMPTY = new Color[0];
098:
099: /**
100: * The graduation to write over the color ramp.
101: */
102: private Graduation graduation;
103:
104: /**
105: * Graduation units. This is constructed from {@link Graduation#getUnit} and cached
106: * for faster rendering.
107: */
108: private String units;
109:
110: /**
111: * The colors to paint (never {@code null}).
112: */
113: private Color[] colors = EMPTY;
114:
115: /**
116: * {@code true} if tick label must be display.
117: */
118: private boolean labelVisibles = true;
119:
120: /**
121: * {@code true} if tick label can be display with an automatic color. The
122: * automatic color will be white or black depending the background color.
123: */
124: private boolean autoForeground = true;
125:
126: /**
127: * {@code true} if the color bar should be drawn horizontally,
128: * or {@code false} if it should be drawn vertically.
129: */
130: private boolean horizontal = true;
131:
132: /**
133: * Rendering hints for the graduation. This include the color bar
134: * length, which is used for the space between ticks.
135: */
136: private transient RenderingHints hints;
137:
138: /**
139: * The tick iterator used during the last painting. This iterator will be reused as mush
140: * as possible in order to reduce garbage-collections.
141: */
142: private transient TickIterator reuse;
143:
144: /**
145: * A temporary buffer for conversions from RGB to HSB
146: * values. This is used by {@link #getForeground(int)}.
147: */
148: private transient float[] HSB;
149:
150: /**
151: * The {@link ComponentUI} object for computing preferred
152: * size, drawn the component and handle some events.
153: */
154: private final UI ui = new UI();
155:
156: /**
157: * Constructs an initially empty color bar. Colors can be
158: * set using one of the {@code setColors(...)} methods.
159: */
160: public ColorRamp() {
161: setOpaque(true);
162: setUI(ui);
163: }
164:
165: /**
166: * Constructs a color bar for the specified coverage.
167: */
168: public ColorRamp(final Coverage coverage) {
169: this ();
170: setColors(coverage);
171: }
172:
173: /**
174: * Returns the graduation to paint over colors. If the graduation is
175: * not yet defined, then this method returns {@code null}.
176: */
177: public Graduation getGraduation() {
178: return graduation;
179: }
180:
181: /**
182: * Sets the graduation to paint on top of the color bar. The graduation can be set also
183: * by a call to {@link #setColors(SampleDimension)} and {@link #setColors(Coverage)}.
184: * This method will fire a property change event with the {@code "graduation"} name.
185: * <p>
186: * The graduation minimum and maximum values should be both inclusive.
187: *
188: * @param graduation The new graduation, or {@code null} if none.
189: * @return {@code true} if this object changed as a result of this call.
190: */
191: public boolean setGraduation(final Graduation graduation) {
192: final Graduation oldGraduation = this .graduation;
193: if (graduation != oldGraduation) {
194: if (oldGraduation != null) {
195: oldGraduation.removePropertyChangeListener(ui);
196: }
197: if (graduation != null) {
198: graduation.addPropertyChangeListener(ui);
199: }
200: this .graduation = graduation;
201: units = null;
202: if (graduation != null) {
203: final Unit unit = graduation.getUnit();
204: if (unit != null) {
205: units = unit.toString();
206: }
207: }
208: }
209: final boolean changed = !Utilities.equals(graduation,
210: oldGraduation);
211: if (changed) {
212: repaint();
213: }
214: firePropertyChange("graduation", oldGraduation, graduation);
215: return changed;
216: }
217:
218: /**
219: * Returns the colors painted by this {@code ColorRamp}.
220: *
221: * @return The colors (never {@code null}).
222: */
223: public Color[] getColors() {
224: return (colors.length != 0) ? (Color[]) colors.clone() : colors;
225: }
226:
227: /**
228: * Sets the colors to paint.
229: * This method will fire a property change event with the {@code "colors"} name.
230: *
231: * @param colors The colors to paint.
232: * @return {@code true} if the state of this {@code ColorRamp} changed as a result of this call.
233: *
234: * @see #setColors(Coverage)
235: * @see #setColors(SampleDimension)
236: * @see #setColors(IndexColorModel)
237: * @see #getColors()
238: * @see #getGraduation()
239: */
240: public boolean setColors(final Color[] colors) {
241: final Color[] oldColors = this .colors;
242: this .colors = (colors != null && colors.length != 0) ? (Color[]) colors
243: .clone()
244: : EMPTY;
245: final boolean changed = !Arrays.equals(oldColors, this .colors);
246: if (changed) {
247: repaint();
248: }
249: firePropertyChange("colors", oldColors, colors);
250: return changed;
251: }
252:
253: /**
254: * Sets the colors to paint from an {@link IndexColorModel}. The default implementation
255: * fetches the colors from the index color model and invokes {@link #setColors(Color[])}.
256: *
257: * @param model The colors to paint.
258: * @return {@code true} if the state of this {@code ColorRamp} changed as a result of this call.
259: *
260: * @see #setColors(Coverage)
261: * @see #setColors(SampleDimension)
262: * @see #setColors(Color[])
263: * @see #getColors()
264: * @see #getGraduation()
265: */
266: public boolean setColors(final IndexColorModel model) {
267: final Color[] colors;
268: if (model == null) {
269: colors = EMPTY;
270: } else {
271: colors = new Color[model.getMapSize()];
272: for (int i = 0; i < colors.length; i++) {
273: colors[i] = new Color(model.getRed(i), model
274: .getGreen(i), model.getBlue(i), model
275: .getAlpha(i));
276: }
277: }
278: return setColors(colors);
279: }
280:
281: /**
282: * Sets the graduation and the colors from a sample dimension.
283: * The default implementation fetchs the palette and the minimum and maximum values
284: * from the supplied band, and then invokes {@link #setColors(Color[]) setColors} and
285: * {@link #setGraduation setGraduation}.
286: *
287: * @param band The sample dimension, or {@code null}.
288: * @return {@code true} if the state of this {@code ColorRamp} changed as a result of this call.
289: *
290: * @see #setColors(Coverage)
291: * @see #setColors(SampleDimension)
292: * @see #setColors(IndexColorModel)
293: * @see #setColors(Color[])
294: * @see #getColors()
295: * @see #getGraduation()
296: */
297: public boolean setColors(SampleDimension band) {
298: Color[] colors = EMPTY;
299: Graduation graduation = null;
300: /*
301: * Gets the color palette, preferably from the "non-geophysics" view since it is usually
302: * the one backed by an IndexColorModel. We assume that 'palette[i]' gives the color of
303: * sample value 'i'. We will search for the largest range of valid sample integer values,
304: * ignoring "nodata" values. Those "nodata" values appear usually at the begining or at
305: * the end of the whole palette range.
306: *
307: * Note that the above algorithm works without Category. We try to avoid dependency
308: * on categories because some applications don't use them. TODO: should we use this
309: * algorithm only as a fallback (i.e. use categories when available)?
310: */
311: if (band != null) {
312: if (band instanceof GridSampleDimension) {
313: band = ((GridSampleDimension) band).geophysics(false);
314: }
315: final int[][] palette = band.getPalette();
316: if (palette != null) {
317: int lower = 0; // Will be inclusive
318: int upper = 0; // Will be exclusive
319: final double[] nodata = band.getNoDataValues();
320: final double[] sorted = new double[nodata != null ? nodata.length + 2
321: : 2];
322: sorted[0] = -1;
323: sorted[sorted.length - 1] = palette.length;
324: if (nodata != null) {
325: System.arraycopy(nodata, 0, sorted, 1,
326: nodata.length);
327: }
328: Arrays.sort(sorted);
329: for (int i = 1; i < sorted.length; i++) {
330: // Note: Don't cast to integer now, because we
331: // want to take NaN and infinity in account.
332: final double lo = Math.floor(sorted[i - 1]) + 1; // "Nodata" always excluded
333: final double hi = Math.ceil(sorted[i]); // "Nodata" included if integer
334: if (lo >= 0 && hi <= palette.length
335: && (hi - lo) > (upper - lower)) {
336: lower = (int) lo;
337: upper = (int) hi;
338: }
339: }
340: /*
341: * We now know the range of values to show on the palette. Creates the colors from
342: * the palette. Only palette using RGB colors are understood at this time, but the
343: * graduation (after this block) is still created for all kind of palette.
344: */
345: if (PaletteInterpretation.RGB.equals(band
346: .getPaletteInterpretation())) {
347: colors = new Color[upper - lower];
348: for (int i = 0; i < colors.length; i++) {
349: int r = 0, g = 0, b = 0, a = 255;
350: final int[] c = palette[i + lower];
351: if (c != null)
352: switch (c.length) {
353: default: // Fall through
354: case 4:
355: a = c[3]; // Fall through
356: case 3:
357: b = c[2]; // Fall through
358: case 2:
359: g = c[1]; // Fall through
360: case 1:
361: r = c[0]; // Fall through
362: case 0:
363: break;
364: }
365: colors[i] = new Color(r, g, b, a);
366: }
367: }
368: /*
369: * Transforms the lower and upper sample values into minimum and maximum geophysics
370: * values and creates the graduation. Note that the maximum value will be inclusive,
371: * at the difference of upper value which was exclusive prior this point.
372: */
373: if (upper > lower) {
374: upper--; // Make it inclusive.
375: }
376: double min, max;
377: try {
378: final MathTransform1D tr = band
379: .getSampleToGeophysics();
380: min = tr.transform(lower);
381: max = tr.transform(upper);
382: } catch (TransformException cause) {
383: IllegalArgumentException e = new IllegalArgumentException(
384: Errors.format(
385: ErrorKeys.ILLEGAL_ARGUMENT_$2,
386: "band", band));
387: e.initCause(cause);
388: throw e;
389: }
390: if (min > max) {
391: // This case occurs typically when displaying a color ramp for
392: // sea bathymetry, for which floor level are negative numbers.
393: min = -min;
394: max = -max;
395: }
396: if (!(min <= max)) {
397: // This case occurs if one or both values is NaN.
398: throw new IllegalArgumentException(Errors
399: .format(ErrorKeys.ILLEGAL_ARGUMENT_$2,
400: "band", band));
401: }
402: graduation = createGraduation(this .graduation, band,
403: min, max);
404: }
405: }
406: return setGraduation(graduation) | setColors(colors); // Realy |, not ||
407: }
408:
409: /**
410: * Sets the graduation and the colors from a coverage.
411: * The default implementation fetchs the visible sample dimension from the specified coverage,
412: * and then invokes {@link #setColors(Color[]) setColors} and
413: * {@link #setGraduation setGraduation}.
414: *
415: * @param coverage The coverage, or {@code null}.
416: * @return {@code true} if the state of this {@code ColorRamp} changed as a result of this call.
417: *
418: * @see #setColors(IndexColorModel)
419: * @see #setColors(SampleDimension)
420: * @see #getColors()
421: * @see #getGraduation()
422: */
423: public boolean setColors(final Coverage coverage) {
424: SampleDimension band = null;
425: if (coverage != null) {
426: band = coverage.getSampleDimension(CoverageUtilities
427: .getVisibleBand(band));
428: }
429: return setColors(band);
430: }
431:
432: /**
433: * Returns the component's orientation (horizontal or vertical).
434: * It should be one of the following constants:
435: * ({@link SwingConstants#HORIZONTAL} or {@link SwingConstants#VERTICAL}).
436: */
437: public int getOrientation() {
438: return (horizontal) ? SwingConstants.HORIZONTAL
439: : SwingConstants.VERTICAL;
440: }
441:
442: /**
443: * Set the component's orientation (horizontal or vertical).
444: *
445: * @param orient {@link SwingConstants#HORIZONTAL} or {@link SwingConstants#VERTICAL}.
446: */
447: public void setOrientation(final int orient) {
448: switch (orient) {
449: case SwingConstants.HORIZONTAL:
450: horizontal = true;
451: break;
452: case SwingConstants.VERTICAL:
453: horizontal = false;
454: break;
455: default:
456: throw new IllegalArgumentException(String.valueOf(orient));
457: }
458: }
459:
460: /**
461: * Tests if graduation labels are paint on top of the
462: * colors ramp. Default value is {@code true}.
463: */
464: public boolean isLabelVisibles() {
465: return labelVisibles;
466: }
467:
468: /**
469: * Sets whatever the graduation labels should be painted on top of the colors ramp.
470: */
471: public void setLabelVisibles(final boolean visible) {
472: labelVisibles = visible;
473: }
474:
475: /**
476: * Sets the label colors. A {@code null} value reset the automatic color.
477: *
478: * @see #getForeground
479: */
480: public void setForeground(final Color color) {
481: super .setForeground(color);
482: autoForeground = (color == null);
483: }
484:
485: /**
486: * Returns a color for label at the specified index. The default color will be
487: * black or white, depending of the background color at the specified index.
488: */
489: private Color getForeground(final int colorIndex) {
490: final Color color = colors[colorIndex];
491: HSB = Color.RGBtoHSB(color.getRed(), color.getGreen(), color
492: .getBlue(), HSB);
493: return (HSB[2] >= 0.5f) ? Color.black : Color.white;
494: }
495:
496: /**
497: * Paint the color ramp. This method doesn't need to restore
498: * {@link Graphics2D} to its initial state once finished.
499: *
500: * @param graphics The graphic context in which to paint.
501: * @param bounds The bounding box where to paint the color ramp.
502: * @return Bounding box of graduation labels (NOT taking in account the color ramp
503: * behind them), or {@code null} if no label has been painted.
504: */
505: private Rectangle2D paint(final Graphics2D graphics,
506: final Rectangle bounds) {
507: final int length = colors.length;
508: final double dx, dy;
509: if (length == 0) {
510: dx = 0;
511: dy = 0;
512: } else {
513: dx = (double) (bounds.width - 2 * MARGIN) / length;
514: dy = (double) (bounds.height - 2 * MARGIN) / length;
515: int i = 0, lastIndex = 0;
516: Color color = colors[i];
517: Color nextColor = color;
518: int R, G, B;
519: int nR = R = color.getRed();
520: int nG = G = color.getGreen();
521: int nB = B = color.getBlue();
522: final int ox = bounds.x + MARGIN;
523: final int oy = bounds.y + bounds.height - MARGIN;
524: final Rectangle2D.Double rect = new Rectangle2D.Double();
525: rect.setRect(bounds);
526: while (++i <= length) {
527: if (i != length) {
528: nextColor = colors[i];
529: nR = nextColor.getRed();
530: nG = nextColor.getGreen();
531: nB = nextColor.getBlue();
532: if (R == nR && G == nG && B == nB) {
533: continue;
534: }
535: }
536: if (horizontal) {
537: rect.x = ox + dx * lastIndex;
538: rect.width = dx * (i - lastIndex);
539: if (lastIndex == 0) {
540: rect.x -= MARGIN;
541: rect.width += MARGIN;
542: }
543: if (i == length) {
544: rect.width += MARGIN;
545: }
546: } else {
547: rect.y = oy - dy * i;
548: rect.height = dy * (i - lastIndex);
549: if (lastIndex == 0) {
550: rect.height += MARGIN;
551: }
552: if (i == length) {
553: rect.y -= MARGIN;
554: rect.height += MARGIN;
555: }
556: }
557: graphics.setColor(color);
558: graphics.fill(rect);
559: lastIndex = i;
560: color = nextColor;
561: R = nR;
562: G = nG;
563: B = nB;
564: }
565: }
566: Rectangle2D labelBounds = null;
567: if (labelVisibles && graduation != null) {
568: /*
569: * Prepare graduation writting. First, computes the color ramp width in pixels.
570: * Then, computes the coefficients for conversion of graduation values to pixel
571: * coordinates.
572: */
573: double x = bounds.getCenterX();
574: double y = bounds.getCenterY();
575: final double axisRange = graduation.getRange();
576: final double axisMinimum = graduation.getMinimum();
577: final double visualLength, scale, offset;
578: if (horizontal) {
579: visualLength = bounds.getWidth() - 2 * MARGIN - dx;
580: scale = visualLength / axisRange;
581: offset = (bounds.getMinX() + MARGIN + 0.5 * dx) - scale
582: * axisMinimum;
583: } else {
584: visualLength = bounds.getHeight() - 2 * MARGIN - dy;
585: scale = -visualLength / axisRange;
586: offset = (bounds.getMaxY() - MARGIN - 0.5 * dy) + scale
587: * axisMinimum;
588: }
589: if (hints == null) {
590: hints = new RenderingHints(null);
591: }
592: final double valueToLocation = length / axisRange;
593: Font font = getFont();
594: if (font == null) {
595: font = Font.decode("SansSerif-10");
596: }
597: final FontRenderContext context = graphics
598: .getFontRenderContext();
599: hints.put(Graduation.VISUAL_AXIS_LENGTH, new Float(
600: (float) visualLength));
601: graphics.setColor(getForeground());
602: /*
603: * Now write the graduation.
604: */
605: for (final TickIterator ticks = reuse = graduation
606: .getTickIterator(hints, reuse); ticks.hasNext(); ticks
607: .nextMajor()) {
608: if (ticks.isMajorTick()) {
609: final GlyphVector glyph = font.createGlyphVector(
610: context, ticks.currentLabel());
611: final Rectangle2D rectg = glyph.getVisualBounds();
612: final double width = rectg.getWidth();
613: final double height = rectg.getHeight();
614: final double value = ticks.currentPosition();
615: final double position = value * scale + offset;
616: final int colorIndex = Math.min(Math.max((int) Math
617: .round((value - axisMinimum)
618: * valueToLocation), 0), length - 1);
619: if (horizontal)
620: x = position;
621: else
622: y = position;
623: rectg.setRect(x - 0.5 * width, y - 0.5 * height,
624: width, height);
625: if (autoForeground) {
626: graphics.setColor(getForeground(colorIndex));
627: }
628: graphics.drawGlyphVector(glyph, (float) rectg
629: .getMinX(), (float) rectg.getMaxY());
630: if (labelBounds != null) {
631: labelBounds.add(rectg);
632: } else {
633: labelBounds = rectg;
634: }
635: }
636: }
637: /*
638: * Writes units.
639: */
640: if (units != null) {
641: final GlyphVector glyph = font.createGlyphVector(
642: context, units);
643: final Rectangle2D rectg = glyph.getVisualBounds();
644: final double width = rectg.getWidth();
645: final double height = rectg.getHeight();
646: if (horizontal) {
647: double left = bounds.getMaxX() - width;
648: if (labelBounds != null) {
649: final double check = labelBounds.getMaxX() + 4;
650: if (check < left) {
651: left = check;
652: }
653: }
654: rectg
655: .setRect(left, y - 0.5 * height, width,
656: height);
657: } else {
658: rectg.setRect(x - 0.5 * width, bounds.getMinY()
659: + height, width, height);
660: }
661: if (autoForeground) {
662: graphics.setColor(getForeground(length - 1));
663: }
664: if (labelBounds == null
665: || !labelBounds.intersects(rectg)) {
666: graphics.drawGlyphVector(glyph, (float) rectg
667: .getMinX(), (float) rectg.getMaxY());
668: }
669: }
670: }
671: return labelBounds;
672: }
673:
674: /**
675: * Returns a graduation for the specified sample dimension, minimum and maximum values. This
676: * method must returns a graduation of the appropriate class, usually {@link NumberGraduation}
677: * or {@link LogarithmicNumberGraduation}. If the supplied {@code reuse} object is non-null and
678: * is of the appropriate class, then this method can returns {@code reuse} without creating a
679: * new graduation object. This method must set graduations's
680: * {@linkplain AbstractGraduation#setMinimum minimum},
681: * {@linkplain AbstractGraduation#setMaximum maximum} and
682: * {@linkplain AbstractGraduation#setUnit unit} according the values given in arguments.
683: *
684: * @param reuse The graduation to reuse if possible.
685: * @param band The sample dimension to create graduation for.
686: * @param minimum The minimal geophysics value to appears in the graduation.
687: * @param maximum The maximal geophysics value to appears in the graduation.
688: * @return A graduation for the supplied sample dimension.
689: */
690: protected Graduation createGraduation(final Graduation reuse,
691: final SampleDimension band, final double minimum,
692: final double maximum) {
693: MathTransform1D tr = band.getSampleToGeophysics();
694: boolean linear = false;
695: boolean logarithmic = false;
696: try {
697: /*
698: * An heuristic approach to determine if the transform is linear or logarithmic.
699: * We look at the derivative, which should be constant everywhere for a linear
700: * scale and be proportional to the inverse of 'x' for a logarithmic one.
701: */
702: tr = (MathTransform1D) tr.inverse();
703: final double EPS = 1E-6; // For rounding error.
704: final double ratio = tr.derivative(minimum)
705: / tr.derivative(maximum);
706: if (Math.abs(ratio - 1) <= EPS) {
707: linear = true;
708: }
709: if (Math.abs(ratio * (minimum / maximum) - 1) <= EPS) {
710: logarithmic = true;
711: }
712: } catch (TransformException exception) {
713: // Transformation failed. We don't know if the scale is linear or logarithmic.
714: // Continue anyway. A warning will be logged later in this method.
715: }
716: final Unit units = band.getUnits();
717: AbstractGraduation graduation = (reuse instanceof AbstractGraduation) ? (AbstractGraduation) reuse
718: : null;
719: if (linear) {
720: if (graduation == null
721: || !graduation.getClass().equals(
722: NumberGraduation.class)) {
723: graduation = new NumberGraduation(units);
724: }
725: } else if (logarithmic) {
726: if (graduation == null
727: || !graduation.getClass().equals(
728: LogarithmicNumberGraduation.class)) {
729: graduation = new LogarithmicNumberGraduation(units);
730: }
731: } else {
732: org.geotools.util.logging.Logging.getLogger(
733: "org.geotools.gui.swing").log(
734: Logging.format(Level.WARNING,
735: LoggingKeys.UNRECOGNIZED_SCALE_TYPE_$1,
736: Utilities.getShortClassName(tr)));
737: graduation = new NumberGraduation(units);
738: }
739: if (graduation == reuse) {
740: graduation.setUnit(units);
741: }
742: graduation.setMinimum(minimum);
743: graduation.setMaximum(maximum);
744: return graduation;
745: }
746:
747: /**
748: * Returns an image representation for this color ramp. The image size will be this
749: * {@linkplain #getSize widget size}.
750: */
751: public BufferedImage toImage() {
752: final BufferedImage image = new BufferedImage(getWidth(),
753: getHeight(), BufferedImage.TYPE_INT_ARGB);
754: final Graphics2D graphics = image.createGraphics();
755: paint(graphics, new Rectangle(0, 0, image.getWidth(), image
756: .getHeight()));
757: graphics.dispose();
758: return image;
759: }
760:
761: /**
762: * Returns a string representation for this color ramp.
763: */
764: public String toString() {
765: int count = 0;
766: int i = 0;
767: if (i < colors.length) {
768: Color last = colors[i];
769: while (++i < colors.length) {
770: Color c = colors[i];
771: if (!c.equals(last)) {
772: last = c;
773: count++;
774: }
775: }
776: }
777: return Utilities.getShortClassName(this ) + '[' + count
778: + " colors]";
779: }
780:
781: /**
782: * Notifies this component that it now has a parent component. This method
783: * is invoked by <cite>Swing</cite> and shouldn't be directly used.
784: */
785: public void addNotify() {
786: super .addNotify();
787: if (graduation != null) {
788: graduation.removePropertyChangeListener(ui); // Avoid duplication
789: graduation.addPropertyChangeListener(ui);
790: }
791: }
792:
793: /**
794: * Notifies this component that it no longer has a parent component.
795: * This method is invoked by <em>Swing</em> and shouldn't be directly used.
796: */
797: public void removeNotify() {
798: if (graduation != null) {
799: graduation.removePropertyChangeListener(ui);
800: }
801: super .removeNotify();
802: }
803:
804: /**
805: * Classe ayant la charge de dessiner la rampe de couleurs, ainsi que
806: * de calculer l'espace qu'elle occupe. Cette classe peut aussi réagir
807: * à certains événements.
808: *
809: * @version $Id: ColorRamp.java 27862 2007-11-12 19:51:19Z desruisseaux $
810: * @author Martin Desruisseaux
811: */
812: private final class UI extends ComponentUI implements
813: PropertyChangeListener {
814: /**
815: * Retourne la dimension minimale de cette rampe de couleurs.
816: */
817: public Dimension getMinimumSize(final JComponent c) {
818: return (((ColorRamp) c).horizontal) ? new Dimension(
819: 2 * MARGIN, 16) : new Dimension(16, 2 * MARGIN);
820: }
821:
822: /**
823: * Retourne la dimension préférée de cette rampe de couleurs.
824: */
825: public Dimension getPreferredSize(final JComponent c) {
826: return (((ColorRamp) c).horizontal) ? new Dimension(256, 16)
827: : new Dimension(16, 256);
828: }
829:
830: /**
831: * Dessine la rampe de couleurs vers le graphique spécifié. Cette méthode a
832: * l'avantage d'être appelée automatiquement par <i>Swing</i> avec une copie
833: * d'un objet {@link Graphics}, ce qui nous évite d'avoir à le remettre dans
834: * son état initial lorsqu'on a terminé le traçage de la rampe de couleurs.
835: * On n'a pas cet avantage lorsque l'on ne fait que redéfinir
836: * {@link JComponent#paintComponent}.
837: */
838: public void paint(final Graphics graphics,
839: final JComponent component) {
840: final ColorRamp ramp = (ColorRamp) component;
841: if (ramp.colors != null) {
842: final Rectangle bounds = ramp.getBounds();
843: bounds.x = 0;
844: bounds.y = 0;
845: ramp.paint((Graphics2D) graphics, bounds);
846: }
847: }
848:
849: /**
850: * Méthode appelée automatiquement chaque fois qu'une propriété de l'axe a changée.
851: */
852: public void propertyChange(final PropertyChangeEvent event) {
853: repaint();
854: }
855: }
856: }
|