/**
* Copyright 2010-2013 Jingjing Li.
*
* This file is part of jplot2d.
*
* jplot2d is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or any later version.
*
* jplot2d is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Lesser Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with jplot2d. If not, see .
*/
package org.jplot2d.axtick;
import java.lang.reflect.Array;
import java.text.Format;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Formatter;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import org.jplot2d.axtick.DateInterval.Unit;
import org.jplot2d.tex.MathElement;
import org.jplot2d.tex.TeXMathUtils;
/**
*
* @author Jingjing Li
*
*/
public class TAIMicrosTickCalculator extends LongTickCalculator implements RangeAdvisor {
private final TimeZone zone;
private final Locale locale;
/**
* Always be positive
*/
private DateInterval dateInterval;
private int minorNumber;
private long[] tickValues;
private long[] minorValues;
public TAIMicrosTickCalculator(TimeZone zone, Locale locale) {
this.zone = zone;
this.locale = locale;
}
protected static DateInterval calcInterval(long start, long end, int tickNumber) {
if (start == end) {
throw new IllegalArgumentException("The range span must be great than zero");
}
if (tickNumber <= 0) {
throw new IllegalArgumentException("The ticks number must be great than zero");
} else if (tickNumber == 1) {
tickNumber = 2;
}
long lo, hi;
if (end > start) {
lo = start;
hi = end;
} else {
lo = end;
hi = start;
}
tickNumber = Math.abs(tickNumber);
DateInterval itvA, itvB;
double rough = (double) (hi - lo) / (tickNumber - 1);
if (rough < 1000) {
int scale;
if (rough < 10) {
scale = 1;
} else if (rough < 100) {
scale = 10;
} else {
scale = 100;
}
/* 1 <= rough/scale < 10 */
double coeff = rough / scale;
if (coeff < 2) {
itvA = new DateInterval(Unit.MICROSECOND, scale * 1);
itvB = new DateInterval(Unit.MICROSECOND, scale * 2);
} else if (coeff < 5) {
itvA = new DateInterval(Unit.MICROSECOND, scale * 2);
itvB = new DateInterval(Unit.MICROSECOND, scale * 5);
} else {
itvA = new DateInterval(Unit.MICROSECOND, scale * 5);
itvB = new DateInterval(Unit.MICROSECOND, scale * 10);
}
} else {
DateInterval[] itvAB = DateTickCalculator.calcCandidateIntervals(rough / 1000);
itvA = itvAB[0];
itvB = itvAB[1];
}
long iLoA = lo / itvA.getTimeInMicros();
long iHiA = hi / itvA.getTimeInMicros();
if (hi % itvA.getTimeInMicros() != 0) {
iHiA++;
}
int tickNumA = (int) (iHiA - iLoA + 1);
long iLoB = lo / itvB.getTimeInMicros();
long iHiB = hi / itvB.getTimeInMicros();
if (hi % itvB.getTimeInMicros() != 0) {
iHiB++;
}
int tickNumB = (int) (iHiB - iLoB + 1);
/* tickNumB < tickNumber < tickNumA */
if (tickNumA - tickNumber <= tickNumber - tickNumB) {
return itvA;
} else {
return itvB;
}
}
/**
* @param interval
* must be positive
* @param offset
* @param minorTickNumber
*/
protected void calcValues() {
if (dateInterval.getValue() == 0)
throw new IllegalArgumentException("delta cannot be zero");
TAIMicrosCalendar loCal = new TAIMicrosCalendar(zone, locale);
loCal.setTimeInMicros(lo);
TAIMicrosCalendar hiCal = new TAIMicrosCalendar(zone, locale);
hiCal.setTimeInMicros(hi);
TAIMicrosCalendar t1cal = (TAIMicrosCalendar) loCal.clone();
/* round up to unit */
setCalendarBelowToMin(t1cal, dateInterval.getUnit());
if (t1cal.before(loCal)) {
add(t1cal, dateInterval.getUnit(), 1);
}
List ticks = new ArrayList();
List mticks = new ArrayList();
if (minorNumber == 0) {
int delta = distanceToIntervalBoundary(t1cal, dateInterval);
if (delta != 0) {
add(t1cal, dateInterval.getUnit(), dateInterval.getValue() - delta);
}
while (!t1cal.after(hiCal)) {
// System.out.println(t1cal.getTimeInMicros());
ticks.add(t1cal.getTimeInMicros());
add(t1cal, dateInterval.getUnit(), dateInterval.getValue());
}
} else {
int mitv = dateInterval.getValue() / (minorNumber + 1);
DateInterval minorInterval = new DateInterval(dateInterval.getUnit(), mitv);
int delta = distanceToIntervalBoundary(t1cal, minorInterval);
if (delta != 0) {
add(t1cal, dateInterval.getUnit(), mitv - delta);
}
while (!t1cal.after(hiCal)) {
if (distanceToIntervalBoundary(t1cal, dateInterval) == 0) {
ticks.add(t1cal.getTimeInMicros());
} else {
mticks.add(t1cal.getTimeInMicros());
}
add(t1cal, dateInterval.getUnit(), mitv);
}
}
tickValues = new long[ticks.size()];
minorValues = new long[mticks.size()];
if (inverted) {
for (int i = 0, j = tickValues.length - 1; i < tickValues.length; i++, j--) {
tickValues[i] = ticks.get(j);
}
for (int i = 0, j = minorValues.length - 1; i < minorValues.length; i++, j--) {
minorValues[i] = mticks.get(j);
}
} else {
for (int i = 0; i < tickValues.length; i++) {
tickValues[i] = ticks.get(i);
}
for (int i = 0; i < minorValues.length; i++) {
minorValues[i] = mticks.get(i);
}
}
}
public void expandRangeByTickNumber(int tickNumber) {
if (tickNumber <= 0) {
throw new IllegalArgumentException("tick number must be positive.");
} else if (tickNumber == 1) {
tickNumber = 2;
}
long span = hi - lo;
if (span < tickNumber - 1) {
/* expand range to tick number */
long halfXpand = (tickNumber - 1 - span) / 2;
long odd = (tickNumber - 1 - span) % 2;
lo -= halfXpand;
hi += halfXpand;
hi += odd;
if (lo < 0) {
lo = 0;
hi -= lo;
}
dateInterval = new DateInterval(Unit.MICROSECOND, 1);
} else {
dateInterval = calcInterval(lo, hi, tickNumber);
expandRangeByTickInterval();
}
}
public void expandRangeByTickInterval(double interval) {
dateInterval = DateInterval.createWithMicros((long) Math.round(interval));
expandRangeByTickInterval();
/* if _lo == _hi and on interval boundary, the expanding has no effect */
if (lo == hi) {
TAIMicrosCalendar hiCal = new TAIMicrosCalendar(zone, locale);
hiCal.setTimeInMicros(hi);
add(hiCal, dateInterval.getUnit(), dateInterval.getValue());
hi = hiCal.getTimeInMicros();
}
}
protected void expandRangeByTickInterval() {
TAIMicrosCalendar loCal = new TAIMicrosCalendar(zone, locale);
loCal.setTimeInMicros(lo);
TAIMicrosCalendar hiCal = new TAIMicrosCalendar(zone, locale);
hiCal.setTimeInMicros(hi);
setCalendarBelowToMin(loCal, dateInterval.getUnit());
int loDelta = distanceToIntervalBoundary(loCal, dateInterval);
if (loDelta != 0) {
add(loCal, dateInterval.getUnit(), -loDelta);
}
setCalendarBelowToMin(hiCal, dateInterval.getUnit());
if (hiCal.getTimeInMicros() < hi) {
add(hiCal, dateInterval.getUnit(), 1);
}
int hiDelta = distanceToIntervalBoundary(hiCal, dateInterval);
if (hiDelta != 0) {
add(hiCal, dateInterval.getUnit(), dateInterval.getValue() - hiDelta);
}
lo = loCal.getTimeInMicros();
hi = hiCal.getTimeInMicros();
}
public void calcValuesByTickNumber(int tickNumber, int minorTickNumber) {
DateInterval interval = calcInterval(lo, hi, tickNumber);
calcValuesByTickInterval(interval, 0, minorTickNumber);
}
public void calcValuesByTickInterval(long interval, long offset, int minorTickNumber) {
calcValuesByTickInterval(DateInterval.createWithMicros(interval), offset, minorTickNumber);
}
private void calcValuesByTickInterval(DateInterval interval, long offset, int minorTickNumber) {
dateInterval = interval;
if (dateInterval.getValue() == 1) {
minorNumber = 0;
} else {
if (minorTickNumber == AUTO_MINORTICK_NUMBER) {
minorTickNumber = 3;
}
minorNumber = calcMinorNumber(dateInterval.getValue(), minorTickNumber);
}
calcValues();
}
public double getInterval() {
return dateInterval.getTimeInMicros();
}
public int getMinorNumber() {
return minorNumber;
}
public long[] getValues() {
return tickValues;
}
public long[] getMinorValues() {
return minorValues;
}
public String getLabelFormate() {
TAIMicrosCalendar loCal = new TAIMicrosCalendar(zone, locale);
loCal.setTimeInMicros(lo);
TAIMicrosCalendar hiCal = new TAIMicrosCalendar(zone, locale);
hiCal.setTimeInMicros(hi);
Unit umax = getFirsNonEqualField(loCal, hiCal);
return DateTickCalculator.calcLabelFormat(dateInterval.getUnit(), umax);
}
public Format calcLabelTextFormat(Object canonicalValues) {
return null;
}
public String calcLabelFormatString(Object values) {
if (((long[]) values).length == 0) {
return "";
}
TAIMicrosCalendar cal = new TAIMicrosCalendar(zone, locale);
Unit umin = Unit.YEAR;
long lo = Long.MAX_VALUE, hi = 0;
for (long v : (long[]) values) {
if (lo > v) {
lo = v;
}
if (hi < v) {
hi = v;
}
cal.setTimeInMicros(v);
Unit lnmf = getLastNonMinField(cal);
if (umin.time > lnmf.time) {
umin = lnmf;
}
}
TAIMicrosCalendar loCal = new TAIMicrosCalendar(zone, locale);
loCal.setTimeInMicros(lo);
TAIMicrosCalendar hiCal = new TAIMicrosCalendar(zone, locale);
hiCal.setTimeInMicros(hi);
Unit umax = getFirsNonEqualField(loCal, hiCal);
return DateTickCalculator.calcLabelFormat(umin, umax);
}
public boolean isValidFormat(String format) {
try {
String.format(format, Calendar.getInstance(zone, locale));
return true;
} catch (IllegalArgumentException e) {
return false;
}
}
protected static Unit getLastNonMinField(TAIMicrosCalendar cal) {
if (cal.get(TAIMicrosCalendar.MICROSECOND) > cal.getMinimum(TAIMicrosCalendar.MICROSECOND)) {
return Unit.MICROSECOND;
} else {
return DateTickCalculator.getLastNonMinField(cal.calendar);
}
}
protected static Unit getFirsNonEqualField(TAIMicrosCalendar a, TAIMicrosCalendar b) {
Unit u = DateTickCalculator.getFirsNonEqualField(a.calendar, b.calendar);
if (u != null) {
return u;
} else if (a.get(TAIMicrosCalendar.MICROSECOND) != b.get(TAIMicrosCalendar.MICROSECOND)) {
return Unit.MICROSECOND;
}
return null;
}
/**
* Sets all calendar fields below the given unit to its minimal value.
*
* @param cal
* @param unit
* @return true if any fields below the given unit is larger than its minimal value
*/
protected static void setCalendarBelowToMin(TAIMicrosCalendar cal, Unit unit) {
switch (unit) {
case MICROSECOND:
break;
default:
DateTickCalculator.setCalendarBelowToMin(cal.calendar, unit);
cal.set(TAIMicrosCalendar.MICROSECOND, 0);
break;
}
}
protected static void add(TAIMicrosCalendar cal, Unit unit, int amount) {
switch (unit) {
case MICROSECOND:
cal.add(TAIMicrosCalendar.MICROSECOND, amount);
break;
case MILLISECOND:
cal.add(Calendar.MILLISECOND, amount);
break;
case SECOND:
cal.add(Calendar.SECOND, amount);
break;
case MINUTE:
cal.add(Calendar.MINUTE, amount);
break;
case HOUR:
cal.add(Calendar.HOUR_OF_DAY, amount);
break;
case DAY:
cal.add(Calendar.DAY_OF_MONTH, amount);
break;
case WEEK:
cal.add(Calendar.WEEK_OF_YEAR, amount);
break;
case MONTH:
cal.add(Calendar.MONTH, amount);
break;
case YEAR:
cal.add(Calendar.YEAR, amount);
break;
}
}
/**
* Returns the distance downward to a interval boundary.
*
* @param cal
* @param itv
* @return the distance.
*/
protected static int distanceToIntervalBoundary(TAIMicrosCalendar cal, DateInterval itv) {
switch (itv.getUnit()) {
case MICROSECOND:
return cal.get(TAIMicrosCalendar.MICROSECOND) % itv.getValue();
default:
return DateTickCalculator.distanceToIntervalBoundary(cal.calendar, itv);
}
}
public MathElement[] formatValues(String format, Object values) {
TAIMicrosCalendar cal = new TAIMicrosCalendar(zone, locale);
int n = Array.getLength(values);
MathElement[] labels = new MathElement[n];
for (int i = 0; i < n; i++) {
cal.setTimeInMicros(Array.getLong(values, i));
@SuppressWarnings("resource")
String microString = String.valueOf(cal.microsecond);
if (microString.length() == 1) {
microString = "00" + microString;
} else if (microString.length() == 2) {
microString = "0" + microString;
}
String texString = new Formatter(locale).format(format, cal.calendar).toString();
int z6idx = texString.lastIndexOf("000000");
texString = texString.substring(0, z6idx) + microString + texString.substring(z6idx + 6);
if (texString.indexOf('$') == -1) {
labels[i] = new MathElement.Mtext(texString);
} else {
labels[i] = TeXMathUtils.parseText(texString);
}
}
return labels;
}
}
Ads help maintain this website.