/**
* Copyright 2010-2014 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.element.impl;
import java.awt.Font;
import java.awt.geom.Dimension2D;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.text.Format;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jplot2d.axtick.RangeAdvisor;
import org.jplot2d.axtick.TickAlgorithm;
import org.jplot2d.axtick.TickCalculator;
import org.jplot2d.element.AxisTransform;
import org.jplot2d.notice.Notice;
import org.jplot2d.tex.MathElement;
import org.jplot2d.tex.TeXMathUtils;
import org.jplot2d.transform.AxisTickTransform;
import org.jplot2d.transform.NormalTransform;
import org.jplot2d.transform.TransformType;
import org.jplot2d.util.NumberArrayUtils;
import org.jplot2d.util.Range;
/**
* Manage tick values and labels of multiple axes.
*
* Implementation Notes: This class has some internal cache for properties of its axes to trace the values valid
* conditions. The {@link #calcTicks()} can detect if ticks need to be recalculated.
*
* @author Jingjing Li
*
*/
public class AxisTickManagerImpl extends ElementImpl implements AxisTickManagerEx, Cloneable {
/**
* An internal cache for status of an axis managed by this tick manager
*/
private static class AxisStatus {
private double axisLength;
private boolean labelSameOrientation;
private Font labelFont;
public AxisStatus(double length, boolean labelSameOrientation, Font font) {
this.axisLength = length;
this.labelSameOrientation = labelSameOrientation;
this.labelFont = font;
}
public double getAxisLength() {
return axisLength;
}
public boolean isLabelSameOrientation() {
return labelSameOrientation;
}
public Font getLabelFont() {
return labelFont;
}
}
public static final int DEFAULT_TICKS_NUMBER = 11;
public static final int AUTO_TICKS_MIN = 4;
private AxisTransformEx axisTransform;
private List axes = new ArrayList();
private boolean autoAdjustNumber = true;
private int tickNumber = DEFAULT_TICKS_NUMBER;
private boolean autoInterval = true;
private double interval;
private double offset;
private boolean autoValues = true;
private Object fixedValues;
private Object fixedMinorValues;
private boolean autoMinorNumber = true;
private int actualMinorNumber;
private int minorNumber;
private boolean autoLabelFormat = true;
private Format labelTextFormat;
private String labelFormat;
/**
* fixed labels correspondent to fixed ticks
*/
private MathElement[] fixedLabels;
private int labelInterval = 1;
/**
* values in visible range
*/
private Object values = new double[0];
private Object minorValues = new double[0];
/**
* labels before fixed label override.
*/
private MathElement[] autoLabels;
/**
* labels in visible range
*/
private MathElement[] labels = new MathElement[0];
private TickAlgorithm tickAlgorithm;
private TickCalculator tickCalculator;
/* =========== xxx Changed =========== */
private boolean autoAdjustNumberChanged;
private boolean autoIntervalChanged;
private boolean autoValuesChanged;
private boolean autoLabelFormatChanged;
private boolean propNumberChanged;
/**
* interval or offset changed
*/
private boolean intervalChanged;
private boolean _valuesChanged;
/**
* The minorNumber changed or autoMinorNumber changed
*/
private boolean minorNumberChanged;
private boolean _minorValuesChanged;
private boolean labelFormatChanged;
/**
* The label font cache for detect font changes
*/
private Map axisStatusMap = new HashMap();
private boolean _labelMaxDensityChanged;
private boolean _rangeChanged;
private boolean _tickAlgorithmChanged;
private boolean _axisCircularRangeChanged;
private boolean _trfChanged;
/**
* The tick range for calculation
*/
private Range range;
private AxisTickTransform tickTransform;
private NormalTransform axisNormalTransform;
private Range circularRange;
public AxisTickManagerImpl() {
}
public String getId() {
StringBuilder sb = new StringBuilder();
sb.append("Tick(");
for (AxisEx axis : axes) {
sb.append(axis.getShortId()).append(',');
}
sb.replace(sb.length() - 1, sb.length(), ")");
return sb.toString();
}
public String getFullId() {
if (axisTransform != null) {
int xidx = axisTransform.indexOfTickManager(this);
return "Tick" + xidx + "." + axisTransform.getFullId();
} else {
return "AxisTickManager@" + Integer.toHexString(System.identityHashCode(this));
}
}
public InvokeStep getInvokeStepFormParent() {
if (axes.size() == 0) {
return null;
}
Method method;
try {
method = AxisEx.class.getMethod("getTickManager");
} catch (NoSuchMethodException e) {
throw new Error(e);
}
return new InvokeStep(method);
}
public AxisEx getParent() {
return (AxisImpl) parent;
}
public ElementEx getPrim() {
if (axes.size() == 0) {
return null;
} else {
return axes.get(0);
}
}
public void notify(Notice msg) {
if (getPrim() != null) {
getPrim().notify(msg);
}
}
public AxisTransformEx getAxisTransform() {
return axisTransform;
}
public void setAxisTransform(AxisTransform rangeManager) {
if (this.axisTransform != null) {
this.axisTransform.removeTickManager(this);
}
this.axisTransform = (AxisTransformEx) rangeManager;
if (this.axisTransform != null) {
this.axisTransform.addTickManager(this);
updateTickAlgorithm();
}
}
public void transformTypeChanged() {
updateTickAlgorithm();
}
/**
* update tick algorithm when axis type or tick transform changed.
*/
private void updateTickAlgorithm() {
TickAlgorithm ta = axisTransform.getType().getTickAlgorithm(axisTransform.getTransform(), getTickTransform());
if (ta == null) {
ta = axisTransform.getType().getTickAlgorithm(axisTransform.getTransform(), null);
}
setTickAlgorithm(ta);
}
public AxisEx[] getAxes() {
return axes.toArray(new AxisEx[axes.size()]);
}
public void addAxis(AxisEx axis) {
axes.add(axis);
if (axes.size() == 1) {
parent = axes.get(0);
} else {
parent = null;
}
/*
* Note: The axis cache info will be put when calcTicks() is called
*/
_labelMaxDensityChanged = true;
}
public void removeAxis(AxisEx axis) {
axes.remove(axis);
if (axes.size() == 1) {
parent = axes.get(0);
} else {
parent = null;
}
axisStatusMap.remove(axis);
_labelMaxDensityChanged = true;
}
public AxisTickTransform getTickTransform() {
return tickTransform;
}
public void setTickTransform(AxisTickTransform transform) {
this.tickTransform = transform;
_trfChanged = true;
updateTickAlgorithm();
}
public Range getRange() {
Range range = getAxisTransform().getRange();
if (tickTransform == null) {
return range;
} else {
double start = tickTransform.transformUser2Tick(range.getStart());
double end = tickTransform.transformUser2Tick(range.getEnd());
return new Range.Double(start, end);
}
}
public void setRange(Range range) {
if (tickTransform == null) {
getAxisTransform().setRange(range);
} else {
double ustart = tickTransform.transformTick2User(range.getStart());
double uend = tickTransform.transformTick2User(range.getEnd());
getAxisTransform().setRange(new Range.Double(ustart, uend));
}
}
public boolean isAutoAdjustTicks() {
return autoAdjustNumber;
}
public void setAutoAdjustTicks(boolean flag) {
this.autoAdjustNumber = flag;
autoAdjustNumberChanged = true;
}
public int getTicks() {
return tickNumber;
}
public void setTicks(int tickNumber) {
this.tickNumber = tickNumber;
propNumberChanged = true;
}
public boolean isAutoTickInterval() {
return autoInterval;
}
public void setAutoTickInterval(boolean autoInterval) {
if (this.autoInterval != autoInterval) {
this.autoInterval = autoInterval;
autoIntervalChanged = true;
}
}
public double getTickInterval() {
return interval;
}
public double getTickOffset() {
return offset;
}
public void setTickInterval(double interval) {
if (Double.isNaN(interval) || Double.isInfinite(interval)) {
throw new IllegalArgumentException("tick interval can not be NaN or Infinit.");
}
if (this.interval != interval) {
this.interval = interval;
intervalChanged = true;
setAutoTickInterval(false);
}
}
public void setTickOffset(double offset) {
if (Double.isNaN(interval) || Double.isInfinite(interval)) {
throw new IllegalArgumentException("tick offset can not be NaN or Infinit.");
}
if (this.offset != offset) {
this.offset = offset;
intervalChanged = true;
setAutoTickInterval(false);
}
}
public boolean isAutoTickValues() {
return autoValues;
}
public void setAutoTickValues(boolean atv) {
if (this.autoValues != atv) {
this.autoValues = atv;
this.autoValuesChanged = true;
}
}
public Object getFixedTickValues() {
return fixedValues;
}
public void setFixedTickValues(Object values) {
this.fixedValues = values;
setAutoTickValues(false);
}
public Object getFixedMinorTickValues() {
return fixedMinorValues;
}
public void setFixedMinorTickValues(Object minorValues) {
this.fixedMinorValues = minorValues;
}
public boolean isAutoMinorTicks() {
return autoMinorNumber;
}
public void setAutoMinorTicks(boolean flag) {
if (this.autoMinorNumber != flag) {
this.autoMinorNumber = flag;
minorNumberChanged = true;
}
}
public int getMinorTicks() {
return minorNumber;
}
public void setMinorTicks(int minors) {
if (this.minorNumber != minors) {
this.minorNumber = minors;
minorNumberChanged = true;
setAutoMinorTicks(false);
}
}
public int getActualMinorTicks() {
return actualMinorNumber;
}
/* =========================== Labels ============================= */
public boolean isAutoLabelFormat() {
return autoLabelFormat;
}
public void setAutoLabelFormat(boolean alf) {
if (this.autoLabelFormat != alf) {
this.autoLabelFormat = alf;
autoLabelFormatChanged = true;
}
}
public Format getLabelTextFormat() {
return labelTextFormat;
}
public void setLabelTextFormat(Format format) {
this.labelTextFormat = format;
labelFormatChanged = true;
setAutoLabelFormat(false);
}
public String getLabelFormat() {
return labelFormat;
}
public void setLabelFormat(String format) {
if (tickCalculator.isValidFormat(format)) {
this.labelFormat = format;
labelFormatChanged = true;
setAutoLabelFormat(false);
} else {
throw new IllegalArgumentException("The label format is invalid");
}
}
public String[] getFixedLabelStrings() {
if (fixedLabels == null) {
return new String[0];
}
String[] result = new String[fixedLabels.length];
for (int i = 0; i < fixedLabels.length; i++) {
result[i] = TeXMathUtils.toString(fixedLabels[i]);
}
return result;
}
public void setFixedLabelStrings(String[] labels) {
MathElement[] mes = new MathElement[labels.length];
for (int i = 0; i < mes.length; i++) {
mes[i] = TeXMathUtils.parseText(labels[i]);
}
fixedLabels = mes;
}
public int getLabelInterval() {
return labelInterval;
}
public void setLabelInterval(int n) {
if (n <= 0) {
throw new IllegalArgumentException("Label interval cannot be 0 or negative.");
}
this.labelInterval = n;
}
public Object getTickValues() {
return values;
}
public Object getMinorTickValues() {
return minorValues;
}
public MathElement[] getLabelModels() {
return labels;
}
public TickAlgorithm getTickAlgorithm() {
return tickAlgorithm;
}
public void setTickAlgorithm(TickAlgorithm algorithm) {
if (tickAlgorithm != algorithm) {
tickAlgorithm = algorithm;
tickCalculator = tickAlgorithm.createCalculator();
_tickAlgorithmChanged = true;
}
}
public String[] getLabelStrings() {
String[] result = new String[labels.length];
for (int i = 0; i < labels.length; i++) {
result[i] = TeXMathUtils.toString(labels[i]);
}
return result;
}
public void calcTicks() {
// detect changes of range manager
AxisTransformEx va = getAxisTransform();
if (!getRange().equals(this.range)) {
_rangeChanged = true;
this.range = getRange();
}
if (_tickAlgorithmChanged || _rangeChanged) {
tickCalculator.setRange(range);
}
if (!va.getNormalTransform().equals(this.axisNormalTransform)) {
_trfChanged = true;
this.axisNormalTransform = va.getNormalTransform();
}
if (va.getType().getCircularRange() != this.circularRange) {
_axisCircularRangeChanged = true;
this.circularRange = va.getType().getCircularRange();
}
for (AxisEx axis : axes) {
AxisStatus axisStatus = axisStatusMap.get(axis);
if (axisStatus == null) { // new axis
axisStatus = new AxisStatus(axis.getLength(), isLabelSameOrientation(axis), axis.getEffectiveFont());
axisStatusMap.put(axis, axisStatus);
} else { // re-create axisStatus if changed
boolean labelSameOrientation = isLabelSameOrientation(axis);
boolean newStatus = false;
if (axis.getLength() != axisStatus.getAxisLength()) {
_labelMaxDensityChanged = true;
newStatus = true;
}
if (labelSameOrientation != axisStatus.isLabelSameOrientation()) {
_labelMaxDensityChanged = true;
newStatus = true;
}
if (!axis.getEffectiveFont().equals(axisStatus.getLabelFont())) {
_labelMaxDensityChanged = true;
newStatus = true;
}
if (newStatus) {
axisStatus = new AxisStatus(axis.getLength(), labelSameOrientation, axis.getEffectiveFont());
axisStatusMap.put(axis, axisStatus);
}
}
}
if (autoValues && autoInterval && autoAdjustNumber) {
calcAutoTicks();
return;
}
int[] valueIdxes = null, mvalueIdxes;
Object values, mvalues;
if (autoValues) {
if (autoInterval) {
if (autoValuesChanged || autoIntervalChanged || autoAdjustNumberChanged || propNumberChanged
|| minorNumberChanged || _tickAlgorithmChanged || _axisCircularRangeChanged || _rangeChanged) {
tickCalculator.calcValuesByTickNumber(tickNumber, autoMinorNumber ? -1 : minorNumber);
interval = tickCalculator.getInterval();
actualMinorNumber = tickCalculator.getMinorNumber();
}
} else {
if (autoValuesChanged || intervalChanged || minorNumberChanged || _tickAlgorithmChanged
|| _axisCircularRangeChanged || _rangeChanged) {
tickCalculator.calcValuesByTickInterval(interval, offset, autoMinorNumber ? -1 : minorNumber);
actualMinorNumber = tickCalculator.getMinorNumber();
}
}
values = tickCalculator.getValues();
mvalues = tickCalculator.getMinorValues();
} else {
// filter off the ticks out of range
if (fixedValues != null) {
valueIdxes = tickCalculator.getInRangeValuesIdx(fixedValues);
values = NumberArrayUtils.subArray(fixedValues, valueIdxes);
} else {
values = new double[0];
}
if (fixedMinorValues != null) {
mvalueIdxes = tickCalculator.getInRangeValuesIdx(fixedMinorValues);
mvalues = NumberArrayUtils.subArray(fixedMinorValues, mvalueIdxes);
} else {
mvalues = new double[0];
}
}
if (!NumberArrayUtils.equals(this.values, values)) {
this.values = values;
_valuesChanged = true;
}
if (!NumberArrayUtils.equals(this.minorValues, mvalues)) {
this.minorValues = mvalues;
_minorValuesChanged = true;
}
autoIntervalChanged = autoAdjustNumberChanged = false;
propNumberChanged = minorNumberChanged = false;
intervalChanged = false;
autoValuesChanged = _rangeChanged = _tickAlgorithmChanged = false;
/* calculate auto format & labels form values */
if (autoLabelFormat && (_valuesChanged || autoLabelFormatChanged)) {
Format atf = tickCalculator.calcLabelTextFormat(getCanonicalValues(values));
String alf = tickCalculator.calcLabelFormatString(getCanonicalValues(values));
changeLabelFormat(atf, alf);
}
autoLabelFormatChanged = false;
if (_valuesChanged || labelFormatChanged) {
autoLabels = calcAutoLabels(getCanonicalValues(values), labelTextFormat, labelFormat);
}
labelFormatChanged = false;
/* apply fixed labels */
MathElement[] fixedLabelsInRange;
if (autoValues || fixedLabels == null) {
fixedLabelsInRange = fixedLabels;
} else {
List ul = new ArrayList();
for (int i : valueIdxes) {
if (i < fixedLabels.length) {
ul.add(fixedLabels[i]);
}
}
fixedLabelsInRange = ul.toArray(new MathElement[ul.size()]);
}
MathElement[] labels = calcLabels(fixedLabelsInRange, autoLabels, labelInterval);
boolean labelsChanged = false;
if (!Arrays.equals(this.labels, labels)) {
this.labels = labels;
labelsChanged = true;
}
/* shrink labels */
if (_valuesChanged || labelsChanged || _labelMaxDensityChanged || _axisCircularRangeChanged || _trfChanged) {
shrinkLabelFonts();
}
_labelMaxDensityChanged = _trfChanged = _axisCircularRangeChanged = false;
boolean tickChanged = false;
if (labelsChanged || _labelMaxDensityChanged) {
tickChanged = true;
_labelMaxDensityChanged = false;
}
if (_valuesChanged || _minorValuesChanged) {
tickChanged = true;
_valuesChanged = _minorValuesChanged = _trfChanged = false;
}
if (tickChanged) {
for (AxisEx axis : axes) {
axis.invalidateThickness();
ComponentImpl.redraw(axis);
}
}
}
/**
* calculate interval from given range, tick number, label height and label format. This will set the interval, the
* values and the label density.
*/
private void calcAutoTicks() {
/* Nothing changed */
if (!(autoValuesChanged || autoIntervalChanged || autoAdjustNumberChanged || propNumberChanged
|| minorNumberChanged || autoLabelFormatChanged || labelFormatChanged || _labelMaxDensityChanged
|| _tickAlgorithmChanged || _trfChanged || _rangeChanged)) {
return;
}
autoValuesChanged = autoIntervalChanged = autoAdjustNumberChanged = false;
propNumberChanged = minorNumberChanged = false;
autoLabelFormatChanged = labelFormatChanged = false;
_labelMaxDensityChanged = false;
_tickAlgorithmChanged = _trfChanged = false;
_rangeChanged = false;
int tickNumber = this.tickNumber;
Object values;
Format labelTextFormat = this.labelTextFormat;
String labelFormat = this.labelFormat;
MathElement[] autoLabels;
MathElement[] labels;
while (true) {
tickCalculator.calcValuesByTickNumber(tickNumber, autoMinorNumber ? -1 : minorNumber);
values = tickCalculator.getValues();
if (autoLabelFormat) {
labelTextFormat = tickCalculator.calcLabelTextFormat(getCanonicalValues(values));
labelFormat = tickCalculator.calcLabelFormatString(getCanonicalValues(values));
}
autoLabels = calcAutoLabels(getCanonicalValues(values), labelTextFormat, labelFormat);
labels = calcLabels(fixedLabels, autoLabels, labelInterval);
double density = getMaxLabelsDensity(axisNormalTransform, values, labels);
if (density <= 1) {
break;
}
/* fast approximating, the next tick we should try */
tickNumber = Math.min(tickNumber, Array.getLength(values)) - 1;
if (tickNumber < AUTO_TICKS_MIN) {
break;
}
}
if (!NumberArrayUtils.equals(this.values, values)) {
this.values = values;
_valuesChanged = true;
}
if (!NumberArrayUtils.equals(minorValues, tickCalculator.getMinorValues())) {
this.minorValues = tickCalculator.getMinorValues();
this.actualMinorNumber = tickCalculator.getMinorNumber();
_minorValuesChanged = true;
}
boolean labelsChanged = false;
if (!Arrays.equals(this.labels, labels)) {
this.labels = labels;
labelsChanged = true;
}
interval = tickCalculator.getInterval();
changeLabelFormat(labelTextFormat, labelFormat);
this.autoLabels = autoLabels;
if (tickNumber < AUTO_TICKS_MIN) {
shrinkLabelFonts();
} else {
// reset actual label font on all axes
for (AxisEx axis : axes) {
axis.setActualFont(axis.getEffectiveFont());
}
}
boolean tickChanged = false;
if (labelsChanged || _labelMaxDensityChanged) {
tickChanged = true;
_labelMaxDensityChanged = false;
}
if (_valuesChanged || _minorValuesChanged) {
tickChanged = true;
_valuesChanged = _minorValuesChanged = _trfChanged = false;
}
if (tickChanged) {
for (AxisEx axis : axes) {
axis.invalidateThickness();
ComponentImpl.redraw(axis);
}
}
}
private boolean isLabelSameOrientation(AxisEx axis) {
return (axis.getOrientation() == axis.getLabelOrientation());
}
/**
* Combine the user assigned labels with auto labels.
*
* @param fixedLabels
* user labels for every major tick, no label interval considered.
* @param autoLabels
* labels for every major tick, no label interval considered.
* @param labelInterval
* @return
*/
private static MathElement[] calcLabels(MathElement[] fixedLabels, MathElement[] autoLabels, int labelInterval) {
int fixedLabelsLength = (fixedLabels == null) ? 0 : fixedLabels.length;
int autoLabelsLength = (autoLabels == null) ? 0 : autoLabels.length;
int length = (int) Math.floor((double) (autoLabelsLength - 1) / labelInterval) + 1;
MathElement[] result = new MathElement[length];
int j = 0;
for (int i = 0; i < autoLabelsLength; i = i + labelInterval) {
if (i < fixedLabelsLength && fixedLabels[i] != null) {
result[j] = fixedLabels[i];
} else {
result[j] = autoLabels[i];
}
j++;
}
return result;
}
/**
* Calculate label from values and format.
*/
private MathElement[] calcAutoLabels(Object values, Format textFormat, String format) {
if (textFormat == null) {
return tickCalculator.formatValues(format, values);
} else {
return tickCalculator.formatValues(textFormat, values);
}
}
/**
* @param format
* must not be null
*/
private void changeLabelFormat(Format textFormat, String format) {
if (labelTextFormat != textFormat && (labelTextFormat == null || !labelTextFormat.equals(textFormat))) {
labelTextFormat = textFormat;
labelFormatChanged = true;
}
if (labelFormat != format && (labelFormat == null || !labelFormat.equals(format))) {
labelFormat = format;
labelFormatChanged = true;
}
}
/**
* Shrink label font to satisfy density on all axes.
*
* @return
*/
private void shrinkLabelFonts() {
for (AxisEx axis : axes) {
AxisStatus axisStatus = axisStatusMap.get(axis);
Font sf = shrinkLabelFont(axisStatus);
axis.setActualFont(sf);
}
}
private Font shrinkLabelFont(AxisStatus axisStatus) {
Font font = axisStatus.getLabelFont();
double density = getLabelsDensity(axisNormalTransform, axisStatus.getAxisLength(), values, labels, font,
axisStatus.isLabelSameOrientation());
if (Double.isInfinite(density)) { // when axis length is zero
return font;
}
double oldDensity = density;
int count = 0;
while (density > 1) {
font = font.deriveFont((float) (font.getSize2D() / ((density > 1.1) ? density : 1.1)));
density = getLabelsDensity(axisNormalTransform, axisStatus.getAxisLength(), values, labels, font,
axisStatus.isLabelSameOrientation());
if (oldDensity / density >= 1.1) {
oldDensity = density;
count = 0;
} else {
if (count++ > 10) {
break;
}
}
}
return font;
}
/**
* density > 1 means label overlapped.
*
* @return the label density.
*/
private double getMaxLabelsDensity(NormalTransform nxf, Object tickValues, MathElement[] labels) {
double maxDensity = 0;
for (AxisEx axis : axes) {
double density = getLabelsDensity(nxf, axis.getLength(), tickValues, labels, axis.getEffectiveFont(),
isLabelSameOrientation(axis));
if (maxDensity < density) {
maxDensity = density;
}
}
return maxDensity;
}
/**
* Calculate label density by the given values. density > 1 means labels overlapped.
*
* @param nxf
* @param axisLength
* @param tickValues
* @param labels
* @param labelFont
* @param labelSameOrientation
* @return
*/
private double getLabelsDensity(NormalTransform nxf, double axisLength, Object tickValues, MathElement[] labels,
Font labelFont, boolean labelSameOrientation) {
if (Array.getLength(tickValues) == 0) {
return 0;
}
Dimension2D[] labelsSize = AxisImpl.getLabelsPaperSize(labels, labelFont);
/* the space between 2 neighbor labels */
double blankWidth = labelsSize[0].getHeight() / 2;
/* Note: when scale is very small, deltaD will be zero */
double maxDensity = 0;
double ticA = transTickToPaper(nxf, axisLength, Array.getDouble(tickValues, 0));
if (labelSameOrientation) {
for (int i = 1; i < labelsSize.length; i++) {
double ticB = transTickToPaper(nxf, axisLength, Array.getDouble(tickValues, i * labelInterval));
double deltaD = Math.abs(ticB - ticA);
double desity = (labelsSize[i - 1].getWidth() / 2 + labelsSize[i].getWidth() / 2 + blankWidth) / deltaD;
if (maxDensity < desity) {
maxDensity = desity;
}
ticA = ticB;
}
} else {
for (int i = 1; i < labelsSize.length; i++) {
double ticB = transTickToPaper(nxf, axisLength, Array.getDouble(tickValues, i * labelInterval));
double deltaD = Math.abs(ticB - ticA);
double desity = (labelsSize[i - 1].getHeight() / 2 + labelsSize[i].getHeight() / 2 + blankWidth)
/ deltaD;
if (maxDensity < desity) {
maxDensity = desity;
}
ticA = ticB;
}
}
return maxDensity;
}
/**
* Transform tick value to paper value on this axis.
*
* @param the
* tick value
* @return paper value
*/
private double transTickToPaper(NormalTransform nxf, double axisLength, double tickValue) {
double uv;
if (tickTransform != null) {
uv = tickTransform.transformTick2User(tickValue);
} else {
uv = tickValue;
}
return nxf.convToNR(uv) * axisLength;
}
/**
* Calculate the canonical values of the given values. The canonical values will be used to produce labels.
*
* @param values
* the tick values
* @return the canonical values
*/
private Object getCanonicalValues(Object values) {
if (values instanceof double[]) {
double[] result = new double[Array.getLength(values)];
double[] array = (double[]) values;
for (int i = 0; i < array.length; i++) {
result[i] = getCanonicalValue(array[i]);
}
return result;
}
if (values instanceof long[]) {
long[] result = new long[Array.getLength(values)];
long[] array = (long[]) values;
for (int i = 0; i < array.length; i++) {
result[i] = getCanonicalValue(array[i]);
}
return result;
}
return null;
}
private double getCanonicalValue(double d) {
if (circularRange == null) {
return d;
}
double uv;
if (tickTransform == null) {
uv = d;
} else {
uv = tickTransform.transformTick2User(d);
}
double canon = uv % circularRange.getSpan() + circularRange.getMin();
if (tickTransform == null) {
return canon;
} else {
return tickTransform.transformUser2Tick(canon);
}
}
private long getCanonicalValue(long d) {
if (circularRange == null) {
return d;
}
double uv;
if (tickTransform == null) {
uv = d;
} else {
uv = tickTransform.transformTick2User(d);
}
double canon = uv % circularRange.getSpan() + circularRange.getMin();
if (tickTransform == null) {
return (long) canon;
} else {
return (long) tickTransform.transformUser2Tick(canon);
}
}
public Range expandRangeToTick(TransformType txfType, Range range) {
RangeAdvisor rav = (RangeAdvisor) tickCalculator;
rav.setRange(range);
if (autoValues) {
if (autoInterval) {
if (autoAdjustNumber) {
expandRangeToAutoAdjustedTick(txfType, range);
} else {
rav.expandRangeByTickNumber(tickNumber);
}
} else {
rav.expandRangeByTickInterval(interval);
}
}
return rav.getRange();
}
/**
* expand range on autoAdjustNumber axis.
*/
private Range expandRangeToAutoAdjustedTick(TransformType txfType, Range range) {
RangeAdvisor rav = (RangeAdvisor) tickCalculator;
int tickNumber = this.tickNumber;
while (true) {
rav.setRange(range);
rav.expandRangeByTickNumber(tickNumber);
Range r = rav.getRange();
tickCalculator.calcValuesByTickInterval(rav.getInterval(), 0, 0);
Object values = tickCalculator.getValues();
Format labelTextFormat;
String labelFormat;
if (autoLabelFormat) {
labelTextFormat = tickCalculator.calcLabelTextFormat(getCanonicalValues(values));
labelFormat = tickCalculator.calcLabelFormatString(getCanonicalValues(values));
} else {
labelTextFormat = this.labelTextFormat;
labelFormat = this.labelFormat;
}
MathElement[] autoLabels = calcAutoLabels(getCanonicalValues(values), labelTextFormat, labelFormat);
MathElement[] labels = calcLabels(null, autoLabels, labelInterval);
NormalTransform trf = txfType.createNormalTransform(r);
// calculate max desity
double maxDensity = getMaxLabelsDensity(trf, values, labels);
if (maxDensity > 1) {
/* fast approximating, the next tick we should try */
tickNumber = Math.min(tickNumber, Array.getLength(values)) - 1;
if (tickNumber >= AUTO_TICKS_MIN) {
continue;
}
}
return r;
}
}
@Override
public AxisTickManagerImpl copyStructure(Map orig2copyMap) {
AxisTickManagerImpl result = null;
try {
result = (AxisTickManagerImpl) this.clone();
} catch (CloneNotSupportedException e) {
}
result.axes = new ArrayList();
result.axisStatusMap = new HashMap();
if (orig2copyMap != null) {
orig2copyMap.put(this, result);
}
// copy or link range manager
AxisTransformEx axisTransformCopy = (AxisTransformEx) orig2copyMap.get(axisTransform);
if (axisTransformCopy == null) {
axisTransformCopy = (AxisTransformEx) axisTransform.copyStructure(orig2copyMap);
}
result.axisTransform = axisTransformCopy;
axisTransformCopy.addTickManager(result);
return result;
}
}
Ads help maintain this website.