Coverage Report - org.jfree.chart.plot.GradientColorPalette
 
Classes in this File Line Coverage Branch Coverage Complexity
GradientColorPalette
98%
103/105
100%
16/16
2.706
 
 1  
 /* ======================================================================= 
 2  
  * A visualisation library extension for JFreeChart. Please see JFreeChart
 3  
  * for further information.
 4  
  * =======================================================================
 5  
  * Copyright (C) 2006  University of Helsinki, Department of Computer Science
 6  
  *
 7  
  * This library is free software; you can redistribute it and/or
 8  
  * modify it under the terms of the GNU Lesser General Public
 9  
  * License as published by the Free Software Foundation; either
 10  
  * version 2.1 of the License, or (at your option) any later version.
 11  
  *
 12  
  * This library is distributed in the hope that it will be useful,
 13  
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 14  
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 15  
  * Lesser General Public License for more details.
 16  
  *
 17  
  * You should have received a copy of the GNU Lesser General Public
 18  
  * License along with this library; if not, write to the Free Software
 19  
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 20  
  * -----------------------------
 21  
  * Contact:  ohtu@cs.helsinki.fi
 22  
  * -----------------------------
 23  
  *
 24  
  */
 25  
 
 26  
 package org.jfree.chart.plot;
 27  
 
 28  
 import java.awt.Color;
 29  
 import java.util.Iterator;
 30  
 import java.util.LinkedList;
 31  
 import java.util.Map;
 32  
 import java.util.NoSuchElementException;
 33  
 import java.util.Set;
 34  
 import java.util.TreeMap;
 35  
 
 36  
 import javax.swing.event.ChangeEvent;
 37  
 import javax.swing.event.ChangeListener;
 38  
 
 39  
 /**
 40  
  * A class implementing mapping of colors to values. Used in {@link HCPlot}.
 41  
  *
 42  
  * @author  viski project
 43  
  */
 44  
 public class GradientColorPalette
 45  
     implements Cloneable {
 46  
 
 47  
     private TreeMap keyColorMap;
 48  
     private boolean linearMapping;
 49  
 
 50  
     private LinkedList listeners;
 51  
 
 52  
 
 53  
     /**
 54  
      * Creates a default color palette.
 55  
      * The default palette is the following:
 56  
      * value < -1        red
 57  
      * -1 < value < 0   red to black
 58  
      *  value = 0        black
 59  
      *  0 < value < 1   black to green
 60  
      *  1 < value        green
 61  
      */
 62  20
     public GradientColorPalette() {
 63  
 
 64  20
         this.keyColorMap = new TreeMap();
 65  20
         this.linearMapping = true;
 66  20
         this.listeners = new LinkedList();
 67  20
         addKeyColor (-1.0, new Color(255,  0,  0));
 68  20
         addKeyColor ( 0.0, new Color(  0,  0,  0));
 69  20
         addKeyColor ( 1.0, new Color(  0,255,  0));
 70  20
         return;
 71  
 
 72  
     }
 73  
 
 74  
     /**
 75  
      * Creates a default scaled color palette.
 76  
      * The default palette is the following:
 77  
      * value < -min        red
 78  
      * -min < value < 0        red to black
 79  
      *  value = 0        black
 80  
      *  0 < value < max        black to green
 81  
      *  max < value        green
 82  
      */
 83  22
     public GradientColorPalette(double min, double max) {
 84  
 
 85  22
         this.keyColorMap = new TreeMap();
 86  22
         this.linearMapping = true;
 87  22
         this.listeners = new LinkedList();
 88  22
         addKeyColor (min, new Color(255,  0,  0));
 89  22
         addKeyColor ( 0.0, new Color(  0,  0,  0));
 90  22
         addKeyColor (max, new Color(  0,255,  0));
 91  22
         return;
 92  
 
 93  
     }
 94  
 
 95  
     /**
 96  
      * Creates a custom color palette.
 97  
      *
 98  
      * @param keyColorMap  a mapping between values of type double and
 99  
      * a color to be used as key colors.
 100  
      */
 101  3
     public GradientColorPalette(Map keyColorMap) {
 102  3
         this.keyColorMap = new TreeMap(keyColorMap);
 103  2
         this.linearMapping = true;
 104  2
         this.listeners = new LinkedList();
 105  2
     }
 106  
 
 107  
     /**
 108  
      * Sets a new color for an already mapped value.
 109  
      *
 110  
      * @param value  value that maps to this color.
 111  
      * @param color  the color the specified value is mapped to.
 112  
      *
 113  
      * @throws  NullPointerException, if the specified color is null.
 114  
      */
 115  
     public Color setKeyColor(double value, Color color) {
 116  
 
 117  8
         if (color == null) throw new NullPointerException();
 118  7
         Color old = (Color)this.keyColorMap.remove((Object)new Double(value));
 119  7
         this.keyColorMap.put ((Object)new Double(value), (Object)color);
 120  7
         notifyChangeListeners(new ChangeEvent(this));
 121  7
         return old;
 122  
 
 123  
     }
 124  
 
 125  
     /**
 126  
      * Adds a new keycolor.
 127  
      *
 128  
      * @param value  value that maps to this color. This can be any
 129  
      *     double. If a keycolor with this value already exists, it
 130  
      *     its color-value is reset.
 131  
      * @param r  the value of the red component of the color as a
 132  
      *     integer from 0 to 255.
 133  
      * @param g  the value of the green component of the color as a
 134  
      *     integer from 0 to 255.
 135  
      * @param b  the value of the blue component of the color as a
 136  
      *     integer from 0 to 255.
 137  
      *
 138  
      * @throws IllegalArgumentException if either r, g or b is not
 139  
      *     in the range [0,255]
 140  
      */
 141  
     public void addKeyColor(double value, int r, int g, int b)
 142  
         throws IllegalArgumentException {
 143  
 
 144  14
         addKeyColor(value, new Color(r,g,b));
 145  
 
 146  8
     }
 147  
 
 148  
     /**
 149  
      * Adds a new keycolor.
 150  
      *
 151  
      * @param value  value that maps to this color. This can be any
 152  
      *     double. If a keycolor with this value already exists, it
 153  
      *     its color-value is reset.
 154  
      * @param color  the color the specified value is mapped to.
 155  
      *
 156  
      * @throws  NullPointerException, if the specified color is null.
 157  
      */
 158  
     public void addKeyColor(double value, Color color) 
 159  
         throws NullPointerException {
 160  
 
 161  140
         if (color == null) throw new NullPointerException();
 162  139
         this.keyColorMap.put ((Object)new Double(value), (Object)color);
 163  139
         notifyChangeListeners(new ChangeEvent(this));
 164  
 
 165  139
     }
 166  
 
 167  
     /**
 168  
      * Adds a change listener
 169  
      *
 170  
      * @param listener  the listener.
 171  
      */
 172  
     public void addChangeListener(ChangeListener listener) {
 173  
 
 174  26
         this.listeners.add((Object)listener);
 175  
 
 176  26
     }
 177  
 
 178  
     /**
 179  
      * Removes a change listener
 180  
      *
 181  
      * @param listener  the listener.
 182  
      */
 183  
     public void removeChangeListener(ChangeListener listener) {
 184  
 
 185  2
         this.listeners.remove((Object)listener);
 186  
 
 187  2
     }
 188  
 
 189  
     /**
 190  
      * Notifies listeners of an event
 191  
      *
 192  
      * @param event  the event.
 193  
      */
 194  
     private void notifyChangeListeners(ChangeEvent event) {
 195  
 
 196  163
         Iterator iterator = listeners.iterator();
 197  
         ChangeListener listener;
 198  
 
 199  165
         while(iterator.hasNext()) {
 200  
 
 201  2
             listener = (ChangeListener)(iterator.next());
 202  2
             listener.stateChanged (event);
 203  
 
 204  2
         }
 205  
 
 206  163
     }
 207  
 
 208  
     /**
 209  
      * Returns the keycolor map.
 210  
      *
 211  
      * @return  The keycolor map.
 212  
      */
 213  
     public Set getKeyColors() {
 214  
 
 215  16
         return this.keyColorMap.keySet();
 216  
 
 217  
     }
 218  
 
 219  
     /**
 220  
      * Returns a specified key color.
 221  
      *
 222  
      * @param value  a value specifying a key color.
 223  
      *
 224  
      * @return  The keycolor, or null, if the specified key color does not
 225  
      * exist.
 226  
      *
 227  
      */
 228  
     public Color getKeyColor(double value) {
 229  
 
 230  43
         return (Color)this.keyColorMap.get((Object)new Double(value));
 231  
 
 232  
     }
 233  
 
 234  
     /**
 235  
      * Removes a specified key color.
 236  
      *
 237  
      * @param value  a value specifying a key color.
 238  
      *
 239  
      * @return  The removed keycolor, or null, if the specified key color
 240  
      * does not exist.
 241  
      */
 242  
     public Color removeKeyColor(double value) {
 243  
 
 244  7
         Color old = (Color)this.keyColorMap.remove((Object)new Double(value));
 245  7
         notifyChangeListeners(new ChangeEvent(this));
 246  7
         return old;
 247  
 
 248  
     }
 249  
 
 250  
     /**
 251  
      * Returns the keycolor map.
 252  
      *
 253  
      * @return  The keycolor map.
 254  
      */
 255  
     public Color getColor(double value) {
 256  
 
 257  
         double proportion;
 258  
         Color color1;
 259  
         Color color2;
 260  
         Color color;
 261  90
         Double valueObject = new Double(value);
 262  
         Double value1;
 263  
         Double value2;
 264  
 
 265  
         // if the value maps directly to a color, return it.
 266  90
         color = (Color)this.keyColorMap.get((Object)(valueObject));
 267  90
         if (color != null) return color;
 268  
 
 269  
         // get the closest keyColor < value
 270  
         try {
 271  7
             value1 = (Double)this.keyColorMap.headMap(valueObject).lastKey();
 272  2
         } catch (NoSuchElementException e) {
 273  
             // or if it doesn't exist, return the closest keyColor > value
 274  2
             return (Color)this.keyColorMap.get((Object)
 275  
                 (this.keyColorMap.tailMap(valueObject).firstKey())
 276  
             );
 277  5
         }
 278  
 
 279  
         // get the closest keyColor > value
 280  
         try {
 281  5
             value2 = (Double)this.keyColorMap.tailMap(valueObject).firstKey();
 282  1
         } catch (NoSuchElementException e) {
 283  
             // or if it doesn't exist, return the closest keyColor < value
 284  1
             return (Color)this.keyColorMap.get((Object)
 285  
                 (this.keyColorMap.headMap(valueObject).lastKey())
 286  
             );
 287  4
         }
 288  
 
 289  4
         color1 = (Color)this.keyColorMap.get((Object)value1);
 290  4
         color2 = (Color)this.keyColorMap.get((Object)value2);
 291  
 
 292  
         // map the color between color1 and color2
 293  4
         if (this.linearMapping) {
 294  
 
 295  1
             proportion = (value-value1.doubleValue())
 296  
                 / (value2.doubleValue()-value1.doubleValue());
 297  
 
 298  1
         } else {
 299  
 
 300  
             // TODO: how does one calculate this?
 301  
             double logvalue;
 302  
             double logvalue1;
 303  
             double logvalue2;
 304  3
             if (value<0) {
 305  
 
 306  
                 // there has to be keyValue 0 if we are using
 307  
                 // logarithmic scale.
 308  
                 // if not, just do something.
 309  2
                 if (value2.doubleValue() > 0)
 310  0
                     return new Color(0,0,0);
 311  
                     // this is unreachable.
 312  
 
 313  
                 // kludge
 314  2
                 if (value2.doubleValue() == 0) value2 = new Double(value/100);
 315  
 
 316  2
                 logvalue = Math.log(-value);
 317  2
                 logvalue1 = Math.log(-value1.doubleValue());
 318  2
                 logvalue2 = Math.log(-value2.doubleValue());
 319  2
                 proportion = (logvalue-logvalue1)
 320  
                     / (logvalue2-logvalue1);
 321  
 
 322  2
             } else {
 323  
 
 324  
                 // there has to be keyValue 0 if we are using
 325  
                 // logarithmic scale.
 326  
                 // if not, just do something.
 327  1
                 if (value1.doubleValue() < 0)
 328  0
                     return new Color(0,0,0);
 329  
                     // this is unreachable.
 330  
 
 331  
                 // kludge
 332  1
                 if (value1.doubleValue() == 0) value1 = new Double(value/100);
 333  
 
 334  1
                 logvalue = Math.log(value);
 335  1
                 logvalue1 = Math.log(value1.doubleValue());
 336  1
                 logvalue2 = Math.log(value2.doubleValue());
 337  
 
 338  1
                 proportion = (logvalue-logvalue1)
 339  
                     / (logvalue2-logvalue1);
 340  
 
 341  
             }
 342  
 
 343  
         }
 344  
 
 345  4
         return new Color(
 346  
             (int)(color2.getRed()*proportion +
 347  
                   color1.getRed()*(1-proportion)),
 348  
             (int)(color2.getGreen()*proportion +
 349  
                   color1.getGreen()*(1-proportion)),
 350  
             (int)(color2.getBlue()*proportion +
 351  
                   color1.getBlue()*(1-proportion))
 352  
         );
 353  
         
 354  
     }
 355  
 
 356  
     /**
 357  
      * sets linear or logarithmic value mapping.
 358  
      *
 359  
      * @param linear  true to set linear mapping, false, to set logarithmic
 360  
      * mapping.
 361  
      */
 362  
     public void setLinear(boolean linear) {
 363  
 
 364  10
         this.linearMapping = linear;
 365  10
         notifyChangeListeners (new ChangeEvent(this));
 366  
 
 367  10
     }
 368  
 
 369  
     /**
 370  
      * Returns a value indicating whether linear or logarithmic mapping
 371  
      * is being used.
 372  
      *
 373  
      * @return  true, if linear mapping is in use, false otherwise.
 374  
      */
 375  
     public boolean isLinear() {
 376  
 
 377  10
         return this.linearMapping;
 378  
 
 379  
     }
 380  
 
 381  
     /**
 382  
      * Clones this object.
 383  
      *
 384  
      * @return  a clone of this object.
 385  
      */
 386  
     public Object clone() {
 387  
 
 388  1
         Iterator iterator = listeners.iterator();
 389  
         ChangeListener listener;
 390  
 
 391  1
         GradientColorPalette clone = new GradientColorPalette(this.keyColorMap);
 392  1
         clone.setLinear(this.linearMapping);
 393  
 
 394  3
         while(iterator.hasNext()) {
 395  
 
 396  2
             listener = (ChangeListener)(iterator.next());
 397  2
             clone.addChangeListener(listener);
 398  
 
 399  2
         }
 400  
 
 401  1
         return (Object)clone;
 402  
 
 403  
     }
 404  
 
 405  
     /**
 406  
      * Checks whether this object is equal to a specified object.
 407  
      *
 408  
      * @param obj  an object to check equality against.
 409  
      *
 410  
      * @return  true, if the objects are equal, false otherwise.
 411  
      */
 412  
     public boolean equals(Object obj) {
 413  
 
 414  
         GradientColorPalette that;
 415  
         Set thisKeyColors;
 416  
         Set thatKeyColors;
 417  
         LinkedList listeners;
 418  
         Iterator iterator;
 419  
 
 420  7
         if (!(obj instanceof GradientColorPalette)) return false;
 421  7
         that = (GradientColorPalette)obj;
 422  7
         if (that.isLinear() != this.linearMapping) return false;
 423  
 
 424  7
         thisKeyColors = this.getKeyColors();
 425  7
         thatKeyColors = that.getKeyColors();
 426  7
         if (!thisKeyColors.equals(thatKeyColors)) return false;
 427  5
         iterator = thisKeyColors.iterator();
 428  19
         while (iterator.hasNext()) {
 429  15
             double value = ((Double)iterator.next()).doubleValue();
 430  15
             if (!this.getKeyColor(value).equals(that.getKeyColor(value)))
 431  1
                 return false;
 432  14
         }
 433  
 
 434  4
         return true;
 435  
     }
 436  
 
 437  
 }