001/*
002 *                  BioJava development code
003 *
004 * This code may be freely distributed and modified under the
005 * terms of the GNU Lesser General Public Licence.  This should
006 * be distributed with the code.  If you do not have a copy,
007 * see:
008 *
009 *      http://www.gnu.org/copyleft/lesser.html
010 *
011 * Copyright for this code is held jointly by the individual
012 * authors.  These should be listed in @author doc comments.
013 *
014 * For more information on the BioJava project and its aims,
015 * or to join the biojava-l mailing list, visit the home page
016 * at:
017 *
018 *      http://www.biojava.org/
019 *
020 * Created on Aug 3, 2007
021 */
022package org.biojava.nbio.structure.gui.util.color;
023
024import org.biojava.nbio.structure.gui.util.color.LinearColorInterpolator.InterpolationDirection;
025
026import javax.swing.*;
027import java.awt.*;
028import java.awt.color.ColorSpace;
029import java.util.*;
030
031/**
032 * Maps a set of real values onto a gradient.
033 *
034 * The real line is partitioned into segments [a,b). The endpoint of each segment is labeled with a color.
035 * Colors are linearly interpolated between finite endpoints. Endpoints implicitly exist for
036 * Double.NEGATIVE_INFINITY and Double.POSITIVE_INFINITY, representing default colors. Thus any point
037 * in the segment [-Inf,a) is labeled with the negInf color, and any point in [b,Inf] is labeled with the posInf
038 * color. If no endpoints are present, the posInf color is used as default.
039 *
040 * Common gradients are predefined an may be instantiated through
041 * GradientMapper.getGradientMapper().
042 *
043 * @author Spencer Bliven
044 *
045 */
046public class GradientMapper implements ContinuousColorMapper, Map<Double, Color> {
047
048        public static final int BLACK_WHITE_GRADIENT = 1;
049        public static final int WHITE_BLACK_GRADIENT = 2;
050        public static final int RED_BLUE_GRADIENT = 3;
051        public static final int RAINBOW_GRADIENT = 4;
052        public static final int RAINBOW_INTENSITY_GRADIENT = 5;
053
054        private NavigableMap<Double,Color> mapping;
055        private ColorInterpolator interpolator;
056
057        public GradientMapper() {
058                this(Color.black,Color.white);
059        }
060        public GradientMapper(Color negInf, Color posInf) {
061                this(negInf,posInf,ColorSpace.getInstance(ColorSpace.CS_sRGB));
062        }
063        public GradientMapper(Color negInf, Color posInf, ColorSpace cspace) {
064                mapping = new TreeMap<Double,Color>();
065                mapping.put(Double.NEGATIVE_INFINITY, negInf);
066                mapping.put(Double.POSITIVE_INFINITY, posInf);
067                interpolator = new LinearColorInterpolator(cspace);
068        }
069
070        /**
071         * Constructs a gradientMapper to draw one of the pre-defined gradients
072         *
073         * For example,
074         * GradientMapper.getGradientMapper(GradientMapper.RAINBOW_GRADIENT, 0, 10)
075         *
076         * @param gradientType One of the gradient types, eg GradientMapper.BLACK_WHITE_GRADIENT
077         * @param min Start of the gradient
078         * @param max End of the gradient
079         * @return
080         */
081        public static GradientMapper getGradientMapper(int gradientType, double min, double max) {
082                GradientMapper gm;
083                switch( gradientType ) {
084                case BLACK_WHITE_GRADIENT:
085                        gm = new GradientMapper(Color.BLACK, Color.WHITE);
086                        gm.put(min, Color.BLACK);
087                        gm.put(max, Color.WHITE);
088                        return gm;
089                case WHITE_BLACK_GRADIENT:
090                        gm = new GradientMapper(Color.WHITE, Color.BLACK);
091                        gm.put(min, Color.WHITE);
092                        gm.put(max, Color.BLACK);
093                        return gm;
094                case RED_BLUE_GRADIENT:
095                        gm = new GradientMapper(Color.RED, Color.BLUE);
096                        gm.put(min, Color.RED);
097                        gm.put(max, Color.BLUE);
098                        return gm;
099                case RAINBOW_GRADIENT: {
100                        //Set up interpolation in HSV colorspace
101                        ColorSpace hsv = HSVColorSpace.getHSVColorSpace();
102                        LinearColorInterpolator interp = new LinearColorInterpolator(hsv);
103                        interp.setInterpolationDirection(0, InterpolationDirection.UPPER);
104
105                        Color hsvLow = new Color(hsv,new float[] {0f, 1f, 1f},1f);
106                        Color hsvHigh = new Color(hsv,new float[] {1f, 1f, 1f},1f);
107
108                        gm = new GradientMapper(hsvLow, hsvHigh, hsv);
109                        gm.put(min, hsvLow);
110                        gm.put(max, hsvHigh);
111                        gm.setInterpolator(interp);
112                        return gm;
113                }
114                case RAINBOW_INTENSITY_GRADIENT: {
115                        //Set up interpolation in HSV colorspace
116                        ColorSpace hsv = HSVColorSpace.getHSVColorSpace();
117                        LinearColorInterpolator interp = new LinearColorInterpolator(hsv);
118                        interp.setInterpolationDirection(0, InterpolationDirection.LOWER);
119
120                        Color hsvLow = new Color(hsv,new float[] {1f, 1f, 1f},1f);
121                        Color hsvHigh = new Color(hsv,new float[] {0f, 1f, 0f},1f);
122
123                        gm = new GradientMapper(hsvLow, hsvHigh, hsv);
124                        gm.put(min, hsvLow);
125                        gm.put(max, hsvHigh);
126                        gm.setInterpolator(interp);
127                        return gm;
128                }
129                default:
130                        throw new IllegalArgumentException("Unsupported gradient "+gradientType);
131                }
132        }
133        /**
134         * @param value
135         * @return
136         * @see org.biojava.nbio.structure.gui.util.color.ContinuousColorMapper#getColor(double)
137         */
138        @Override
139        public Color getColor(double value) {
140                Double left = mapping.floorKey(value);
141                Double right = mapping.higherKey(value);
142
143                //don't interpolate to infinity
144                if(right == null || right.isInfinite()) {
145                        return mapping.get(Double.POSITIVE_INFINITY);
146                }
147                if(left == null || left.isInfinite()) {
148                        return mapping.get(Double.NEGATIVE_INFINITY);
149                }
150
151                // fraction of left color to use
152                float alpha = (float) ((right-value)/(right-left));
153                return interpolator.interpolate(mapping.get(left),mapping.get(right),alpha);
154        }
155
156
157
158        /*-*************************
159         *  Map methods
160         ***************************/
161
162
163        /**
164         * Clears all finite endpoints
165         *
166         * @see java.util.Map#clear()
167         */
168        @Override
169        public void clear() {
170                Color neg = mapping.get(Double.NEGATIVE_INFINITY);
171                Color pos = mapping.get(Double.POSITIVE_INFINITY);
172                mapping.clear();
173                mapping.put(Double.NEGATIVE_INFINITY, neg);
174                mapping.put(Double.POSITIVE_INFINITY, pos);
175        }
176        /**
177         * @param position
178         * @return
179         * @see java.util.Map#containsKey(java.lang.Object)
180         */
181        @Override
182        public boolean containsKey(Object position) {
183                return mapping.containsKey(position);
184        }
185        /**
186         * @param color
187         * @return
188         * @see java.util.Map#containsValue(java.lang.Object)
189         */
190        @Override
191        public boolean containsValue(Object color) {
192                return mapping.containsValue(color);
193        }
194        /**
195         * @return
196         * @see java.util.Map#entrySet()
197         */
198        @Override
199        public Set<java.util.Map.Entry<Double, Color>> entrySet() {
200                return mapping.entrySet();
201        }
202        /**
203         * @param position
204         * @return The color of the endpoint at position, or null if no endpoint exists there
205         * @see java.util.Map#get(java.lang.Object)
206         */
207        @Override
208        public Color get(Object position) {
209                return mapping.get(position);
210        }
211        /**
212         * @return true if this gradient does not contain finite endpoints
213         * @see java.util.Map#isEmpty()
214         */
215        @Override
216        public boolean isEmpty() {
217                return mapping.size() <= 2;
218        }
219        /**
220         * @return
221         * @see java.util.Map#keySet()
222         */
223        @Override
224        public Set<Double> keySet() {
225                return mapping.keySet();
226        }
227        /**
228         * Adds a gradient endpoint at the specified position.
229         * @param position The endpoint position. May be Double.POSITIVE_INFINITY or Double.NEGATIVE_INFINITY for endpoints.
230         * @param color
231         * @return
232         * @see java.util.Map#put(java.lang.Object, java.lang.Object)
233         */
234        @Override
235        public Color put(Double position, Color color) {
236                if( position == null ) {
237                        throw new NullPointerException("Null endpoint position");
238                }
239                if( color == null ){
240                        throw new NullPointerException("Null colors are not allowed.");
241                }
242                return mapping.put(position, color);
243        }
244        /**
245         * @param m
246         * @see java.util.Map#putAll(java.util.Map)
247         */
248        @Override
249        public void putAll(Map<? extends Double, ? extends Color> m) {
250                mapping.putAll(m);
251        }
252        /**
253         * @param position
254         * @return
255         * @see java.util.Map#remove(java.lang.Object)
256         */
257        @Override
258        public Color remove(Object position) {
259                if( ((Double)position).isInfinite() ) {
260                        throw new UnsupportedOperationException("Cannot remove infinite endpoints");
261                }
262                return mapping.remove(position);
263        }
264        /**
265         * @return Number of finite endpoints
266         * @see java.util.Map#size()
267         */
268        @Override
269        public int size() {
270                return mapping.size()-2;
271        }
272        /**
273         * @return
274         * @see java.util.Map#values()
275         */
276        @Override
277        public Collection<Color> values() {
278                return mapping.values();
279        }
280
281
282        /**
283         * @return the interpolator
284         */
285        public ColorInterpolator getInterpolator() {
286                return interpolator;
287        }
288        /**
289         * @param interpolator the interpolator to set
290         */
291        public void setInterpolator(ColorInterpolator interpolator) {
292                this.interpolator = interpolator;
293        }
294        /**
295         * @param args
296         */
297        public static void main(String[] args) {
298                GradientMapper[] mappers = new GradientMapper[20];
299                int i = 0;
300                ColorSpace hsv = HSVColorSpace.getHSVColorSpace();
301                LinearColorInterpolator interp;
302
303
304                // RGB colorspace
305                mappers[i] = new GradientMapper(Color.black, Color.white);
306                mappers[i].put(-5., Color.red);
307                mappers[i].put(5., Color.blue);
308                i++;
309
310                // Premade
311                mappers[i] = GradientMapper.getGradientMapper(BLACK_WHITE_GRADIENT,-5,5);
312                i++;
313                mappers[i] = GradientMapper.getGradientMapper(RAINBOW_INTENSITY_GRADIENT,-5,5);
314                //mappers[i].put(Double.NEGATIVE_INFINITY, mappers[i].get(Double.NEGATIVE_INFINITY).brighter());
315                //mappers[i].put(Double.POSITIVE_INFINITY, mappers[i].get(Double.POSITIVE_INFINITY).darker());
316                i++;
317
318                // Rainbow
319                mappers[i] = new GradientMapper(Color.black, Color.white, hsv);
320                mappers[i].put(-5., new Color(hsv,new float[] {0f, 1f, 1f},1f));
321                mappers[i].put( 5., new Color(hsv,new float[] {1f, 1f, 1f},1f));
322                i++;
323
324                // HSV INNER
325                mappers[i] = new GradientMapper(Color.black, Color.white, hsv);
326                mappers[i].put( 5., Color.red);
327                mappers[i].put(-5., Color.blue);
328                i++;
329
330                // HSV OUTER
331                interp = new LinearColorInterpolator(hsv);
332                interp.setInterpolationDirection(0, InterpolationDirection.OUTER);
333                mappers[i] = new GradientMapper(Color.black, Color.white, hsv);
334                mappers[i].put( 5., Color.red);
335                mappers[i].put(-5., Color.blue);
336                mappers[i].setInterpolator(interp);
337                i++;
338
339                // HSV UPPER
340                interp = new LinearColorInterpolator(hsv);
341                interp.setInterpolationDirection(0, InterpolationDirection.UPPER);
342                mappers[i] = new GradientMapper(Color.black, Color.white, hsv);
343                mappers[i].put( 5., Color.red);
344                mappers[i].put(-5., Color.blue);
345                mappers[i].setInterpolator(interp);
346                i++;
347
348                // HSV LOWER
349                interp = new LinearColorInterpolator(hsv);
350                interp.setInterpolationDirection(0, InterpolationDirection.LOWER);
351                mappers[i] = new GradientMapper(Color.black, Color.white, hsv);
352                mappers[i].put( 5., Color.red);
353                mappers[i].put(-5., Color.blue);
354                mappers[i].setInterpolator(interp);
355                i++;
356
357                // Mimic DefaultMapper
358                interp = new LinearColorInterpolator(hsv);
359                interp.setInterpolationDirection(0, InterpolationDirection.INNER);
360                mappers[i] = new GradientMapper(Color.green, Color.black, hsv);
361                mappers[i].put( 0., new Color(hsv,new float[] {1f, .9f, 1f},1f));
362                mappers[i].put(10., new Color(hsv,new float[] {0f, .9f, 0f},1f));
363                mappers[i].setInterpolator(interp);
364                i++;
365
366                // Better DefaultGradient
367                interp = new LinearColorInterpolator(hsv);
368                interp.setInterpolationDirection(0, InterpolationDirection.INNER);
369                mappers[i] = new GradientMapper(Color.green, Color.black, hsv);
370                mappers[i].put( 0., new Color(hsv,new float[] {1f, .9f, 1f},1f));
371                mappers[i].put( 1., new Color(hsv,new float[] {0f, .9f, 1f},1f));
372                mappers[i].put( 1+1e-6, Color.white);
373                mappers[i].put(10., Color.black);
374                mappers[i].setInterpolator(interp);
375                i++;
376                // Better DefaultGradient
377                interp = new LinearColorInterpolator(hsv);
378                interp.setInterpolationDirection(0, InterpolationDirection.INNER);
379                mappers[i] = new GradientMapper(Color.green, Color.black, hsv);
380                mappers[i].put( 0., new Color(hsv,new float[] {1f, .9f, 1f},1f));
381                mappers[i].put( 1., new Color(hsv,new float[] {.2f, .9f, 1f},1f));
382                mappers[i].put( 1+1e-6, Color.white);
383                mappers[i].put(10., Color.black);
384                mappers[i].setInterpolator(interp);
385                i++;
386
387
388                DefaultMatrixMapper defaultMapper = new DefaultMatrixMapper(10f,.9f);
389
390
391
392                JFrame frame = new JFrame("GradientMapper");
393                JPanel main = new JPanel();
394                main.setPreferredSize(new Dimension(300,500));
395
396                for(int j=0;j<i;j++) {
397                        GradientPanel grad1 = new GradientPanel(mappers[j],-10,10);
398                        //grad1.setPreferredSize(new Dimension(500,50));
399                        main.add(grad1);
400                }
401                GradientPanel grad2 = new GradientPanel(defaultMapper,-10,10);
402                //grad2.setPreferredSize(new Dimension(500,50));
403                main.add(grad2);
404                //main.add(new GradientPanel(defaultMapper,-10,10));
405
406
407                frame.getContentPane().add(main);
408                frame.pack();
409                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
410                frame.setVisible(true);
411
412        }
413
414
415}