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