Mercurial > hg > svcore
comparison base/ScaleTickIntervals.h @ 1418:e7cb4fb2aee4 scale-ticks
Rework log scale calculation, update to changed interface
author | Chris Cannam |
---|---|
date | Thu, 04 May 2017 15:37:43 +0100 |
parents | 359147a50853 |
children | e7e626a87a1e |
comparison
equal
deleted
inserted
replaced
1417:359147a50853 | 1418:e7cb4fb2aee4 |
---|---|
41 double value; // value this tick represents | 41 double value; // value this tick represents |
42 std::string label; // value as written | 42 std::string label; // value as written |
43 }; | 43 }; |
44 | 44 |
45 typedef std::vector<Tick> Ticks; | 45 typedef std::vector<Tick> Ticks; |
46 | 46 |
47 /** | |
48 * Return a set of ticks that divide the range r linearly into | |
49 * roughly r.n equal divisions, in such a way as to yield | |
50 * reasonably human-readable labels. | |
51 */ | |
47 static Ticks linear(Range r) { | 52 static Ticks linear(Range r) { |
48 return linearTicks(r); | 53 return linearTicks(r); |
49 } | 54 } |
50 | 55 |
56 /** | |
57 * Return a set of ticks that divide the range r into roughly r.n | |
58 * logarithmic divisions, in such a way as to yield reasonably | |
59 * human-readable labels. | |
60 */ | |
51 static Ticks logarithmic(Range r) { | 61 static Ticks logarithmic(Range r) { |
62 LogRange::mapRange(r.min, r.max); | |
63 return logarithmicAlready(r); | |
64 } | |
65 | |
66 /** | |
67 * Return a set of ticks that divide the range r into roughly r.n | |
68 * logarithmic divisions, on the asssumption that r.min and r.max | |
69 * already represent the logarithms of the boundary values rather | |
70 * than the values themselves. | |
71 */ | |
72 static Ticks logarithmicAlready(Range r) { | |
52 return logTicks(r); | 73 return logTicks(r); |
53 } | 74 } |
54 | 75 |
55 private: | 76 private: |
77 enum Display { | |
78 Fixed, | |
79 Scientific, | |
80 Auto | |
81 }; | |
82 | |
56 struct Instruction { | 83 struct Instruction { |
57 double initial; // value of first tick | 84 double initial; // value of first tick |
58 double limit; // max from original range | 85 double limit; // max from original range |
59 double spacing; // increment between ticks | 86 double spacing; // increment between ticks |
60 double roundTo; // what all displayed values should be rounded to | 87 double roundTo; // what all displayed values should be rounded to |
61 bool fixed; // whether to use fixed precision (%f rather than %e) | 88 Display display; // whether to use fixed precision (%e, %f, or %g) |
62 int precision; // number of dp (%f) or sf (%e) | 89 int precision; // number of dp (%f) or sf (%e) |
63 bool logUnmap; // true if values represent logs of display values | 90 bool logUnmap; // true if values represent logs of display values |
64 }; | 91 }; |
65 | 92 |
66 static Instruction linearInstruction(Range r) | 93 static Instruction linearInstruction(Range r) |
67 { | 94 { |
95 Display display = Auto; | |
96 | |
97 if (r.max < r.min) { | |
98 return linearInstruction({ r.max, r.min, r.n }); | |
99 } | |
100 if (r.n < 1 || r.max == r.min) { | |
101 return { r.min, r.min, 1.0, r.min, display, 1, false }; | |
102 } | |
103 | |
104 double inc = (r.max - r.min) / r.n; | |
105 | |
106 double digInc = log10(inc); | |
107 double digMax = log10(fabs(r.max)); | |
108 double digMin = log10(fabs(r.min)); | |
109 | |
110 int precInc = int(floor(digInc)); | |
111 double roundTo = pow(10.0, precInc); | |
112 | |
113 if (precInc > -4 && precInc < 4) { | |
114 display = Fixed; | |
115 } else if ((digMax >= -2.0 && digMax <= 3.0) && | |
116 (digMin >= -3.0 && digMin <= 3.0)) { | |
117 display = Fixed; | |
118 } else { | |
119 display = Scientific; | |
120 } | |
121 | |
122 int precRange = int(ceil(digMax - digInc)); | |
123 | |
124 int prec = 1; | |
125 | |
126 if (display == Fixed) { | |
127 if (digInc < 0) { | |
128 prec = -precInc; | |
129 } else { | |
130 prec = 0; | |
131 } | |
132 } else { | |
133 prec = precRange; | |
134 } | |
135 | |
136 #ifdef DEBUG_SCALE_TICK_INTERVALS | |
137 std::cerr << "\nmin = " << r.min << ", max = " << r.max << ", n = " << r.n | |
138 << ", inc = " << inc << std::endl; | |
139 std::cerr << "digMax = " << digMax << ", digInc = " << digInc | |
140 << std::endl; | |
141 std::cerr << "display = " << display << ", inc = " << inc | |
142 << ", precInc = " << precInc << ", precRange = " << precRange | |
143 << ", prec = " << prec << std::endl; | |
144 std::cerr << "roundTo = " << roundTo << std::endl; | |
145 #endif | |
146 | |
147 double min = r.min; | |
148 | |
149 if (roundTo != 0.0) { | |
150 inc = round(inc / roundTo) * roundTo; | |
151 if (inc < roundTo) inc = roundTo; | |
152 min = ceil(min / roundTo) * roundTo; | |
153 if (min > r.max) min = r.max; | |
154 } | |
155 | |
156 if (display == Scientific && min != 0.0) { | |
157 double digNewMin = log10(fabs(min)); | |
158 if (digNewMin < digInc) { | |
159 prec = int(ceil(digMax - digNewMin)); | |
160 #ifdef DEBUG_SCALE_TICK_INTERVALS | |
161 std::cerr << "min is smaller than increment, adjusting prec to " | |
162 << prec << std::endl; | |
163 #endif | |
164 } | |
165 } | |
166 | |
167 return { min, r.max, inc, roundTo, display, prec, false }; | |
168 } | |
169 | |
170 static Instruction logInstruction(Range r) | |
171 { | |
172 Display display = Auto; | |
173 | |
68 if (r.n < 1) { | 174 if (r.n < 1) { |
69 return {}; | 175 return {}; |
70 } | 176 } |
71 if (r.max < r.min) { | 177 if (r.max < r.min) { |
72 return linearInstruction({ r.max, r.min, r.n }); | 178 return logInstruction({ r.max, r.min, r.n }); |
73 } | 179 } |
180 if (r.max == r.min) { | |
181 return { r.min, r.max, 1.0, r.min, display, 1, true }; | |
182 } | |
74 | 183 |
75 double inc = (r.max - r.min) / r.n; | 184 double inc = (r.max - r.min) / r.n; |
76 if (inc == 0) { | |
77 #ifdef DEBUG_SCALE_TICK_INTERVALS | |
78 std::cerr << "inc == 0, using trivial range" << std::endl; | |
79 #endif | |
80 double roundTo = r.min; | |
81 if (roundTo <= 0.0) { | |
82 roundTo = 1.0; | |
83 } | |
84 return { r.min, r.max, 1.0, roundTo, true, 1, false }; | |
85 } | |
86 | 185 |
87 double digInc = log10(inc); | 186 double digInc = log10(inc); |
88 double digMax = log10(fabs(r.max)); | 187 int precInc = int(floor(digInc)); |
89 double digMin = log10(fabs(r.min)); | 188 double roundTo = pow(10.0, precInc); |
90 | 189 |
91 int precInc = int(trunc(digInc)); | 190 if (roundTo != 0.0) { |
92 if (double(precInc) != digInc) { | 191 inc = round(inc / roundTo) * roundTo; |
93 precInc -= 1; | 192 if (inc < roundTo) inc = roundTo; |
94 } | 193 } |
95 | 194 |
96 bool fixed = false; | 195 // if inc is close to giving us powers of two, nudge it |
97 if (precInc > -4 && precInc < 4) { | 196 if (fabs(inc - 0.301) < 0.01) { |
98 fixed = true; | 197 inc = log10(2.0); |
99 } else if ((digMax >= -3.0 && digMax <= 2.0) && | 198 } |
100 (digMin >= -3.0 && digMin <= 3.0)) { | 199 |
101 fixed = true; | 200 // smallest increment as displayed |
102 } | 201 double minDispInc = |
103 | 202 LogRange::unmap(r.min + inc) - LogRange::unmap(r.min); |
104 int precRange = int(ceil(digMax - digInc)); | |
105 | 203 |
106 int prec = 1; | 204 int prec = 1; |
107 | 205 |
108 if (fixed) { | 206 if (minDispInc > 0.0) { |
109 if (digInc < 0) { | 207 prec = int(floor(log10(minDispInc))); |
110 prec = -precInc; | 208 if (prec < 0) prec = -prec; |
111 } else { | 209 } |
112 prec = 0; | 210 |
113 } | 211 if (r.max >= -2.0 && r.max <= 3.0 && |
114 } else { | 212 r.min >= -3.0 && r.min <= 3.0) { |
115 prec = precRange; | 213 display = Fixed; |
116 } | 214 if (prec == 0) prec = 1; |
117 | 215 } |
118 double roundTo = pow(10.0, precInc); | 216 |
119 | |
120 #ifdef DEBUG_SCALE_TICK_INTERVALS | 217 #ifdef DEBUG_SCALE_TICK_INTERVALS |
121 std::cerr << "\nmin = " << r.min << ", max = " << r.max << ", n = " << r.n | 218 std::cerr << "\nmin = " << r.min << ", max = " << r.max << ", n = " << r.n |
122 << ", inc = " << inc << std::endl; | 219 << ", inc = " << inc << ", minDispInc = " << minDispInc |
123 std::cerr << "digMax = " << digMax << ", digInc = " << digInc | 220 << ", digInc = " << digInc << std::endl; |
124 << std::endl; | 221 std::cerr << "display = " << display << ", inc = " << inc |
125 std::cerr << "fixed = " << fixed << ", inc = " << inc | 222 << ", precInc = " << precInc |
126 << ", precInc = " << precInc << ", precRange = " << precRange | |
127 << ", prec = " << prec << std::endl; | 223 << ", prec = " << prec << std::endl; |
128 std::cerr << "roundTo = " << roundTo << std::endl; | 224 std::cerr << "roundTo = " << roundTo << std::endl; |
129 #endif | 225 #endif |
130 | 226 |
131 inc = round(inc / roundTo) * roundTo; | 227 double min = r.min; |
132 if (inc < roundTo) inc = roundTo; | 228 if (inc != 0.0) { |
133 | 229 min = ceil(r.min / inc) * inc; |
134 double min = ceil(r.min / roundTo) * roundTo; | 230 if (min > r.max) min = r.max; |
135 if (min > r.max) min = r.max; | 231 } |
136 | 232 |
137 if (!fixed && min != 0.0) { | 233 return { min, r.max, inc, 0.0, display, prec, true }; |
138 double digNewMin = log10(fabs(min)); | |
139 if (digNewMin < digInc) { | |
140 prec = int(ceil(digMax - digNewMin)); | |
141 #ifdef DEBUG_SCALE_TICK_INTERVALS | |
142 std::cerr << "min is smaller than increment, adjusting prec to " | |
143 << prec << std::endl; | |
144 #endif | |
145 } | |
146 } | |
147 | |
148 return { min, r.max, inc, roundTo, fixed, prec, false }; | |
149 } | 234 } |
150 | 235 |
151 static Ticks linearTicks(Range r) { | 236 static Ticks linearTicks(Range r) { |
152 Instruction instruction = linearInstruction(r); | 237 Instruction instruction = linearInstruction(r); |
153 Ticks ticks = explode(instruction); | 238 Ticks ticks = explode(instruction); |
154 return ticks; | 239 return ticks; |
155 } | 240 } |
156 | 241 |
157 static Ticks logTicks(Range r) { | 242 static Ticks logTicks(Range r) { |
158 Range mapped(r); | 243 Instruction instruction = logInstruction(r); |
159 LogRange::mapRange(mapped.min, mapped.max); | |
160 Instruction instruction = linearInstruction(mapped); | |
161 instruction.logUnmap = true; | |
162 if (fabs(mapped.min - mapped.max) > 3) { | |
163 instruction.fixed = false; | |
164 } | |
165 Ticks ticks = explode(instruction); | 244 Ticks ticks = explode(instruction); |
166 return ticks; | 245 return ticks; |
167 } | 246 } |
168 | 247 |
169 static Tick makeTick(bool fixed, int precision, double value) { | 248 static Tick makeTick(Display display, int precision, double value) { |
170 const int buflen = 40; | 249 const int buflen = 40; |
171 char buffer[buflen]; | 250 char buffer[buflen]; |
172 snprintf(buffer, buflen, | 251 snprintf(buffer, buflen, |
173 fixed ? "%.*f" : "%.*e", | 252 display == Auto ? "%.*g" : |
253 display == Fixed ? "%.*f" : | |
254 "%.*e", | |
174 precision, value); | 255 precision, value); |
175 return Tick({ value, std::string(buffer) }); | 256 return Tick({ value, std::string(buffer) }); |
176 } | 257 } |
177 | 258 |
178 static Ticks explode(Instruction instruction) { | 259 static Ticks explode(Instruction instruction) { |
180 #ifdef DEBUG_SCALE_TICK_INTERVALS | 261 #ifdef DEBUG_SCALE_TICK_INTERVALS |
181 std::cerr << "initial = " << instruction.initial | 262 std::cerr << "initial = " << instruction.initial |
182 << ", limit = " << instruction.limit | 263 << ", limit = " << instruction.limit |
183 << ", spacing = " << instruction.spacing | 264 << ", spacing = " << instruction.spacing |
184 << ", roundTo = " << instruction.roundTo | 265 << ", roundTo = " << instruction.roundTo |
185 << ", fixed = " << instruction.fixed | 266 << ", display = " << instruction.display |
186 << ", precision = " << instruction.precision | 267 << ", precision = " << instruction.precision |
187 << ", logUnmap = " << instruction.logUnmap | 268 << ", logUnmap = " << instruction.logUnmap |
188 << std::endl; | 269 << std::endl; |
189 #endif | 270 #endif |
190 | 271 |
202 | 283 |
203 Ticks ticks; | 284 Ticks ticks; |
204 | 285 |
205 while (true) { | 286 while (true) { |
206 double value = instruction.initial + n * instruction.spacing; | 287 double value = instruction.initial + n * instruction.spacing; |
207 value = instruction.roundTo * round(value / instruction.roundTo); | |
208 if (value >= max + eps) { | 288 if (value >= max + eps) { |
209 break; | 289 break; |
210 } | 290 } |
211 if (instruction.logUnmap) { | 291 if (instruction.logUnmap) { |
212 value = pow(10.0, value); | 292 value = pow(10.0, value); |
213 } | 293 } |
214 ticks.push_back(makeTick(instruction.fixed, | 294 if (instruction.roundTo != 0.0) { |
295 value = instruction.roundTo * round(value / instruction.roundTo); | |
296 } | |
297 ticks.push_back(makeTick(instruction.display, | |
215 instruction.precision, | 298 instruction.precision, |
216 value)); | 299 value)); |
217 ++n; | 300 ++n; |
218 } | 301 } |
219 | 302 |