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