001package com.hfg.graphics;
002
003import com.hfg.html.HTMLTag;
004import com.hfg.html.Span;
005import com.hfg.html.attribute.HTMLColor;
006import com.hfg.math.Range;
007import com.hfg.util.CompareUtil;
008import com.hfg.util.StringUtil;
009import com.hfg.util.Value;
010import com.hfg.util.ValueImpl;
011import com.hfg.xml.XMLName;
012import com.hfg.xml.XMLTag;
013
014
015//------------------------------------------------------------------------------
016
017/**
018 Colorist implementation that uses ranges.
019 <div>
020 @author J. Alex Taylor, hairyfatguy.com
021 </div>
022 */
023//------------------------------------------------------------------------------
024// com.hfg Library
025//
026// This library is free software; you can redistribute it and/or
027// modify it under the terms of the GNU Lesser General Public
028// License as published by the Free Software Foundation; either
029// version 2.1 of the License, or (at your option) any later version.
030//
031// This library is distributed in the hope that it will be useful,
032// but WITHOUT ANY WARRANTY; without even the implied warranty of
033// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
034// Lesser General Public License for more details.
035//
036// You should have received a copy of the GNU Lesser General Public
037// License along with this library; if not, write to the Free Software
038// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
039//
040// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com
041// jataylor@hairyfatguy.com
042//------------------------------------------------------------------------------
043public class ScaleColorist<T extends Number> implements Colorist
044{
045   private ColorScale mColorScale;
046   private Range<T> mRange;
047
048   private static final XMLName XML_RANGE      = new XMLName("Range");
049   private static final XMLName XML_TYPE_ATT   = new XMLName("type");
050   private static final XMLName XML_START_ATT  = new XMLName("start");
051   private static final XMLName XML_END_ATT    = new XMLName("end");
052
053   //###########################################################################
054   // CONSTRUCTORS
055   //###########################################################################
056
057   //---------------------------------------------------------------------------
058   public ScaleColorist(ColorScale inColorScale, Range<T> inRange)
059   {
060      mColorScale = inColorScale;
061      mRange = inRange;
062   }
063
064
065   //---------------------------------------------------------------------------
066   public ScaleColorist(XMLTag inXMLTag)
067   {
068      inXMLTag.verifyTagName(XML_COLORIST);
069
070      XMLTag colorScaleTag = inXMLTag.getRequiredSubtagByName(ColorScale.XML_COLOR_SCALE);
071      mColorScale = new ColorScale(colorScaleTag);
072
073      XMLTag rangeTag = inXMLTag.getRequiredSubtagByName(XML_RANGE);
074      mRange = extractRange(rangeTag);
075   }
076
077   //---------------------------------------------------------------------------
078   private Range extractRange(XMLTag inRangeTag)
079   {
080      Range range = null;
081
082      String type = inRangeTag.getAttributeValue(XML_TYPE_ATT);
083
084      if (type.equals(Integer.class.getSimpleName()))
085      {
086         range = new Range<Integer>();
087
088         String start = inRangeTag.getAttributeValue(XML_START_ATT);
089         if (StringUtil.isSet(start))
090         {
091            range.setStart(Integer.parseInt(start));
092         }
093
094         String end = inRangeTag.getAttributeValue(XML_END_ATT);
095         if (StringUtil.isSet(end))
096         {
097            range.setEnd(Integer.parseInt(end));
098         }
099      }
100      else if (type.equals(Long.class.getSimpleName()))
101      {
102         range = new Range<Long>();
103
104         String start = inRangeTag.getAttributeValue(XML_START_ATT);
105         if (StringUtil.isSet(start))
106         {
107            range.setStart(Long.parseLong(start));
108         }
109
110         String end = inRangeTag.getAttributeValue(XML_END_ATT);
111         if (StringUtil.isSet(end))
112         {
113            range.setEnd(Long.parseLong(end));
114         }
115      }
116      else if (type.equals(Float.class.getSimpleName()))
117      {
118         range = new Range<Float>();
119
120         String start = inRangeTag.getAttributeValue(XML_START_ATT);
121         if (StringUtil.isSet(start))
122         {
123            range.setStart(Float.parseFloat(start));
124         }
125
126         String end = inRangeTag.getAttributeValue(XML_END_ATT);
127         if (StringUtil.isSet(end))
128         {
129            range.setEnd(Float.parseFloat(end));
130         }
131      }
132      else // Use Double as the catch-all
133      {
134         range = new Range<Double>();
135
136         String start = inRangeTag.getAttributeValue(XML_START_ATT);
137         if (StringUtil.isSet(start))
138         {
139            range.setStart(Double.parseDouble(start));
140         }
141
142         String end = inRangeTag.getAttributeValue(XML_END_ATT);
143         if (StringUtil.isSet(end))
144         {
145            range.setEnd(Double.parseDouble(end));
146         }
147      }
148
149      return range;
150   }
151
152   //###########################################################################
153   // PUBLIC METHODS
154   //###########################################################################
155
156   //---------------------------------------------------------------------------
157   public ColorScale getColorScale()
158   {
159      return mColorScale;
160   }
161
162   //---------------------------------------------------------------------------
163   public Range<T> getRange()
164   {
165      return mRange;
166   }
167
168
169   //---------------------------------------------------------------------------
170   public XMLTag toXMLTag()
171   {
172      XMLTag tag = new XMLTag(XML_COLORIST);
173      tag.setAttribute(XML_CLASS_ATT, getClass().getName());
174
175      tag.addSubtag(mColorScale.toXMLTag());
176
177      XMLTag rangeTag = new XMLTag(XML_RANGE);
178      tag.addSubtag(rangeTag);
179
180      Class typeClass = null;
181
182      if (getRange().getStart() != null)
183      {
184         rangeTag.setAttribute(XML_START_ATT, getRange().getStart());
185         typeClass = getRange().getStart().getClass();
186      }
187
188      if (getRange().getEnd() != null)
189      {
190         rangeTag.setAttribute(XML_END_ATT, getRange().getEnd());
191         typeClass = getRange().getEnd().getClass();
192      }
193
194      if (typeClass != null)
195      {
196         rangeTag.setAttribute(XML_TYPE_ATT, typeClass.getSimpleName());
197      }
198
199      return tag;
200   }
201
202   //---------------------------------------------------------------------------
203   @Override
204   public HTMLTag toDisplayHTML()
205   {
206      // background-image: linear-gradient(315deg, #ff000066,#ffa50066,#ffff0066,#00800066,#0000ff66,#4b008266,#ee82ee66)
207      Span span = new Span()
208            .addStyle("background-image: linear-gradient(90deg, " + StringUtil.join(getColorScale().getColors(), ",") + ");");
209
210      span.addContent(getRange().getStart() + "");
211      span.addSpan(getRange().getEnd() + "").addStyle("margin-left:200px;");
212
213      return span;
214   }
215
216   //---------------------------------------------------------------------------
217   @Override
218   public int hashCode()
219   {
220      int hashcode = 1;
221
222      if (mRange != null)
223      {
224         hashcode += 31 * mRange.hashCode();
225      }
226
227      if (mColorScale != null)
228      {
229         hashcode += 31 * mColorScale.hashCode();
230      }
231
232      return hashcode;
233   }
234
235   //---------------------------------------------------------------------------
236   @Override
237   public boolean equals(Object inObj2)
238   {
239      return (inObj2 != null
240            && inObj2 instanceof ScaleColorist
241            && 0 == compareTo((ScaleColorist) inObj2));
242   }
243
244   //---------------------------------------------------------------------------
245   @Override
246   public int compareTo(Colorist inObj2)
247   {
248      int result = -1;
249
250      if (this == inObj2)
251      {
252         result = 0;
253      }
254      else if (inObj2 instanceof ScaleColorist)
255      {
256         if (inObj2 != null)
257         {
258            result = CompareUtil.compare(mRange, ((ScaleColorist)inObj2).mRange);
259
260            if(0 == result)
261            {
262               result = CompareUtil.compare(mColorScale, ((ScaleColorist)inObj2).mColorScale);
263            }
264         }
265      }
266
267      return result;
268   }
269
270   //---------------------------------------------------------------------------
271   @Override
272   public ColorSpec colorByNumber(Value inValue)
273   {
274      HTMLColor bgColor = new HTMLColor(mColorScale.assignColorForValue(normalizeValue(inValue).floatValue()));
275
276      HTMLColor fgColor = bgColor.getContrastingColor();
277
278      return new ColorSpec().setBackgroundColor(bgColor)
279                            .setForegroundColor(fgColor);
280   }
281
282   //---------------------------------------------------------------------------
283   @Override
284   public ColorSpec colorByNumber(Number inValue)
285   {
286      return colorByNumber(new ValueImpl(inValue));
287   }
288
289
290   //###########################################################################
291   // PRIVATE METHODS
292   //###########################################################################
293
294   //---------------------------------------------------------------------------
295   private Value normalizeValue(Value inValue)
296   {
297      Value normalizedValue;
298
299      if(inValue.doubleValue() != null)
300      {
301         if(! mRange.contains(inValue.doubleValue()))
302         {
303            throw new RuntimeException("Specified value: " + inValue + " is not within expected range: " + mRange + "!");
304         }
305
306         normalizedValue = new ValueImpl((inValue.doubleValue() - mRange.getStart().doubleValue()) / mRange.length());
307      }
308      else
309      {
310         //default to bottom of range if inValue is not a number or is null
311         normalizedValue = new ValueImpl(0);
312      }
313
314      return normalizedValue;
315   }
316
317}