001package com.hfg.graphics;
002
003import java.awt.Color;
004import java.util.ArrayList;
005import java.util.List;
006import java.util.Map;
007
008import com.hfg.html.attribute.HTMLColor;
009import com.hfg.util.CompareUtil;
010import com.hfg.util.collection.CollectionUtil;
011import com.hfg.util.collection.OrderedMap;
012import com.hfg.util.collection.OrderedSet;
013import com.hfg.xml.XMLName;
014import com.hfg.xml.XMLTag;
015
016//------------------------------------------------------------------------------
017/**
018 <div>
019 Non-linear Color scale created from three or more colors.
020 </div>
021 <div>
022 Ex:
023 <pre>
024
025 OrderedMap&lt;Float, Color&gt; colorMap = new OrderedMap&lt;&gt;(3);
026 colorMap.put(20f,Color.GREEN);
027 colorMap.put(80f,Color.YELLOW);
028 colorMap.put(200f,Color.RED);
029
030 NonlinearColorScale colorScale =new NonlinearColorScale(colorMap);
031
032 center.br(4);
033 center.addSpan("Non-linear Color Scale Created from Green (20), Yellow (80), &amp; Red (200)");
034 Table table = center.addTable();
035 Tr row = table.addRow();
036
037 for (int i = 20; i &lt;=200; i+=20)
038 {
039   HTMLColor scaleColor = new HTMLColor(colorScale.assignColorForValue(i));
040   row.addCell(String.format("%d", i))
041       .setStyle(CSS.color(scaleColor.getContrastingColor()) + CSS.bgColor(scaleColor));
042 }
043 </pre>
044 </div>
045 <div style='margin-top:20px'>
046 <span>Non-linear Color Scale Created from Green (20), Yellow (80), &amp; Red (200)</span>
047 <table>
048 <tr>
049 <td style='color:#000000;background-color:#00ff00;'>20</td>
050 <td style='color:#000000;background-color:#55ff00;'>40</td>
051 <td style='color:#000000;background-color:#aaff00;'>60</td>
052 <td style='color:#000000;background-color:#ffff00;'>80</td>
053 <td style='color:#000000;background-color:#ffd500;'>100</td>
054 <td style='color:#000000;background-color:#ffaa00;'>120</td>
055 <td style='color:#000000;background-color:#ff8000;'>140</td>
056 <td style='color:#ffffff;background-color:#ff5500;'>160</td>
057 <td style='color:#ffffff;background-color:#ff2b00;'>180</td>
058 <td style='color:#ffffff;background-color:#ff0000;'>200</td>
059 </tr>
060 </table>
061 </div>
062
063 * @author J. Alex Taylor, hairyfatguy.com
064 */
065//------------------------------------------------------------------------------
066// com.hfg Library
067//
068// This library is free software; you can redistribute it and/or
069// modify it under the terms of the GNU Lesser General Public
070// License as published by the Free Software Foundation; either
071// version 2.1 of the License, or (at your option) any later version.
072//
073// This library is distributed in the hope that it will be useful,
074// but WITHOUT ANY WARRANTY; without even the implied warranty of
075// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
076// Lesser General Public License for more details.
077//
078// You should have received a copy of the GNU Lesser General Public
079// License along with this library; if not, write to the Free Software
080// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
081//
082// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com
083// jataylor@hairyfatguy.com
084//------------------------------------------------------------------------------
085
086public class NonlinearColorScale implements ColorRule, Comparable
087{
088   private OrderedMap<Float, Color> mMap;
089   private Float mMinValue;
090   private Float mMaxValue;
091
092   public static final  XMLName XML_NONLINEAR_COLOR_SCALE = new XMLName("NonlinearColorScale");
093   private static final XMLName XML_COLOR       = new XMLName("Color");
094   private static final XMLName XML_VALUE_ATT   = new XMLName("value");
095
096   //##########################################################################
097   // CONSTRUCTORS
098   //##########################################################################
099
100   //--------------------------------------------------------------------------
101   public NonlinearColorScale(OrderedMap<Float, Color> inMap)
102   {
103      if (null == inMap
104            || inMap.size() < 2)
105      {
106         throw new RuntimeException("At least 2 colors must be specified!");
107      }
108
109      mMap = inMap;
110      mMinValue = mMap.keySet().first();
111      mMaxValue = mMap.keySet().last();
112   }
113
114   //---------------------------------------------------------------------------
115   public NonlinearColorScale(XMLTag inXMLTag)
116   {
117      inXMLTag.verifyTagName(XML_NONLINEAR_COLOR_SCALE);
118
119      List<XMLTag> subTags = inXMLTag.getSubtagsByName(XML_COLOR);
120      if (CollectionUtil.hasValues(subTags))
121      {
122         mMap = new OrderedMap<>(subTags.size());
123
124         for (XMLTag subTag : subTags)
125         {
126            mMap.put(Float.parseFloat(subTag.getAttributeValue(XML_VALUE_ATT)) , HTMLColor.valueOf(subTag.getUnescapedContent()));
127         }
128
129         mMinValue = mMap.keySet().first();
130         mMaxValue = mMap.keySet().last();
131      }
132   }
133
134   //##########################################################################
135   // PUBLIC METHODS
136   //##########################################################################
137
138   //--------------------------------------------------------------------------
139   public List<Float> getThresholds()
140   {
141      return new ArrayList<>(mMap.keySet());
142   }
143
144   //--------------------------------------------------------------------------
145   public List<Color> getColors()
146   {
147      return new ArrayList<>(mMap.values());
148   }
149
150   //--------------------------------------------------------------------------
151   public int size()
152   {
153      return mMap.size();
154   }
155
156   //--------------------------------------------------------------------------
157   /**
158    * Determines the color that should be assigned to the specified value that
159    * has been normalized to the range of 0 to 1.
160    * @param inValue 0 &lt;= float value &gt;= 1.0
161    * @return the assigned color
162    */
163   @Override
164   public Color assignColorForValue(float inValue)
165   {
166      float value = inValue;
167      if (value < mMinValue)
168      {
169         value = mMinValue;
170      }
171
172      if (value > mMaxValue)
173      {
174         value = mMaxValue;
175      }
176
177      // Determine which two colors the value falls between.
178
179      int leftIndex = 0;
180      OrderedSet<Float> keySet = mMap.keySet();
181
182      for (int i = 1; i < keySet.size(); i++)
183      {
184         Float key = keySet.get(i);
185         if (value <= key)
186         {
187            leftIndex = i - 1;
188            break;
189         }
190      }
191
192      Float rangeSize = keySet.get(leftIndex + 1) - keySet.get(leftIndex);
193      Float proportion = (value - keySet.get(leftIndex)) / rangeSize;
194
195      return ColorUtil.blend(mMap.get(keySet.get(leftIndex)), mMap.get(keySet.get(leftIndex + 1)), 1 - proportion);
196   }
197
198   //---------------------------------------------------------------------------
199   public XMLTag toXMLTag()
200   {
201      XMLTag tag = new XMLTag(XML_NONLINEAR_COLOR_SCALE);
202
203      for(Float key : mMap.keySet())
204      {
205         tag.addSubtag(new XMLTag(XML_COLOR)
206                             .setAttribute(XML_VALUE_ATT, key)
207                             .setContent(new HTMLColor(mMap.get(key)).toString()));
208      }
209
210      return tag;
211   }
212
213
214   //--------------------------------------------------------------------------
215   @Override
216   public int compareTo(Object inObj2)
217   {
218      int result = -1;
219
220      if (inObj2 != null
221            && inObj2 instanceof NonlinearColorScale)
222      {
223         NonlinearColorScale colorScale2 = (NonlinearColorScale) inObj2;
224
225         result = CompareUtil.compare((Map) mMap, (Map) colorScale2.mMap);
226      }
227
228      return result;
229   }
230}