2733 |
16 Oct 06 |
nicklas |
1 |
/** |
2733 |
16 Oct 06 |
nicklas |
$Id$ |
2733 |
16 Oct 06 |
nicklas |
3 |
|
3675 |
16 Aug 07 |
jari |
Copyright (C) 2006 Nicklas Nordborg |
2733 |
16 Oct 06 |
nicklas |
5 |
|
2733 |
16 Oct 06 |
nicklas |
This file is part of BASE - BioArray Software Environment. |
2733 |
16 Oct 06 |
nicklas |
Available at http://base.thep.lu.se/ |
2733 |
16 Oct 06 |
nicklas |
8 |
|
2733 |
16 Oct 06 |
nicklas |
BASE is free software; you can redistribute it and/or |
2733 |
16 Oct 06 |
nicklas |
modify it under the terms of the GNU General Public License |
4479 |
05 Sep 08 |
jari |
as published by the Free Software Foundation; either version 3 |
2733 |
16 Oct 06 |
nicklas |
of the License, or (at your option) any later version. |
2733 |
16 Oct 06 |
nicklas |
13 |
|
2733 |
16 Oct 06 |
nicklas |
BASE is distributed in the hope that it will be useful, |
2733 |
16 Oct 06 |
nicklas |
but WITHOUT ANY WARRANTY; without even the implied warranty of |
2733 |
16 Oct 06 |
nicklas |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2733 |
16 Oct 06 |
nicklas |
GNU General Public License for more details. |
2733 |
16 Oct 06 |
nicklas |
18 |
|
2733 |
16 Oct 06 |
nicklas |
You should have received a copy of the GNU General Public License |
4515 |
11 Sep 08 |
jari |
along with BASE. If not, see <http://www.gnu.org/licenses/>. |
2733 |
16 Oct 06 |
nicklas |
21 |
*/ |
2733 |
16 Oct 06 |
nicklas |
22 |
package net.sf.basedb.util; |
2733 |
16 Oct 06 |
nicklas |
23 |
|
2733 |
16 Oct 06 |
nicklas |
24 |
import java.awt.Color; |
2733 |
16 Oct 06 |
nicklas |
25 |
|
2733 |
16 Oct 06 |
nicklas |
26 |
import net.sf.basedb.core.InvalidDataException; |
2733 |
16 Oct 06 |
nicklas |
27 |
import net.sf.basedb.core.NumberOutOfRangeException; |
2733 |
16 Oct 06 |
nicklas |
28 |
|
2733 |
16 Oct 06 |
nicklas |
29 |
/** |
2733 |
16 Oct 06 |
nicklas |
This class is used to generate colors for numerical values. Given a |
2733 |
16 Oct 06 |
nicklas |
numeric range mith a start, mid and max value and one color for each |
2733 |
16 Oct 06 |
nicklas |
of these values you can create new colors for values within the specified |
2733 |
16 Oct 06 |
nicklas |
range. |
2733 |
16 Oct 06 |
nicklas |
<p> |
2733 |
16 Oct 06 |
nicklas |
Example:<br> |
2733 |
16 Oct 06 |
nicklas |
We have the range and colors: <code>-1 (BLUE), 0 (WHITE) , +1 (YELLOW)</code> |
2733 |
16 Oct 06 |
nicklas |
The value -0.5 is converted to a lighter blue and the value +0.5 is converted |
2733 |
16 Oct 06 |
nicklas |
to a ligther yellow. Values falling outside the min or max values are |
2733 |
16 Oct 06 |
nicklas |
assigned the endpoint colors. In this case, BLUE and YELLOW. |
2733 |
16 Oct 06 |
nicklas |
40 |
|
2733 |
16 Oct 06 |
nicklas |
@author nicklas |
2733 |
16 Oct 06 |
nicklas |
@version 2.0 |
2733 |
16 Oct 06 |
nicklas |
@base.modified $Date$ |
2733 |
16 Oct 06 |
nicklas |
44 |
*/ |
2733 |
16 Oct 06 |
nicklas |
45 |
public class ColorGenerator |
2733 |
16 Oct 06 |
nicklas |
46 |
{ |
2733 |
16 Oct 06 |
nicklas |
47 |
|
2733 |
16 Oct 06 |
nicklas |
48 |
/** |
2733 |
16 Oct 06 |
nicklas |
Constant holding the value of <code>1 / ln(2)</code>. It is used |
2733 |
16 Oct 06 |
nicklas |
to calculate the two-based logarithm of values using the formula: |
2733 |
16 Oct 06 |
nicklas |
<code>log2(x) = ln(x) / ln(2)</code> |
2733 |
16 Oct 06 |
nicklas |
52 |
*/ |
2733 |
16 Oct 06 |
nicklas |
53 |
public static final float INVLN2 = (float)(1 / Math.log(2)); |
2733 |
16 Oct 06 |
nicklas |
54 |
|
2733 |
16 Oct 06 |
nicklas |
55 |
/** |
2733 |
16 Oct 06 |
nicklas |
Convert a hexadecimal color string such as one used in HTML |
2733 |
16 Oct 06 |
nicklas |
to a Color object. The hexadecimal string must have 6 characters |
2733 |
16 Oct 06 |
nicklas |
in the range 0-9 or A-F (case ignored). |
2733 |
16 Oct 06 |
nicklas |
@param hexRGB The hexadecimal color string |
2733 |
16 Oct 06 |
nicklas |
@return A Color object |
2733 |
16 Oct 06 |
nicklas |
@throws InvalidDataException If the string cannot be converted |
2733 |
16 Oct 06 |
nicklas |
62 |
*/ |
2733 |
16 Oct 06 |
nicklas |
63 |
public static Color fromHex(String hexRGB) |
2733 |
16 Oct 06 |
nicklas |
64 |
{ |
2733 |
16 Oct 06 |
nicklas |
65 |
if (hexRGB == null || !hexRGB.matches("[0-9A-Fa-f]{6}")) |
2733 |
16 Oct 06 |
nicklas |
66 |
{ |
2733 |
16 Oct 06 |
nicklas |
67 |
throw new InvalidDataException("Not a hex RGB value: " + hexRGB); |
2733 |
16 Oct 06 |
nicklas |
68 |
} |
2733 |
16 Oct 06 |
nicklas |
69 |
int r = Integer.parseInt(hexRGB.substring(0, 2), 16); |
2733 |
16 Oct 06 |
nicklas |
70 |
int g = Integer.parseInt(hexRGB.substring(2, 4), 16); |
2733 |
16 Oct 06 |
nicklas |
71 |
int b = Integer.parseInt(hexRGB.substring(4, 6), 16); |
2733 |
16 Oct 06 |
nicklas |
72 |
return new Color(r, g, b); |
2733 |
16 Oct 06 |
nicklas |
73 |
} |
2733 |
16 Oct 06 |
nicklas |
74 |
|
2733 |
16 Oct 06 |
nicklas |
75 |
/** |
2733 |
16 Oct 06 |
nicklas |
Generate a hex color string from a Color object. Useful for |
2733 |
16 Oct 06 |
nicklas |
outputting into HTML pages. |
2733 |
16 Oct 06 |
nicklas |
78 |
|
2733 |
16 Oct 06 |
nicklas |
@param color The color object |
2733 |
16 Oct 06 |
nicklas |
@return The hexadecimal representation of the color |
2733 |
16 Oct 06 |
nicklas |
81 |
*/ |
2733 |
16 Oct 06 |
nicklas |
82 |
public static String toHex(Color color) |
2733 |
16 Oct 06 |
nicklas |
83 |
{ |
2733 |
16 Oct 06 |
nicklas |
84 |
StringBuilder sb = new StringBuilder(6); |
2733 |
16 Oct 06 |
nicklas |
85 |
int r = color.getRed(); |
2733 |
16 Oct 06 |
nicklas |
86 |
if (r < 16) sb.append("0"); |
2733 |
16 Oct 06 |
nicklas |
87 |
sb.append(Integer.toHexString(r)); |
2733 |
16 Oct 06 |
nicklas |
88 |
|
2733 |
16 Oct 06 |
nicklas |
89 |
int g = color.getGreen(); |
2733 |
16 Oct 06 |
nicklas |
90 |
if (g < 16) sb.append("0"); |
2733 |
16 Oct 06 |
nicklas |
91 |
sb.append(Integer.toHexString(g)); |
2733 |
16 Oct 06 |
nicklas |
92 |
|
2733 |
16 Oct 06 |
nicklas |
93 |
int b = color.getBlue(); |
2733 |
16 Oct 06 |
nicklas |
94 |
if (b < 16) sb.append("0"); |
2733 |
16 Oct 06 |
nicklas |
95 |
sb.append(Integer.toHexString(b)); |
2733 |
16 Oct 06 |
nicklas |
96 |
return sb.toString(); |
2733 |
16 Oct 06 |
nicklas |
97 |
} |
2733 |
16 Oct 06 |
nicklas |
98 |
|
2733 |
16 Oct 06 |
nicklas |
99 |
private Color minColor; |
2733 |
16 Oct 06 |
nicklas |
100 |
private Color midColor; |
2733 |
16 Oct 06 |
nicklas |
101 |
private Color maxColor; |
2733 |
16 Oct 06 |
nicklas |
102 |
private float minValue; |
2733 |
16 Oct 06 |
nicklas |
103 |
private float midValue; |
2733 |
16 Oct 06 |
nicklas |
104 |
private float maxValue; |
2733 |
16 Oct 06 |
nicklas |
105 |
private boolean log; |
2733 |
16 Oct 06 |
nicklas |
106 |
|
2733 |
16 Oct 06 |
nicklas |
107 |
/** |
2733 |
16 Oct 06 |
nicklas |
The RGB components of the min color. [0] = red; [1] = green; [2] = blue |
2733 |
16 Oct 06 |
nicklas |
109 |
*/ |
2733 |
16 Oct 06 |
nicklas |
110 |
private final int[] offsetMin; |
2733 |
16 Oct 06 |
nicklas |
111 |
|
2733 |
16 Oct 06 |
nicklas |
112 |
/** |
2733 |
16 Oct 06 |
nicklas |
The RGB components of the midpoint color. [0] = red; [1] = green; [2] = blue |
2733 |
16 Oct 06 |
nicklas |
114 |
*/ |
2733 |
16 Oct 06 |
nicklas |
115 |
private final int[] offsetMid; |
2733 |
16 Oct 06 |
nicklas |
116 |
|
2733 |
16 Oct 06 |
nicklas |
117 |
/** |
2733 |
16 Oct 06 |
nicklas |
The difference of the RGB components of the min and mid |
2733 |
16 Oct 06 |
nicklas |
colors. [0] = red; [1] = green; [2] = blue |
2733 |
16 Oct 06 |
nicklas |
120 |
*/ |
2733 |
16 Oct 06 |
nicklas |
121 |
private final int[] deltaMinMid; |
2733 |
16 Oct 06 |
nicklas |
122 |
|
2733 |
16 Oct 06 |
nicklas |
123 |
/** |
2733 |
16 Oct 06 |
nicklas |
The difference of the RGB components of the mid and max |
2733 |
16 Oct 06 |
nicklas |
colors. [0] = red; [1] = green; [2] = blue |
2733 |
16 Oct 06 |
nicklas |
126 |
*/ |
2733 |
16 Oct 06 |
nicklas |
127 |
private final int[] deltaMidMax; |
2733 |
16 Oct 06 |
nicklas |
128 |
|
2733 |
16 Oct 06 |
nicklas |
129 |
/** |
2733 |
16 Oct 06 |
nicklas |
The inverted difference between the mid and min values: |
2733 |
16 Oct 06 |
nicklas |
1 / (midValue - minValue) |
2733 |
16 Oct 06 |
nicklas |
132 |
*/ |
2733 |
16 Oct 06 |
nicklas |
133 |
private final float rangeMinMid; |
2733 |
16 Oct 06 |
nicklas |
134 |
|
2733 |
16 Oct 06 |
nicklas |
135 |
/** |
2733 |
16 Oct 06 |
nicklas |
The inverted difference between the max and mid values: |
2733 |
16 Oct 06 |
nicklas |
1 / (maxValue - midValue) |
2733 |
16 Oct 06 |
nicklas |
138 |
*/ |
2733 |
16 Oct 06 |
nicklas |
139 |
private final float rangeMidMax; |
2733 |
16 Oct 06 |
nicklas |
140 |
|
2733 |
16 Oct 06 |
nicklas |
141 |
/** |
2733 |
16 Oct 06 |
nicklas |
Create a new color generator. |
2733 |
16 Oct 06 |
nicklas |
@param minColor The color for the minimum endpoint |
2733 |
16 Oct 06 |
nicklas |
@param midColor The color for the midpoint |
2733 |
16 Oct 06 |
nicklas |
@param maxColor The color for the maximum endpoint |
2733 |
16 Oct 06 |
nicklas |
@param minValue The value for the minimum endpoint |
2733 |
16 Oct 06 |
nicklas |
@param midValue The value for the midpoint |
2733 |
16 Oct 06 |
nicklas |
@param maxValue The value for the maximum endpoint |
2733 |
16 Oct 06 |
nicklas |
@param log If values should be logarithmised before beeing assigned a |
2733 |
16 Oct 06 |
nicklas |
color or not. Note the values in this constructor are not affected by this |
2733 |
16 Oct 06 |
nicklas |
setting. |
2733 |
16 Oct 06 |
nicklas |
@throws InvalidDataException If not the minValue <= midValue <= maxValue |
2733 |
16 Oct 06 |
nicklas |
153 |
*/ |
2733 |
16 Oct 06 |
nicklas |
154 |
public ColorGenerator(Color minColor, Color midColor, Color maxColor, |
2733 |
16 Oct 06 |
nicklas |
155 |
float minValue, float midValue, float maxValue, boolean log) |
2733 |
16 Oct 06 |
nicklas |
156 |
{ |
2733 |
16 Oct 06 |
nicklas |
// Check that minValue <= midValue <= maxValue |
2733 |
16 Oct 06 |
nicklas |
158 |
if (minValue > midValue) |
2733 |
16 Oct 06 |
nicklas |
159 |
{ |
2733 |
16 Oct 06 |
nicklas |
160 |
throw new NumberOutOfRangeException("minValue", minValue, midValue, true); |
2733 |
16 Oct 06 |
nicklas |
161 |
} |
2733 |
16 Oct 06 |
nicklas |
162 |
if (midValue > maxValue) |
2733 |
16 Oct 06 |
nicklas |
163 |
{ |
2733 |
16 Oct 06 |
nicklas |
164 |
throw new NumberOutOfRangeException("midValue", midValue, maxValue, true); |
2733 |
16 Oct 06 |
nicklas |
165 |
} |
2733 |
16 Oct 06 |
nicklas |
166 |
|
2733 |
16 Oct 06 |
nicklas |
// Store paramters |
2733 |
16 Oct 06 |
nicklas |
168 |
this.minColor = minColor; |
2733 |
16 Oct 06 |
nicklas |
169 |
this.midColor = midColor; |
2733 |
16 Oct 06 |
nicklas |
170 |
this.maxColor = maxColor; |
2733 |
16 Oct 06 |
nicklas |
171 |
this.minValue = minValue; |
2733 |
16 Oct 06 |
nicklas |
172 |
this.midValue = midValue; |
2733 |
16 Oct 06 |
nicklas |
173 |
this.maxValue = maxValue; |
2733 |
16 Oct 06 |
nicklas |
174 |
this.log = log; |
2733 |
16 Oct 06 |
nicklas |
175 |
|
2733 |
16 Oct 06 |
nicklas |
// Precalculate some settings |
2733 |
16 Oct 06 |
nicklas |
177 |
this.offsetMin = new int[3]; |
2733 |
16 Oct 06 |
nicklas |
178 |
offsetMin[0] = minColor.getRed(); |
2733 |
16 Oct 06 |
nicklas |
179 |
offsetMin[1] = minColor.getGreen(); |
2733 |
16 Oct 06 |
nicklas |
180 |
offsetMin[2] = minColor.getBlue(); |
2733 |
16 Oct 06 |
nicklas |
181 |
|
2733 |
16 Oct 06 |
nicklas |
182 |
this.offsetMid = new int[3]; |
2733 |
16 Oct 06 |
nicklas |
183 |
offsetMid[0] = midColor.getRed(); |
2733 |
16 Oct 06 |
nicklas |
184 |
offsetMid[1] = midColor.getGreen(); |
2733 |
16 Oct 06 |
nicklas |
185 |
offsetMid[2] = midColor.getBlue(); |
2733 |
16 Oct 06 |
nicklas |
186 |
|
2733 |
16 Oct 06 |
nicklas |
187 |
this.deltaMinMid = new int[3]; |
2733 |
16 Oct 06 |
nicklas |
188 |
for (int i = 0; i < 3; ++i) |
2733 |
16 Oct 06 |
nicklas |
189 |
{ |
2733 |
16 Oct 06 |
nicklas |
190 |
deltaMinMid[i] = offsetMid[i] - offsetMin[i]; |
2733 |
16 Oct 06 |
nicklas |
191 |
} |
2733 |
16 Oct 06 |
nicklas |
192 |
|
2733 |
16 Oct 06 |
nicklas |
193 |
this.deltaMidMax = new int[3]; |
2733 |
16 Oct 06 |
nicklas |
194 |
deltaMidMax[0] = maxColor.getRed() - offsetMid[0]; |
2733 |
16 Oct 06 |
nicklas |
195 |
deltaMidMax[1] = maxColor.getGreen() - offsetMid[1]; |
2733 |
16 Oct 06 |
nicklas |
196 |
deltaMidMax[2] = maxColor.getBlue() - offsetMid[2]; |
2733 |
16 Oct 06 |
nicklas |
197 |
|
2733 |
16 Oct 06 |
nicklas |
198 |
this.rangeMinMid = midValue == minValue ? 0 : 1 / (midValue - minValue); |
2733 |
16 Oct 06 |
nicklas |
199 |
this.rangeMidMax = maxValue == midValue ? 0 : 1 / (maxValue - midValue); |
2733 |
16 Oct 06 |
nicklas |
200 |
} |
2733 |
16 Oct 06 |
nicklas |
201 |
|
2733 |
16 Oct 06 |
nicklas |
202 |
/** |
2733 |
16 Oct 06 |
nicklas |
Get the color for the value. If the log flag was true when creating the |
2733 |
16 Oct 06 |
nicklas |
generator the value is first logarithmised. Note that negative values |
2733 |
16 Oct 06 |
nicklas |
can't be logarithmised so null is returned in those cases. |
2733 |
16 Oct 06 |
nicklas |
<p> |
2733 |
16 Oct 06 |
nicklas |
If the value is lower than the min endpoint the min endpoint color |
2733 |
16 Oct 06 |
nicklas |
is returned. If the value is higher than the max endpoint the |
2733 |
16 Oct 06 |
nicklas |
max endpoint color is returned. Otherwise a new color is calculated |
2733 |
16 Oct 06 |
nicklas |
based on the difference between the value and the given points. |
2733 |
16 Oct 06 |
nicklas |
211 |
|
2733 |
16 Oct 06 |
nicklas |
@param value The value |
2733 |
16 Oct 06 |
nicklas |
@return A color object or null if no color could be generated |
2733 |
16 Oct 06 |
nicklas |
214 |
*/ |
2733 |
16 Oct 06 |
nicklas |
215 |
public Color getColor(float value) |
2733 |
16 Oct 06 |
nicklas |
216 |
{ |
2789 |
24 Oct 06 |
nicklas |
217 |
if (Float.isNaN(value)) return null; |
2733 |
16 Oct 06 |
nicklas |
218 |
|
2733 |
16 Oct 06 |
nicklas |
219 |
if (log) |
2733 |
16 Oct 06 |
nicklas |
220 |
{ |
2733 |
16 Oct 06 |
nicklas |
221 |
if (value <= 0) return null; |
2733 |
16 Oct 06 |
nicklas |
222 |
value = ((float)Math.log(value) * INVLN2); |
2733 |
16 Oct 06 |
nicklas |
223 |
} |
2733 |
16 Oct 06 |
nicklas |
224 |
|
2733 |
16 Oct 06 |
nicklas |
225 |
Color newColor = null; |
2733 |
16 Oct 06 |
nicklas |
226 |
if (value >= maxValue) |
2733 |
16 Oct 06 |
nicklas |
227 |
{ |
2733 |
16 Oct 06 |
nicklas |
228 |
newColor = maxColor; |
2733 |
16 Oct 06 |
nicklas |
229 |
} |
2733 |
16 Oct 06 |
nicklas |
230 |
else if (value <= minValue) |
2733 |
16 Oct 06 |
nicklas |
231 |
{ |
2733 |
16 Oct 06 |
nicklas |
232 |
newColor = minColor; |
2733 |
16 Oct 06 |
nicklas |
233 |
} |
2733 |
16 Oct 06 |
nicklas |
234 |
else |
2733 |
16 Oct 06 |
nicklas |
235 |
{ |
2733 |
16 Oct 06 |
nicklas |
236 |
float factor; |
2733 |
16 Oct 06 |
nicklas |
237 |
int[] delta; |
2733 |
16 Oct 06 |
nicklas |
238 |
int[] offset; |
2733 |
16 Oct 06 |
nicklas |
239 |
if (value >= midValue) |
2733 |
16 Oct 06 |
nicklas |
240 |
{ |
2733 |
16 Oct 06 |
nicklas |
241 |
delta = deltaMidMax; |
2733 |
16 Oct 06 |
nicklas |
242 |
offset = offsetMid; |
2733 |
16 Oct 06 |
nicklas |
243 |
factor = (value - midValue) * rangeMidMax; |
2733 |
16 Oct 06 |
nicklas |
244 |
} |
2733 |
16 Oct 06 |
nicklas |
245 |
else |
2733 |
16 Oct 06 |
nicklas |
246 |
{ |
2733 |
16 Oct 06 |
nicklas |
247 |
delta = deltaMinMid; |
2733 |
16 Oct 06 |
nicklas |
248 |
offset = offsetMin; |
2733 |
16 Oct 06 |
nicklas |
249 |
factor = (value - minValue) * rangeMinMid; |
2733 |
16 Oct 06 |
nicklas |
250 |
} |
2733 |
16 Oct 06 |
nicklas |
251 |
int r = (int)(offset[0] + factor * delta[0]); |
2733 |
16 Oct 06 |
nicklas |
252 |
int g = (int)(offset[1] + factor * delta[1]); |
2733 |
16 Oct 06 |
nicklas |
253 |
int b = (int)(offset[2] + factor * delta[2]); |
2733 |
16 Oct 06 |
nicklas |
254 |
newColor = new Color(r, g, b); |
2733 |
16 Oct 06 |
nicklas |
255 |
} |
2733 |
16 Oct 06 |
nicklas |
256 |
return newColor; |
2733 |
16 Oct 06 |
nicklas |
257 |
} |
2733 |
16 Oct 06 |
nicklas |
258 |
|
2733 |
16 Oct 06 |
nicklas |
259 |
} |