1 package org.opentrafficsim.draw.core;
2
3 import java.awt.Color;
4 import java.io.Serializable;
5 import java.util.Arrays;
6
7 import org.djutils.exceptions.Throw;
8 import org.djutils.logger.CategoryLogger;
9 import org.opentrafficsim.core.animation.ColorInterpolator;
10
11
12
13
14
15
16
17
18
19
20
21 public class BoundsPaintScale implements ColorPaintScale, Serializable
22 {
23
24
25 public static final Color[] GREEN_RED = new Color[] {Color.GREEN, Color.YELLOW, Color.RED};
26
27
28 public static final Color[] GREEN_RED_DARK =
29 new Color[] {Color.GREEN.darker(), Color.GREEN, Color.YELLOW, Color.RED, Color.RED.darker()};
30
31
32 private static final long serialVersionUID = 20181008L;
33
34
35 private double[] bounds;
36
37
38 private Color[] boundColors;
39
40
41
42
43
44
45
46 public BoundsPaintScale(final double[] bounds, final Color[] boundColors) throws IllegalArgumentException
47 {
48 Throw.when(bounds.length < 2, IllegalArgumentException.class, "bounds must have >= 2 entries");
49 Throw.when(bounds.length != boundColors.length, IllegalArgumentException.class,
50 "bounds must have same length as boundColors");
51 this.bounds = new double[bounds.length];
52 this.boundColors = new Color[bounds.length];
53
54
55 for (int nextBound = 0; nextBound < bounds.length; nextBound++)
56 {
57
58 double currentLowest = Double.POSITIVE_INFINITY;
59 int bestIndex = -1;
60 int index;
61 for (index = 0; index < bounds.length; index++)
62 {
63 if (bounds[index] < currentLowest && (nextBound == 0 || bounds[index] > this.bounds[nextBound - 1]))
64 {
65 bestIndex = index;
66 currentLowest = bounds[index];
67 }
68 }
69 Throw.when(bestIndex < 0, IllegalArgumentException.class, "duplicate value in bounds");
70 this.bounds[nextBound] = bounds[bestIndex];
71 this.boundColors[nextBound] = boundColors[bestIndex];
72 }
73 }
74
75
76
77
78
79
80 public static Color[] reverse(final Color[] colors)
81 {
82 Color[] out = new Color[colors.length];
83 for (int i = 0; i < colors.length; i++)
84 {
85 out[colors.length - i - 1] = colors[i];
86 }
87 return out;
88 }
89
90
91
92
93
94
95 public static Color[] hue(final int n)
96 {
97 Color[] out = new Color[n];
98 for (int i = 0; i < n; i++)
99 {
100 out[i] = new Color(Color.HSBtoRGB(((float) i) / n, 1.0f, 1.0f));
101 }
102 return out;
103 }
104
105
106 @Override
107 public Color getPaint(final double value)
108 {
109 if (Double.isNaN(value))
110 {
111 return Color.BLACK;
112 }
113 if (value < this.bounds[0])
114 {
115 return this.boundColors[0];
116 }
117 if (value > this.bounds[this.bounds.length - 1])
118 {
119 return this.boundColors[this.bounds.length - 1];
120 }
121 int index;
122 for (index = 0; index < this.bounds.length - 1; index++)
123 {
124 if (value < this.bounds[index + 1])
125 {
126 break;
127 }
128 }
129 final double ratio;
130 if (index >= this.bounds.length - 1)
131 {
132 index = this.bounds.length - 2;
133 ratio = 1.0;
134 }
135 else
136 {
137 ratio = (value - this.bounds[index]) / (this.bounds[index + 1] - this.bounds[index]);
138 }
139 if (Double.isInfinite(ratio))
140 {
141 CategoryLogger.always().error("Interpolation value for color is infinite based on {} in {} obtaining index {}.",
142 value, this.bounds, index);
143 }
144 Color mix = ColorInterpolator.interpolateColor(this.boundColors[index], this.boundColors[index + 1], ratio);
145 return mix;
146 }
147
148
149 @Override
150 public final double getLowerBound()
151 {
152 return this.bounds[0];
153 }
154
155
156 @Override
157 public final double getUpperBound()
158 {
159 return this.bounds[this.bounds.length - 1];
160 }
161
162
163 @Override
164 public String toString()
165 {
166 return "BoundsPaintScale [bounds=" + Arrays.toString(this.bounds) + ", boundColors=" + Arrays.toString(this.boundColors)
167 + "]";
168 }
169
170 }