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