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<Float, Color> colorMap = new OrderedMap<>(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), & Red (200)"); 034 Table table = center.addTable(); 035 Tr row = table.addRow(); 036 037 for (int i = 20; i <=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), & 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 <= float value >= 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}