View Javadoc

1   /*
2    * Copyright (c) 2003-2008 by Cosylab d. d.
3    *
4    * This file is part of CosyBeans-Common.
5    *
6    * CosyBeans-Common is free software: you can redistribute it and/or modify
7    * it under the terms of the GNU General Public License as published by
8    * the Free Software Foundation, either version 3 of the License, or
9    * (at your option) any later version.
10   *
11   * CosyBeans-Common is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   * GNU General Public License for more details.
15   *
16   * You should have received a copy of the GNU General Public License
17   * along with CosyBeans-Common.  If not, see <http://www.gnu.org/licenses/>.
18   */
19  
20  package com.cosylab.gui.components.range2;
21  
22  /**
23   * Uses Range to find optimal tick distribution for LogarithmicRange.
24   *
25   * @author <a href="mailto:nejc.kosnik@kgb.ijs.si">Nejc Kosnik</a>
26   * @version $id$
27   */
28  public class LogarithmicTickCalculator implements TickCalculator
29  {
30  	/**
31  	 * Creates a new TickCollector object.
32  	 *
33  	 * @param range Definition of range.
34  	 * @param tickSpacing Minimum space between minor ticks.
35  	 * @param length Available space in pixels.
36  	 */
37  	public LogarithmicTickCalculator(int tickSpacing, boolean boundTicksVisible)
38  	{
39  
40  		this.tickSpacing = tickSpacing;
41  		this.boundTicksVisible = boundTicksVisible;
42  	}
43  
44  	private int tickSpacing;
45  	private boolean boundTicksVisible;
46  
47  	private Tick[] internalCalculateTicks(int length, TickParameters measurer, Range range, RangedValue rangedVal)
48  	{	
49  		if (measurer == null) measurer = new DefaultTickParameters(50, "%0.4g");
50  		double min = rangedVal.getMinimum();
51  		double max = rangedVal.getMaximum();
52  		double tickFactorLog = 1;
53  		double rmaxLog = Math.floor(logg(1.00000001*max));
54  		double rminLog = Math.ceil(logg(0.99999999*min));
55  		double tickSpace;
56  		int nticks;
57  		boolean minorTicksDrawn;
58  
59  		//no major ticks in the range, user is probably missusing logarithmic range
60  		if(Math.pow(10, rmaxLog) < min || Math.pow(10, rminLog) > max) {
61  			int i;
62  			double x;
63  			double step = Math.pow(10, rmaxLog);
64  			Tick[] ticks;
65  
66  			for(x = step; x < min; x += step) {
67  				;
68  			}
69  
70  			rminLog = Math.round(x / step) * step;
71  
72  			for(; x <= max; x += step) {
73  				;
74  			}
75  
76  			rmaxLog = Math.round(x / step - 1) * step;
77  
78  			nticks = (int)Math.round((rmaxLog - rminLog) / step + 1);
79  
80  			if(nticks == 1) {
81  				return onlyOneTick(rminLog, boundTicksVisible, min, max, range, rangedVal);
82  			}
83  
84  			tickSpace = (range.toRelative(rmaxLog,rangedVal)
85  				- range.toRelative(rmaxLog - step,rangedVal)) * length;
86  
87  			if(tickSpace > tickSpacing) {
88  				ticks = (boundTicksVisible ? new Tick[nticks + 2]
89  					: new Tick[nticks]);
90  
91  				if(boundTicksVisible) {
92  //					this tick was unlabelled originally
93  					ticks[0] = new Tick(min, range.toRelative(min,rangedVal), true, "");
94  
95  					for(i = 1, x = rminLog; x <= rmaxLog; x += step, i++) {
96  						ticks[i] = new Tick(x, range.toRelative(x,rangedVal), false);
97  					}
98  //					this tick was unlabelled originally
99  					ticks[nticks + 1] = new Tick(max, range.toRelative(max,rangedVal), true,  "");
100 
101 					return ticks;
102 				} else {
103 					for(i = 0, x = rminLog; x <= rmaxLog; x += step, i++) {
104 						ticks[i] = new Tick(x, range.toRelative(x,rangedVal), false);
105 					}
106 
107 					return ticks;
108 				}
109 			}
110 
111 			step *= 2.0;
112 
113 			for(x = step; x < min; x += step) {
114 				;
115 			}
116 
117 			rminLog = Math.round(x / step) * step;
118 
119 			for(; x <= max; x += step) {
120 				;
121 			}
122 
123 			rmaxLog = Math.round(x / step - 1) * step;
124 			nticks = (int)Math.round((rmaxLog - rminLog) / step + 1);
125 
126 			if(nticks == 1) {
127 				return onlyOneTick(rminLog, boundTicksVisible, min, max, range, rangedVal);
128 			}
129 
130 			tickSpace = (range.toRelative(rmaxLog,rangedVal)
131 				- range.toRelative(rmaxLog - step,rangedVal)) * length;
132 
133 			if(tickSpace > tickSpacing) {
134 				ticks = (boundTicksVisible ? new Tick[nticks + 2]
135 					: new Tick[nticks]);
136 
137 				if(boundTicksVisible) {
138 //					this tick was unlabelled originally			
139 					ticks[0] = new Tick(min, range.toRelative(min,rangedVal), true, "");
140 
141 					for(i = 1, x = rminLog; x <= rmaxLog; x += step, i++) {
142 						ticks[i] = new Tick(x, range.toRelative(x,rangedVal), false);
143 					}
144 
145 //					this tick was unlabelled originally
146 					ticks[nticks + 1] = new Tick(max, range.toRelative(max,rangedVal), true, "");
147 
148 					return ticks;
149 				} else {
150 					for(i = 0, x = rminLog; x <= rmaxLog; x += step, i++) {
151 						ticks[i] = new Tick(x, range.toRelative(x,rangedVal), false);
152 					}
153 
154 					return ticks;
155 				}
156 			}
157 
158 			step = Math.pow(10, Math.floor(logg(min)));
159 
160 			if(min <= 5 * step && max >= 5 * step) {
161 				rminLog = 5 * step;
162 
163 				return onlyOneTick(rminLog, boundTicksVisible, min, max, range, rangedVal);
164 			} else {
165 				ticks = new Tick[0];
166 
167 				return ticks;
168 			}
169 		}
170 
171 		//end of no-major-ticks case
172 		nticks = (int)((rmaxLog - rminLog) / tickFactorLog) + 1;
173 		tickSpace = (range.toRelative(Math.pow(10, rmaxLog),rangedVal)
174 			- range.toRelative(Math.pow(10, rminLog),rangedVal)) * length / nticks;
175 
176 		//we might have to decrease major ticks count
177 		while(tickSpace < tickSpacing || nticks > 25) {
178 			tickFactorLog++;
179 			rminLog = rmaxLog
180 				- Math.floor((rmaxLog - rminLog) / tickFactorLog) * tickFactorLog;
181 
182 			nticks = (int)((rmaxLog - rminLog) / tickFactorLog) + 1;
183 			tickSpace = (range.toRelative(Math.pow(10, rmaxLog),rangedVal)
184 				- range.toRelative(Math.pow(10, rminLog),rangedVal)) * length / nticks;
185 
186 			if(rminLog == rmaxLog) {
187 				break;
188 			}
189 		}
190 
191 		//only one major tick
192 		if(rminLog == rmaxLog) {
193 			double r = Math.max(Math.pow(10, rminLog) - min,
194 				    0.1 * (max - Math.pow(10, rmaxLog)));
195 			double step;
196 
197 			if(r < 0.1) {
198 				if(boundTicksVisible) {
199 					return new Tick[] {
200 						new Tick(min, 0, true, ""), new Tick(max, 1, true, "")
201 					};
202 				} else {
203 					return new Tick[] {
204 						new Tick(rminLog, range.toRelative(Math.pow(10, rminLog),rangedVal), true,
205 								measurer.formatNumber(Math.pow(10, rminLog)))
206 					};
207 				}
208 			}
209 
210 			minorTicksDrawn = (range.toRelative(Math.pow(10, rminLog),rangedVal)
211 				- range.toRelative(0.9 * Math.pow(10, rminLog),rangedVal)) * length > tickSpacing;
212 
213 			double majorTick = Math.pow(10, rminLog);
214 			step = 0.1 * majorTick;
215 
216 			double x;
217 
218 			for(x = Math.pow(10, rminLog); x >= min; x -= step) {
219 				;
220 			}
221 
222 			rminLog = x + step;
223 
224 			for(x = Math.pow(10, rminLog); x <= max; x += step) {
225 				;
226 			}
227 
228 			rmaxLog = x - step;
229 			nticks = (int)Math.round((rmaxLog - rminLog) / step + 1);
230 
231 			Tick[] ticks = new Tick[nticks];
232 			int i;
233 
234 			for(i = 0, x = rminLog; x < majorTick; x += step, i++) {
235 				ticks[i] = new Tick(x, range.toRelative(x,rangedVal), false);
236 			}
237 
238 			ticks[i++] = new Tick(x, range.toRelative(x,rangedVal), true,
239 					measurer.formatNumber(x));
240 			step *= 10;
241 			x += step;
242 
243 			for(; x <= max; x += step, i++) {
244 				ticks[i] = new Tick(x, range.toRelative(x,rangedVal), false);
245 			}
246 
247 			if(boundTicksVisible) {
248 				Tick[] ticks2 = new Tick[nticks + 2];
249 				//unlabelled originally
250 				ticks2[0] = new Tick(min, 0, true, "");
251 
252 				for(i = 1; i <= nticks; i++) {
253 					ticks2[i] = ticks[i - 1];
254 				}
255 
256 				ticks2[i] = new Tick(max, 1.0, true, "");
257 
258 				return ticks2;
259 			}
260 
261 			return ticks;
262 		}
263 		double rminLLog = rminLog;
264 		double rmaxLLog = rmaxLog;
265 		double tickFactorLLog = tickFactorLog;
266 
267 		//measuring tick labels
268 		double x;
269 
270 		while(true) {
271 			for(x = rminLLog; x < rmaxLLog - 0.1; x += tickFactorLLog) {
272 				if(measurer.measureTick(range.toRelative(Math.pow(10, x),rangedVal),
273 						measurer.formatNumber(Math.pow(10, x)))
274 				    + measurer.measureTick(range.toRelative(Math.pow(10,
275 				                x + tickFactorLLog),rangedVal),
276 				                measurer.formatNumber(Math.pow(10, x + tickFactorLLog))) > 1.5 * (range
277 				    .toRelative(Math.pow(10, x + tickFactorLLog),rangedVal)
278 				    - range.toRelative(Math.pow(10, x),rangedVal)) * length) {
279 					break;
280 				}
281 			}
282 
283 			if(x >= rmaxLLog) {
284 				break;
285 			} else {
286 				tickFactorLLog = (tickFactorLog == 1 ? tickFactorLLog + 1
287 					: 2 * tickFactorLLog);
288 				rminLLog = rmaxLLog
289 					- Math.floor((rmaxLLog - rminLog) / tickFactorLLog) * tickFactorLLog;
290 			}
291 		}
292 
293 		if((rmaxLLog - rminLLog) / tickFactorLLog + 1 > 12) { //to many labeled ticks
294 			tickFactorLLog++;
295 			rminLLog = rmaxLLog
296 				- Math.floor((rmaxLLog - rminLLog) / tickFactorLLog) * tickFactorLLog;
297 		}
298 
299 		int factor = 1;
300 		minorTicksDrawn = (range.toRelative(Math.pow(10, rmaxLog),rangedVal)
301 			- range.toRelative(0.9 * Math.pow(10, rmaxLog),rangedVal)) * length > tickSpacing
302 			&& Math.round(tickFactorLog) == 1;
303 
304 		if(minorTicksDrawn) {
305 			; // nothing to do here
306 		} else if((range.toRelative(Math.pow(10, rmaxLog),rangedVal)
307 		    - range.toRelative(0.8 * Math.pow(10, rmaxLog),rangedVal)) * length > tickSpacing
308 		    && Math.round(tickFactorLog) == 1) {
309 			minorTicksDrawn = true;
310 			factor = 2;
311 		} else if((range.toRelative(Math.pow(10, rmaxLog),rangedVal)
312 		    - range.toRelative(0.5 * Math.pow(10, rmaxLog),rangedVal)) * length > tickSpacing
313 		    && Math.round(tickFactorLog) == 1) {
314 			minorTicksDrawn = true;
315 			factor = 5;
316 		}
317 
318 		double step;
319 		double rmin;
320 		double rmax;
321 
322 		if(minorTicksDrawn) {
323 			rmax = Math.pow(10, rmaxLog + 1);
324 			step = rmax * factor / 10;
325 
326 			while(rmax > 1.00000001*max) {
327 				rmax -= step;
328 			}
329 
330 			rmin = Math.pow(10, rminLog - 1) * factor;
331 			step = rmin;
332 
333 			while(rmin < 0.99999999*min) {
334 				rmin += step;
335 			}
336 			nticks = (int)Math.round((factor == 1 ? 9 : 10 / factor) * (rmaxLog
337 			- rminLog) + 1
338 			+ rmax / (factor * Math.pow(10,
339 				rmaxLog))- (factor == 1 ? 1 : 0)
340 			+ (Math.pow(10, rminLog) - rmin) / (factor * Math.pow(10,
341 				rminLog - 1)));
342 				 
343 		} else {
344 			nticks = (int)((rmaxLog - rminLog) / tickFactorLog + 1);
345 			rmin = Math.pow(10, rminLog);
346 			step = rmin;
347 		}
348 		Tick[] ticks = new Tick[nticks];
349 
350 		int kmax = (int)Math.round(10.0 / factor);
351 		int kmin = (factor == 1 ? 2 : 1);
352 		int i = 0;
353 
354 		for(x = rmin; x < 0.95 * Math.pow(10, rminLog) && i < nticks;
355 		    x += step, i++) {
356 			ticks[i] = new Tick(x,range.toRelative(x,rangedVal), false);
357 		}
358 
359 		double xlog = Math.round(rminLog);
360 
361 		for(; xlog < rminLLog - 0.1 && i < nticks; xlog += tickFactorLog) {
362 			x = Math.pow(10, xlog);
363 			ticks[i++] = new Tick(x,range.toRelative(x,rangedVal), true, "");
364 
365 			if(minorTicksDrawn) {
366 				step = factor * x;
367 				x = (factor == 1 ? x + step : step);
368 
369 				for(int k = kmin; k < kmax && i < nticks;
370 				    k++, i++, x += step) {
371 					ticks[i] = new Tick(x,range.toRelative(x,rangedVal), false);
372 				}
373 			}
374 		}
375 
376 		int p = (int)Math.round(tickFactorLLog / tickFactorLog);
377 		xlog = Math.round(rminLLog);
378 
379 		for(; i < nticks; xlog += tickFactorLog) {
380 			x = Math.pow(10, xlog);
381 
382 			if(p == Math.round(tickFactorLLog / tickFactorLog)) {
383 				ticks[i++] = new Tick(x,range.toRelative(x,rangedVal), true,
384 						measurer.formatNumber(x));
385 				p = 1;
386 			} else {
387 				ticks[i++] = new Tick(x,range.toRelative(x,rangedVal), true, "");
388 				p++;
389 			}
390 
391 			if(minorTicksDrawn) {
392 				step = factor * x;
393 				x = (factor == 1 ? x + step : step);
394 
395 				for(int k = kmin; k < kmax && i < nticks;
396 				    k++, i++, x += step) {
397 					ticks[i] = new Tick(x,range.toRelative(x,rangedVal), false);
398 				}
399 			}
400 		}
401 
402 		if(boundTicksVisible) {
403 			Tick[] ticks2 = new Tick[nticks + 2];
404 			//unlabelled
405 			ticks2[0] = new Tick(min,0, true, "");
406 
407 			for(i = 1; i <= nticks; i++) {
408 				ticks2[i] = ticks[i - 1];
409 			}
410 			//unlabelled
411 			ticks2[i] = new Tick(max, 1, true, "");
412 			return ticks2;
413 		}
414 
415 		return ticks;
416 	}
417 
418 	private Tick[] onlyOneTick(double x, boolean boundVisible, double min, double max, Range range, RangedValue rangedVal)
419 	{
420 		Tick[] ticks = (boundVisible ? new Tick[3] : new Tick[1]);
421 
422 		if(boundVisible) {
423 			ticks[0] = new Tick(min,range.toRelative(min,rangedVal), true, "");
424 			ticks[1] = new Tick(x,range.toRelative(x,rangedVal), false);
425 			ticks[2] = new Tick(max,range.toRelative(max,rangedVal), true, "");
426 
427 			return ticks;
428 		}
429 
430 		ticks[0] = new Tick(x,range.toRelative(x,rangedVal), false);
431 
432 		return ticks;
433 	}
434 
435 	private double logg(double x)
436 	{
437 		return Math.log(x) / Math.log(10);
438 	}
439 
440 	/*
441 	 * (non-Javadoc)
442 	 * @see com.cosylab.gui.components.range2.TickCalculator#calculateTicks(int, com.cosylab.gui.components.range2.Range, com.cosylab.gui.components.range2.RangedValue)
443 	 */
444 	public Tick[] calculateTicks(int length, Range range, RangedValue rangedValue) {
445 		return calculateTicks(length, new DefaultTickParameters(50, "%0.4g"), range, rangedValue);
446 	}
447 
448 	/*
449 	 * (non-Javadoc)
450 	 * @see com.cosylab.gui.components.range2.TickCalculator#calculateTicks(int, com.cosylab.gui.components.range2.TickParameters, com.cosylab.gui.components.range2.Range, com.cosylab.gui.components.range2.RangedValue)
451 	 */
452 	public Tick[] calculateTicks(int length, TickParameters measurer, Range range, RangedValue rangedValue) {
453 		return internalCalculateTicks(length, measurer, range, rangedValue);
454 	}
455 	
456 }
457 
458 /* __oOo__ */