View Javadoc
1   /*
2    * ObjectLab, http://www.objectlab.co.uk/open is supporting JTreeMap.
3    *
4    * Based in London, we are world leaders in the design and development
5    * of bespoke applications for the securities financing markets.
6    *
7    * <a href="http://www.objectlab.co.uk/open">Click here to learn more</a>
8    *           ___  _     _           _   _          _
9    *          / _ \| |__ (_) ___  ___| |_| |    __ _| |__
10   *         | | | | '_ \| |/ _ \/ __| __| |   / _` | '_ \
11   *         | |_| | |_) | |  __/ (__| |_| |__| (_| | |_) |
12   *          \___/|_.__// |\___|\___|\__|_____\__,_|_.__/
13   *                   |__/
14   *
15   *                     www.ObjectLab.co.uk
16   *
17   * $Id: ColorProvider.java 69 2006-10-24 16:20:20Z benoitx $
18   *
19   * Copyright 2006 the original author or authors.
20   *
21   * Licensed under the Apache License, Version 2.0 (the "License"); you may not
22   * use this file except in compliance with the License. You may obtain a copy of
23   * the License at
24   *
25   * http://www.apache.org/licenses/LICENSE-2.0
26   *
27   * Unless required by applicable law or agreed to in writing, software
28   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
29   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
30   * License for the specific language governing permissions and limitations under
31   * the License.
32   */
33  package net.sf.jtreemap.swing.provider;
34  
35  import java.awt.Color;
36  import java.awt.Graphics;
37  import java.util.Enumeration;
38  
39  import javax.swing.JPanel;
40  
41  import lombok.extern.slf4j.Slf4j;
42  import net.sf.jtreemap.swing.DefaultValue;
43  import net.sf.jtreemap.swing.JTreeMap;
44  import net.sf.jtreemap.swing.TreeMapNode;
45  import net.sf.jtreemap.swing.Value;
46  
47  /**
48   * An HSB color space color provider for JTreeMap. Uses a specified function to
49   * map the values onto the HSB color space. The default is a linear function,
50   * but in my experience one of the logarithmic ones works best for this color
51   * space.
52   *
53   * @author Andy Adamczak
54   */
55  @Slf4j
56  public class HSBTreeMapColorProvider extends ColorProvider {
57      private static final int HSBVAL_SIZE = 3;
58  
59      /**
60       *
61       */
62      private static final long serialVersionUID = 5009655580804320847L;
63  
64      /**
65       * @author Andy Adamczak
66       */
67      public enum ColorDistributionTypes {
68          LINEAR,
69          LOG,
70          EXP,
71          SQUARE_ROOT,
72          CUBIC_ROOT
73      }
74  
75      /**
76       * @param treeMap
77       * @param color
78       */
79      public HSBTreeMapColorProvider(final JTreeMap treeMap, final Color color) {
80          this(treeMap, ColorDistributionTypes.LINEAR, color, color);
81      }
82  
83      /**
84       * @param treeMap
85       * @param colorDistribution
86       * @param color
87       */
88      public HSBTreeMapColorProvider(final JTreeMap treeMap, final ColorDistributionTypes colorDistribution, final Color color) {
89          this(treeMap, colorDistribution, color, color);
90      }
91  
92      /**
93       * @param treeMap
94       * @param positiveColor
95       * @param negativeColor
96       */
97      public HSBTreeMapColorProvider(final JTreeMap treeMap, final Color positiveColor, final Color negativeColor) {
98          this(treeMap, ColorDistributionTypes.LINEAR, positiveColor, negativeColor);
99      }
100 
101     /**
102      * @param treeMap
103      * @param colorDistribution
104      * @param positiveColor
105      * @param negativeColor
106      */
107     public HSBTreeMapColorProvider(final JTreeMap treeMap, final ColorDistributionTypes colorDistribution, final Color positiveColor,
108             final Color negativeColor) {
109         super();
110         jTreeMap = treeMap;
111         this.colorDistribution = colorDistribution;
112         adjustColor(positiveColor, negativeColor);
113     }
114 
115     /**
116      * @param treeMap
117      * @param hue
118      * @param saturation
119      */
120     public HSBTreeMapColorProvider(final JTreeMap treeMap, final float hue, final float saturation) {
121         this(treeMap, ColorDistributionTypes.LINEAR, hue, saturation, hue, saturation);
122     }
123 
124     /**
125      * @param treeMap
126      * @param colorDistribution
127      * @param hue
128      * @param saturation
129      */
130     public HSBTreeMapColorProvider(final JTreeMap treeMap, final ColorDistributionTypes colorDistribution, final float hue, final float saturation) {
131         this(treeMap, colorDistribution, hue, saturation, hue, saturation);
132     }
133 
134     /**
135      * @param treeMap
136      * @param positiveHue
137      * @param positiveSaturation
138      * @param negativeHue
139      * @param negativeSaturation
140      */
141     public HSBTreeMapColorProvider(final JTreeMap treeMap, final float positiveHue, final float positiveSaturation, final float negativeHue,
142             final float negativeSaturation) {
143         this(treeMap, ColorDistributionTypes.LINEAR, positiveHue, positiveSaturation, negativeHue, negativeSaturation);
144     }
145 
146     /**
147      * @param treeMap
148      * @param colorDistribution
149      * @param positiveHue
150      * @param positiveSaturation
151      * @param negativeHue
152      * @param negativeSaturation
153      */
154     public HSBTreeMapColorProvider(final JTreeMap treeMap, final ColorDistributionTypes colorDistribution, final float positiveHue,
155             final float positiveSaturation, final float negativeHue, final float negativeSaturation) {
156         super();
157         jTreeMap = treeMap;
158         this.colorDistribution = colorDistribution;
159         adjustColor(positiveHue, positiveSaturation, negativeHue, negativeSaturation);
160     }
161 
162     /*
163      * (non-Javadoc)
164      *
165      * @see net.sf.jtreemap.swing.ColorProvider#getLegendPanel()
166      */
167     @Override
168     public JPanel getLegendPanel() {
169         if (legend == null) {
170             legend = new Legend();
171         }
172 
173         return legend;
174     }
175 
176     /**
177      * @param color
178      */
179     public void adjustColor(final Color color) {
180         adjustColor(color, color);
181     }
182 
183     /**
184      * @param positiveColor
185      * @param negativeColor
186      */
187     public void adjustColor(final Color positiveColor, final Color negativeColor) {
188         // Figure out the hue of the passed in colors. Note, greys will map to
189         // reds in this color space, so use the
190         // hue/saturation
191         // constructions for grey scales.
192         float[] hsbvals = new float[HSBVAL_SIZE];
193 
194         hsbvals = Color.RGBtoHSB(positiveColor.getRed(), positiveColor.getGreen(), positiveColor.getBlue(), hsbvals);
195         positiveHue = hsbvals[0];
196         positiveSaturation = 1f;
197 
198         hsbvals = Color.RGBtoHSB(negativeColor.getRed(), negativeColor.getGreen(), negativeColor.getBlue(), hsbvals);
199         negativeHue = hsbvals[0];
200         negativeSaturation = 1f;
201     }
202 
203     /**
204      * @param hue
205      * @param saturation
206      */
207     public void adjustColor(final float hue, final float saturation) {
208         adjustColor(hue, saturation, hue, saturation);
209     }
210 
211     /**
212      * @param posHue
213      * @param posSaturation
214      * @param negHue
215      * @param negSaturation
216      */
217     public void adjustColor(final float posHue, final float posSaturation, final float negHue, final float negSaturation) {
218         this.positiveHue = posHue;
219         this.positiveSaturation = posSaturation;
220         this.negativeHue = negHue;
221         this.negativeSaturation = negSaturation;
222     }
223 
224     /*
225      * (non-Javadoc)
226      *
227      * @see net.sf.jtreemap.swing.ColorProvider#getColor(net.sf.jtreemap.swing.Value)
228      */
229     @Override
230     public Color getColor(final Value value) {
231         // Figure out the current range of colors, map that range into a scale
232         // from 0 to 1,
233         // using the specified distribution type
234         if (maxValue == null || minValue == null) {
235             setValues(jTreeMap.getRoot());
236         }
237         final double max = this.maxValue.getValue();
238         final double min = this.minValue.getValue();
239         double val = value != null ? value.getValue() : 0.00;
240 
241         if (val >= 0) {
242             // Value is greater than 0, use the positive colors
243             double range = max - Math.max(0, min);
244             val -= Math.max(0, min);
245             range = adjustValue(range);
246             return Color.getHSBColor(positiveHue, positiveSaturation, (float) (adjustValue(val) / range));
247         }
248 
249         // Value is less than 0, use the negative colors
250         double range = Math.abs(min - Math.min(0, max));
251         val += Math.min(0, max);
252         val = Math.abs(val);
253         // Value and range are not positive values, we need them to be for the
254         // math functions
255         range = adjustValue(range);
256         return Color.getHSBColor(negativeHue, negativeSaturation, (float) (adjustValue(val) / range));
257     }
258 
259     /**
260      * Given a value, maps that value to a new value using the specified math
261      * function
262      *
263      * @param value
264      *            the value to convert
265      * @return the converted value
266      */
267     private double adjustValue(final double value) {
268         double ret;
269         switch (colorDistribution) {
270         case LOG:
271             ret = Math.log1p(value);
272             break;
273         case EXP:
274             ret = Math.exp(value);
275             break;
276         case SQUARE_ROOT:
277             ret = Math.sqrt(value);
278             break;
279         case CUBIC_ROOT:
280             ret = Math.cbrt(value);
281             break;
282         default:
283             // Linear
284             ret = value;
285             break;
286         }
287         return ret;
288     }
289 
290     /**
291      * Set the max and the min values in the tree map
292      *
293      * @param root
294      *            root of the JTreeMap
295      */
296     private void setValues(final TreeMapNode root) {
297         if (root.isLeaf()) {
298             final Value value = root.getValue();
299 
300             if (value == null) {
301                 return;
302             }
303 
304             setMaxValue(value);
305             setMinValue(value);
306         } else {
307             for (final Enumeration e = root.children(); e.hasMoreElements();) {
308                 final TreeMapNode node = (TreeMapNode) e.nextElement();
309                 setValues(node);
310             }
311         }
312     }
313 
314     private void setMinValue(final Value value) {
315         if (minValue == null || value.getValue() <= minValue.getValue()) {
316             try {
317                 final Class c = value.getClass();
318                 if (minValue == null) {
319                     minValue = (Value) c.newInstance();
320                 }
321                 minValue.setValue(value.getValue());
322             } catch (final IllegalAccessException iae) {
323                 // ignore
324             } catch (final InstantiationException ie) {
325                 // Ignore
326                 log.error("Instantiation Issue", ie);
327             }
328         }
329     }
330 
331     private void setMaxValue(final Value value) {
332         if (maxValue == null || value.getValue() >= maxValue.getValue()) {
333             try {
334                 final Class c = value.getClass();
335                 if (maxValue == null) {
336                     maxValue = (Value) c.newInstance();
337                 }
338                 maxValue.setValue(value.getValue());
339             } catch (final IllegalAccessException iae) {
340                 // ignore
341             } catch (final InstantiationException ie) {
342                 // Ignore
343                 log.error("Instantiation Issue", ie);
344             }
345         }
346     }
347 
348     private final JTreeMap jTreeMap;
349     private JPanel legend;
350     private Value maxValue;
351     private Value minValue;
352     private float positiveHue;
353     private float negativeHue;
354     private float positiveSaturation = 1f;
355     private float negativeSaturation = 1f;
356     private ColorDistributionTypes colorDistribution = ColorDistributionTypes.LINEAR;
357 
358     /**
359      * Panel with the legend
360      *
361      * @author Laurent Dutheil
362      */
363     private class Legend extends JPanel {
364         private static final int Y_INSET = 7;
365         private static final int X_INSET = 15;
366         private static final long serialVersionUID = 6371342387871103592L;
367         private static final int HEIGHT = 20;
368         private static final int WIDTH = 150;
369         private static final int X = 20;
370         private static final int Y = 25;
371 
372         /**
373          * Constructor of Legend
374          */
375         public Legend() {
376             this.setSize(new java.awt.Dimension(2 * Legend.X + Legend.WIDTH, 2 * Legend.Y + Legend.HEIGHT));
377             this.setPreferredSize(new java.awt.Dimension(2 * Legend.X + Legend.WIDTH, 2 * Legend.Y + Legend.HEIGHT));
378         }
379 
380         @Override
381         public void paintComponent(final Graphics g) {
382             super.paintComponent(g);
383             if (HSBTreeMapColorProvider.this.minValue == null || HSBTreeMapColorProvider.this.maxValue == null) {
384                 setValues(HSBTreeMapColorProvider.this.jTreeMap.getRoot());
385             }
386             final Value min = HSBTreeMapColorProvider.this.minValue;
387             final Value max = HSBTreeMapColorProvider.this.maxValue;
388 
389             g.setColor(Color.black);
390             if (min != null && max != null) {
391                 g.drawString(min.getLabel(), Legend.X - X_INSET, Legend.Y - Y_INSET);
392                 g.drawString(max.getLabel(), Legend.X + Legend.WIDTH - X_INSET, Legend.Y - Y_INSET);
393 
394                 final double step = (max.getValue() - min.getValue()) / Legend.WIDTH;
395                 final Value value = new DefaultValue(min.getValue());
396                 for (int i = 0; i < Legend.WIDTH; i++) {
397                     g.setColor(HSBTreeMapColorProvider.this.getColor(value));
398                     g.fillRect(Legend.X + i, Legend.Y, 1, Legend.HEIGHT);
399                     value.setValue(value.getValue() + step);
400                 }
401             }
402         }
403     }
404 }
405 /*
406  *                 ObjectLab is supporing JTreeMap
407  *
408  * Based in London, we are world leaders in the design and development
409  * of bespoke applications for the securities financing markets.
410  *
411  * <a href="http://www.objectlab.co.uk/open">Click here to learn more about us</a>
412  *           ___  _     _           _   _          _
413  *          / _ \| |__ (_) ___  ___| |_| |    __ _| |__
414  *         | | | | '_ \| |/ _ \/ __| __| |   / _` | '_ \
415  *         | |_| | |_) | |  __/ (__| |_| |__| (_| | |_) |
416  *          \___/|_.__// |\___|\___|\__|_____\__,_|_.__/
417  *                   |__/
418  *
419  *                     www.ObjectLab.co.uk
420  */