View Javadoc
1   package org.opentrafficsim.graphs;
2   
3   import java.awt.Color;
4   import java.awt.Paint;
5   
6   import org.jfree.chart.renderer.PaintScale;
7   
8   /**
9    * Create a continuous color paint scale. <br>
10   * Primarily intended for the contour plots, but sufficiently abstract for more general use. <br>
11   * A continuous color paint scale creates paints (actually simple Colors) by linearly interpolating between a limited set of RGB
12   * Color values that correspond to given input values.
13   * <p>
14   * Copyright (c) 2013-2015 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
15   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
16   * <p>
17   * $LastChangedDate: 2015-07-26 01:01:13 +0200 (Sun, 26 Jul 2015) $, @version $Revision: 1155 $, by $Author: averbraeck $,
18   * initial version Jul 30, 2014 <br>
19   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
20   */
21  public class ContinuousColorPaintScale implements PaintScale
22  {
23      /** Boundary values for this ColorPaintScale. */
24      private double[] bounds;
25  
26      /** Color values to use at the boundary values. */
27      private Color[] boundColors;
28  
29      /** Format string to render values in a human readable format (used in tool tip texts). */
30      private final String format;
31  
32      /**
33       * Create a new ContinuousColorPaintScale.
34       * @param format Format string to render the value under the mouse in a human readable format
35       * @param bounds Double[] array of boundary values (all values must be distinct and the number of values must be >= 2)
36       * @param boundColors Color[] array of the colors to use at the boundary values (must have same size as bounds)
37       */
38      ContinuousColorPaintScale(final String format, final double[] bounds, final Color[] boundColors)
39      {
40          this.format = format;
41          if (bounds.length < 2)
42          {
43              throw new Error("bounds must have >= 2 entries");
44          }
45          if (bounds.length != boundColors.length)
46          {
47              throw new Error("bounds must have same length as boundColors");
48          }
49          this.bounds = new double[bounds.length];
50          this.boundColors = new Color[bounds.length];
51          // Store the bounds and boundColors in order of increasing bound value.
52          // This is as inefficient as bubble sorting, but we're dealing with only a few values here.
53          for (int nextBound = 0; nextBound < bounds.length; nextBound++)
54          {
55              // Find the lowest not-yet used bound
56              double currentLowest = Double.POSITIVE_INFINITY;
57              int bestIndex = -1;
58              int index;
59              for (index = 0; index < bounds.length; index++)
60              {
61                  if (bounds[index] < currentLowest && (nextBound == 0 || bounds[index] > this.bounds[nextBound - 1]))
62                  {
63                      bestIndex = index;
64                      currentLowest = bounds[index];
65                  }
66              }
67              if (bestIndex < 0)
68              {
69                  throw new Error("duplicate value in bounds");
70              }
71              this.bounds[nextBound] = bounds[bestIndex];
72              this.boundColors[nextBound] = boundColors[bestIndex];
73          }
74      }
75  
76      /** {@inheritDoc} */
77      @Override
78      public final double getLowerBound()
79      {
80          return this.bounds[0];
81      }
82  
83      /**
84       * Create a mixed color component. When ratio varies from 0.0 to 1.0, the result varies from <i>low</i> to <i>high</i>. If
85       * ratio is outside the range 0.0 to 1.0, the result value can be outside the <i>range</i> <i>low</i> to <i>high</i>.
86       * However, the result is always limited to the range 0..255.
87       * @param ratio Double; value (normally) between 0.0 and 1.0.
88       * @param low Integer; this value is returned when ratio equals 0.0
89       * @param high Integer; this value is returned when ratio equals 1.0
90       * @return Integer; the ratio-weighted average of <i>low</i> and <i>high</i>
91       */
92      private static int mixComponent(final double ratio, final int low, final int high)
93      {
94          final double mix = low * (1 - ratio) + high * ratio;
95          int result = (int) mix;
96          if (result < 0)
97          {
98              result = 0;
99          }
100         if (result > 255)
101         {
102             result = 255;
103         }
104         return result;
105     }
106 
107     /** {@inheritDoc} */
108     @Override
109     public final Paint getPaint(final double value)
110     {
111         int bucket;
112         for (bucket = 0; bucket < this.bounds.length - 1; bucket++)
113         {
114             if (value < this.bounds[bucket + 1])
115             {
116                 break;
117             }
118         }
119         if (bucket >= this.bounds.length - 1)
120         {
121             bucket = this.bounds.length - 2;
122         }
123         final double ratio = (value - this.bounds[bucket]) / (this.bounds[bucket + 1] - this.bounds[bucket]);
124         Color mix =
125             new Color(mixComponent(ratio, this.boundColors[bucket].getRed(), this.boundColors[bucket + 1].getRed()),
126                 mixComponent(ratio, this.boundColors[bucket].getGreen(), this.boundColors[bucket + 1].getGreen()),
127                 mixComponent(ratio, this.boundColors[bucket].getBlue(), this.boundColors[bucket + 1].getBlue()));
128         return mix;
129     }
130 
131     /** {@inheritDoc} */
132     @Override
133     public final double getUpperBound()
134     {
135         return this.bounds[this.bounds.length - 1];
136     }
137 
138     /**
139      * Retrieve the format string.
140      * @return format string
141      */
142     public final String getFormat()
143     {
144         return this.format;
145     }
146 
147 }