View Javadoc
1   package org.opentrafficsim.draw.core;
2   
3   import java.awt.Color;
4   import java.io.Serializable;
5   import java.util.Arrays;
6   
7   import org.djutils.exceptions.Throw;
8   import org.opentrafficsim.core.animation.ColorInterpolator;
9   
10  import nl.tudelft.simulation.dsol.logger.SimLogger;
11  
12  /**
13   * Paint scale interpolating between colors at values.
14   * <p>
15   * Copyright (c) 2013-2019 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/node/13">OpenTrafficSim License</a>.
17   * <p>
18   * @version $Revision$, $LastChangedDate$, by $Author$, initial version 8 okt. 2018 <br>
19   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
20   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
21   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
22   */
23  public class BoundsPaintScale implements ColorPaintScale, Serializable
24  {
25  
26      /** 3-color scale from green to red. */
27      public static final Color[] GREEN_RED = new Color[] {Color.GREEN, Color.YELLOW, Color.RED};
28  
29      /** 5-color scale from green to red with dark edges. */
30      public static final Color[] GREEN_RED_DARK =
31              new Color[] {Color.GREEN.darker(), Color.GREEN, Color.YELLOW, Color.RED, Color.RED.darker()};
32  
33      /** */
34      private static final long serialVersionUID = 20181008L;
35  
36      /** Boundary values for this ColorPaintScale. */
37      private double[] bounds;
38  
39      /** Color values to use at the boundary values. */
40      private Color[] boundColors;
41  
42      /**
43       * Constructor.
44       * @param bounds double[]; value bounds
45       * @param boundColors Color[]; colors at bounds
46       * @throws IllegalArgumentException if less than 2 bounds, unequal number of bounds and colors, or duplicate bounds
47       */
48      public BoundsPaintScale(final double[] bounds, final Color[] boundColors) throws IllegalArgumentException
49      {
50          Throw.when(bounds.length < 2, IllegalArgumentException.class, "bounds must have >= 2 entries");
51          Throw.when(bounds.length != boundColors.length, IllegalArgumentException.class,
52                  "bounds must have same length as boundColors");
53          this.bounds = new double[bounds.length];
54          this.boundColors = new Color[bounds.length];
55          // Store the bounds and boundColors in order of increasing bound value.
56          // This is as inefficient as bubble sorting, but we're dealing with only a few values here.
57          for (int nextBound = 0; nextBound < bounds.length; nextBound++)
58          {
59              // Find the lowest not-yet used bound
60              double currentLowest = Double.POSITIVE_INFINITY;
61              int bestIndex = -1;
62              int index;
63              for (index = 0; index < bounds.length; index++)
64              {
65                  if (bounds[index] < currentLowest && (nextBound == 0 || bounds[index] > this.bounds[nextBound - 1]))
66                  {
67                      bestIndex = index;
68                      currentLowest = bounds[index];
69                  }
70              }
71              Throw.when(bestIndex < 0, IllegalArgumentException.class, "duplicate value in bounds");
72              this.bounds[nextBound] = bounds[bestIndex];
73              this.boundColors[nextBound] = boundColors[bestIndex];
74          }
75      }
76  
77      /**
78       * Reverses the color array.
79       * @param colors Color[]; array of colors
80       * @return Color[]; reversed color array
81       */
82      public static Color[] reverse(final Color[] colors)
83      {
84          Color[] out = new Color[colors.length];
85          for (int i = 0; i < colors.length; i++)
86          {
87              out[colors.length - i - 1] = colors[i];
88          }
89          return out;
90      }
91  
92      /**
93       * Creates an array of {@code n} colors with varying hue.
94       * @param n int; number of colors.
95       * @return Color[]; array of {@code n} colors with varying hue
96       */
97      public static Color[] hue(final int n)
98      {
99          Color[] out = new Color[n];
100         for (int i = 0; i < n; i++)
101         {
102             out[i] = new Color(Color.HSBtoRGB(((float) i) / n, 1.0f, 1.0f));
103         }
104         return out;
105     }
106 
107     /** {@inheritDoc} */
108     @Override
109     public Color getPaint(final double value)
110     {
111         if (Double.isNaN(value))
112         {
113             return Color.BLACK;
114         }
115         if (value < this.bounds[0])
116         {
117             return this.boundColors[0];
118         }
119         if (value > this.bounds[this.bounds.length - 1])
120         {
121             return this.boundColors[this.bounds.length - 1];
122         }
123         int index;
124         for (index = 0; index < this.bounds.length - 1; index++)
125         {
126             if (value < this.bounds[index + 1])
127             {
128                 break;
129             }
130         }
131         final double ratio;
132         if (index >= this.bounds.length - 1)
133         {
134             index = this.bounds.length - 2;
135             ratio = 1.0;
136         }
137         else
138         {
139             ratio = (value - this.bounds[index]) / (this.bounds[index + 1] - this.bounds[index]);
140         }
141         if (Double.isInfinite(ratio))
142         {
143             SimLogger.always().error("Interpolation value for color is infinite based on {} in {} obtaining index {}.", value,
144                     this.bounds, index);
145         }
146         Color mix = ColorInterpolator.interpolateColor(this.boundColors[index], this.boundColors[index + 1], ratio);
147         return mix;
148     }
149 
150     /** {@inheritDoc} */
151     @Override
152     public final double getLowerBound()
153     {
154         return this.bounds[0];
155     }
156 
157     /** {@inheritDoc} */
158     @Override
159     public final double getUpperBound()
160     {
161         return this.bounds[this.bounds.length - 1];
162     }
163 
164     /** {@inheritDoc} */
165     @Override
166     public String toString()
167     {
168         return "BoundsPaintScale [bounds=" + Arrays.toString(this.bounds) + ", boundColors=" + Arrays.toString(this.boundColors)
169                 + "]";
170     }
171 
172 }