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