001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.tools; 003 004import java.awt.Color; 005import java.awt.FontMetrics; 006import java.awt.Graphics2D; 007 008/** 009 * Utility class that helps to work with color scale for coloring GPX tracks etc. 010 * @since 7319 011 */ 012public final class ColorScale { 013 private double min, max; 014 private Color noDataColor; 015 private Color belowMinColor; 016 private Color aboveMaxColor; 017 018 private Color[] colors; 019 private String title = ""; 020 private int intervalCount = 5; 021 022 private ColorScale() { 023 024 } 025 026 public static ColorScale createHSBScale(int count) { 027 ColorScale sc = new ColorScale(); 028 sc.colors = new Color[count]; 029 for (int i = 0; i < count; i++) { 030 sc.colors[i] = Color.getHSBColor(i / 300.0f, 1, 1); 031 } 032 sc.setRange(0, 255); 033 sc.addBounds(); 034 return sc; 035 } 036 037 public static ColorScale createCyclicScale(int count) { 038 ColorScale sc = new ColorScale(); 039 // red yellow green blue red 040 int[] h = new int[] {0, 59, 127, 244, 360}; 041 int[] s = new int[] {100, 84, 99, 100}; 042 int[] b = new int[] {90, 93, 74, 83}; 043 044 sc.colors = new Color[count]; 045 for (int i = 0; i < sc.colors.length; i++) { 046 047 float angle = i / 256f * 4; 048 int quadrant = (int) angle; 049 angle -= quadrant; 050 quadrant = Utils.mod(quadrant+1, 4); 051 052 float vh = h[quadrant] * w(angle) + h[quadrant+1] * (1 - w(angle)); 053 float vs = s[quadrant] * w(angle) + s[Utils.mod(quadrant+1, 4)] * (1 - w(angle)); 054 float vb = b[quadrant] * w(angle) + b[Utils.mod(quadrant+1, 4)] * (1 - w(angle)); 055 056 sc.colors[i] = Color.getHSBColor(vh/360f, vs/100f, vb/100f); 057 } 058 sc.setRange(0, 2*Math.PI); 059 sc.addBounds(); 060 return sc; 061 } 062 063 /** 064 * transition function: 065 * w(0)=1, w(1)=0, 0<=w(x)<=1 066 * @param x number: 0<=x<=1 067 * @return the weighted value 068 */ 069 private static float w(float x) { 070 if (x < 0.5) 071 return 1 - 2*x*x; 072 else 073 return 2*(1-x)*(1-x); 074 } 075 076 public void setRange(double min, double max) { 077 this.min = min; 078 this.max = max; 079 } 080 081 /** 082 * Add standard colors for values below min or above max value 083 */ 084 public void addBounds() { 085 aboveMaxColor = colors[colors.length-1]; 086 belowMinColor = colors[0]; 087 } 088 089 public Color getColor(double value) { 090 if (value < min) return belowMinColor; 091 if (value > max) return aboveMaxColor; 092 if (Double.isNaN(value)) return noDataColor; 093 final int n = colors.length; 094 int idx = (int) ((value-min)*colors.length / (max-min)); 095 if (idx < colors.length) { 096 return colors[idx]; 097 } else { 098 return colors[n-1]; // this happens when value==max 099 } 100 } 101 102 public Color getColor(Number value) { 103 return (value == null) ? noDataColor : getColor(value.doubleValue()); 104 } 105 106 public Color getNoDataColor() { 107 return noDataColor; 108 } 109 110 public void setNoDataColor(Color noDataColor) { 111 this.noDataColor = noDataColor; 112 } 113 114 public ColorScale makeTransparent(int alpha) { 115 for (int i = 0; i < colors.length; i++) { 116 colors[i] = new Color((colors[i].getRGB() & 0xFFFFFF) | ((alpha & 0xFF) << 24), true); 117 } 118 return this; 119 } 120 121 public ColorScale addTitle(String title) { 122 this.title = title; 123 return this; 124 } 125 126 public ColorScale setIntervalCount(int intervalCount) { 127 this.intervalCount = intervalCount; 128 return this; 129 } 130 131 public ColorScale makeReversed() { 132 int n = colors.length; 133 Color tmp; 134 for (int i = 0; i < n/2; i++) { 135 tmp = colors[i]; 136 colors[i] = colors[n-1-i]; 137 colors[n-1-i] = tmp; 138 } 139 tmp = belowMinColor; 140 belowMinColor = aboveMaxColor; 141 aboveMaxColor = tmp; 142 return this; 143 } 144 145 public void drawColorBar(Graphics2D g, int x, int y, int w, int h, double valueScale) { 146 int n = colors.length; 147 148 for (int i = 0; i < n; i++) { 149 g.setColor(colors[i]); 150 if (w < h) { 151 g.fillRect(x, y+i*h/n, w, h/n+1); 152 } else { 153 g.fillRect(x+i*w/n, y, w/n+1, h); 154 } 155 } 156 157 int fw, fh; 158 FontMetrics fm = g.getFontMetrics(); 159 fh = fm.getHeight()/2; 160 fw = fm.stringWidth(String.valueOf(Math.max((int) Math.abs(max*valueScale), 161 (int) Math.abs(min*valueScale)))) + fm.stringWidth("0.123"); 162 g.setColor(noDataColor); 163 if (title != null) { 164 g.drawString(title, x-fw-3, y-fh*3/2); 165 } 166 for (int i = 0; i <= intervalCount; i++) { 167 g.setColor(colors[(int) (1.0*i*n/intervalCount-1e-10)]); 168 final double val = min+i*(max-min)/intervalCount; 169 final String txt = String.format("%.3f", val*valueScale); 170 if (w < h) { 171 g.drawString(txt, x-fw-3, y+i*h/intervalCount+fh/2); 172 } else { 173 g.drawString(txt, x+i*w/intervalCount-fw/2, y+fh-3); 174 } 175 } 176 } 177}