View Javadoc
1   /*
2   
3       dsh-color-scheme  Color schemes.
4       Copyright (c) 2009-2016 held jointly by the individual authors.
5   
6       This library is free software; you can redistribute it and/or modify it
7       under the terms of the GNU Lesser General Public License as published
8       by the Free Software Foundation; either version 3 of the License, or (at
9       your option) any later version.
10  
11      This library is distributed in the hope that it will be useful, but WITHOUT
12      ANY WARRANTY; with out even the implied warranty of MERCHANTABILITY or
13      FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14      License for more details.
15  
16      You should have received a copy of the GNU Lesser General Public License
17      along with this library;  if not, write to the Free Software Foundation,
18      Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA.
19  
20      > http://www.fsf.org/licensing/licenses/lgpl.html
21      > http://www.opensource.org/licenses/lgpl-license.php
22  
23  */
24  package org.dishevelled.color.scheme.impl;
25  
26  import java.awt.Color;
27  
28  import java.util.ArrayList;
29  import java.util.List;
30  
31  import org.dishevelled.color.scheme.ColorFactory;
32  import org.dishevelled.color.scheme.ColorScheme;
33  
34  /**
35   * Continuous divergent color scheme.
36   *
37   * @author  Michael Heuer
38   */
39  public final class ContinuousDivergentColorScheme
40      implements ColorScheme
41  {
42      /** Name. */
43      private final String name;
44  
45      /** List of colors. */
46      private final List<Color> colors;
47  
48      /** List of anchors. */
49      private final List<Double> anchors;
50  
51      /** Minimum value. */
52      private double minimumValue;
53  
54      /** Maximum value. */
55      private double maximumValue;
56  
57      /** Zero value. */
58      private double zeroValue;
59  
60      /** Color factory. */
61      private final ColorFactory colorFactory;
62  
63  
64      /**
65       * Create a new continuous divergent color scheme.
66       *
67       * @param name name
68       * @param colors list of colors, must not be null and must contain at least two colors
69       * @param minimumValue minimum value
70       * @param zeroValue zero value
71       * @param maximumValue maximum value
72       * @param colorFactory color factory, must not be null
73       */
74      public ContinuousDivergentColorScheme(final String name,
75                                            final List<Color> colors,
76                                            final double minimumValue,
77                                            final double zeroValue,
78                                            final double maximumValue,
79                                            final ColorFactory colorFactory)
80      {
81          if (colors == null)
82          {
83              throw new IllegalArgumentException("colors must not be null");
84          }
85          if (colors.size() < 2)
86          {
87              throw new IllegalArgumentException("colors must contain at least two colors");
88          }
89          if (colorFactory == null)
90          {
91              throw new IllegalArgumentException("colorFactory must not be null");
92          }
93          this.name = name;
94          this.colors = new ArrayList<Color>(colors);
95  
96          this.minimumValue = minimumValue;
97          this.zeroValue = zeroValue;
98          this.maximumValue = maximumValue;
99          this.colorFactory = colorFactory;
100 
101         anchors = new ArrayList<Double>(colors.size() - 1);
102         recalculateAnchors();
103     }
104 
105 
106     /**
107      * Recalculate anchors.
108      */
109     private void recalculateAnchors()
110     {
111         if (!anchors.isEmpty())
112         {
113             anchors.clear();
114         }
115 
116         // ick.
117         double z = zeroValue;
118         double mn = minimumValue;
119         double mx = maximumValue;
120         double x = (z - mn);
121         double y = (mx - z);
122         int c = colors.size();
123         int d = (c / 2);
124 
125         if ((c % 2) == 0)
126         {
127             anchors.add(Math.min(mn, z));
128             for (int i = 1; i < d; i++)
129             {
130                 anchors.add((2 * i * x) / (c - 1)); // Math.min?
131             }
132             for (int i = d + 1; i < c; i++)
133             {
134                 anchors.add(((2 * (i - d) - 1) * y) / (c - 1) + z); // Math.max?
135             }
136             anchors.add(Math.max(mx, z));
137         }
138         else
139         {
140             anchors.add(Math.min(mn, z));
141             for (int i = 1; i < d; i++)
142             {
143                 anchors.add((2 * i * x) / (c - 1));
144             }
145             anchors.add(z);
146             for (int i = d + 1; i < (c - 1); i++)
147             {
148                 anchors.add(((2 * (i - d) - 1) * y) / (c - 1) + z);
149             }
150             anchors.add(Math.max(mx, z));
151         }
152     }
153 
154     /**
155      * Return the name of this continuous color scheme.
156      *
157      * @return the name of this continuous color scheme
158      */
159     public String getName()
160     {
161         return name;
162     }
163 
164     @Override
165     public double getMinimumValue()
166     {
167         return minimumValue;
168     }
169 
170     @Override
171     public void setMinimumValue(final double minimumValue)
172     {
173         this.minimumValue = minimumValue;
174         recalculateAnchors();
175     }
176 
177     @Override
178     public double getMaximumValue()
179     {
180         return maximumValue;
181     }
182 
183     @Override
184     public void setMaximumValue(final double maximumValue)
185     {
186         this.maximumValue = maximumValue;
187         recalculateAnchors();
188     }
189 
190     @Override
191     public double getZeroValue()
192     {
193         return zeroValue;
194     }
195 
196     @Override
197     public void setZeroValue(final double zeroValue)
198     {
199         this.zeroValue = zeroValue;
200         recalculateAnchors();
201     }
202 
203     @Override
204     public ColorFactory getColorFactory()
205     {
206         return colorFactory;
207     }
208 
209     @Override
210     public void setColorFactory(final ColorFactory colorFactory)
211     {
212         throw new UnsupportedOperationException("setMaximumValue operation not supported by this color scheme");
213     }
214 
215     /**
216      * Return the minimum color for this color scheme.
217      *
218      * @return the minimum color for this color scheme
219      */
220     private Color getMinimumColor()
221     {
222         return colors.get(0);
223     }
224 
225     /**
226      * Return the minimum anchor for this color scheme.
227      *
228      * @return the minimum anchor for this color scheme
229      */
230     private double getMinimumAnchor()
231     {
232         return anchors.get(0);
233     }
234 
235     /**
236      * Return the maximum color for this color scheme.
237      *
238      * @return the maximum color for this color scheme
239      */
240     private Color getMaximumColor()
241     {
242         return colors.get(colors.size() - 1);
243     }
244 
245     @Override
246     public Color getColor(final double value)
247     {
248         if (value < getMinimumAnchor())
249         {
250             return getMinimumColor();
251         }
252         Color lowerColor = getMinimumColor();
253         double lowerAnchor = getMinimumAnchor();
254         for (int i = 0; i < anchors.size(); i++)
255         {
256             double upperAnchor = anchors.get(i);
257             if (value == upperAnchor)
258             {
259                 return colors.get(i);
260             }
261             else if (value < upperAnchor)
262             {
263                 Color upperColor = colors.get(i);
264                 lowerAnchor = anchors.get(i - 1);
265                 lowerColor = colors.get(i - 1);
266                 int r = (int) interpolate(value, lowerAnchor, upperAnchor,
267                                           (double) lowerColor.getRed(), (double) upperColor.getRed());
268                 int g = (int) interpolate(value, lowerAnchor, upperAnchor,
269                                           (double) lowerColor.getGreen(), (double) upperColor.getGreen());
270                 int b = (int) interpolate(value, lowerAnchor, upperAnchor,
271                                           (double) lowerColor.getBlue(), (double) upperColor.getBlue());
272                 int a = (int) interpolate(value, lowerAnchor, upperAnchor,
273                                           (double) lowerColor.getAlpha(), (double) upperColor.getAlpha());
274                 return colorFactory.createColor(r, g, b, ((float) a) / 255.0f);
275             }
276         }
277         return getMaximumColor();
278     }
279 
280     /**
281      * Interpolate the specified value from the specified source range to the specified target range.
282      *
283      * @param value value to interpolate
284      * @param sourceMinimum source range minimum value
285      * @param sourceMaximum source range maximum value
286      * @param targetMinimum target range minimum value
287      * @param targetMaximum target range maximum value
288      * @return the specified value interpolated from the specified source range to the specified target range
289      */
290     private static double interpolate(final double value,
291                                       final double sourceMinimum,
292                                       final double sourceMaximum,
293                                       final double targetMinimum,
294                                       final double targetMaximum)
295     {
296         return targetMinimum + (targetMaximum - targetMinimum)
297             * ((value - sourceMinimum) / (sourceMaximum - sourceMinimum));
298     }
299 }