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