Skip to content
Snippets Groups Projects
Commit c9c78bad authored by Tim Molter's avatar Tim Molter
Browse files

"smart" tick spacing hint generation to provent tick label crowding and overlap for decimal axes

parent 57e58b4d
No related branches found
No related tags found
No related merge requests found
......@@ -36,6 +36,7 @@ import com.xeiam.xchart.demo.charts.ExampleChart;
* <li>Using ChartBuilder to Make a Chart
* <li>List<Number> data sets
* <li>Setting Series Marker and Marker Color
* <li>Using a custom decimal pattern
*/
public class ScatterChart04 implements ExampleChart {
......@@ -55,7 +56,7 @@ public class ScatterChart04 implements ExampleChart {
List<Double> yData = new ArrayList<Double>();
List<Double> errorBars = new ArrayList<Double>();
for (int i = 0; i <= size; i++) {
xData.add(((double) i) / 100000000);
xData.add(((double) i) / 1000000);
yData.add(10 * Math.exp(-i));
errorBars.add(Math.random() + .3);
}
......@@ -67,6 +68,7 @@ public class ScatterChart04 implements ExampleChart {
chart.getStyleManager().setChartTitleVisible(false);
chart.getStyleManager().setLegendVisible(false);
chart.getStyleManager().setAxisTitlesVisible(false);
chart.getStyleManager().setXAxisDecimalPattern("0.0000000");
// Series
Series series = chart.addSeries("10^(-x)", xData, yData, errorBars);
......
......@@ -15,6 +15,7 @@
*/
package com.xeiam.xchart.internal.chartpart;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.util.List;
......@@ -74,7 +75,7 @@ public class AxisTick implements ChartPart {
// System.out.println("workingspace= " + workingSpace);
}
System.out.println("AxisTick: " + axis.getDirection());
// System.out.println("AxisTick: " + axis.getDirection());
// System.out.println("workingSpace: " + workingSpace);
axisTickCalculator = getAxisTickCalculator(workingSpace);
......@@ -95,8 +96,8 @@ public class AxisTick implements ChartPart {
);
// g.setColor(Color.red);
// g.draw(bounds);
g.setColor(Color.red);
g.draw(bounds);
}
else if (axis.getDirection() == Axis.Direction.X && getChartPainter().getStyleManager().isXAxisTicksVisible()) {
......@@ -106,8 +107,9 @@ public class AxisTick implements ChartPart {
bounds = new Rectangle2D.Double(axisTickMarks.getBounds().getX(), axisTickMarks.getBounds().getY(), axisTickLabels.getBounds().getWidth(), axisTickMarks.getBounds().getHeight()
+ getChartPainter().getStyleManager().getAxisTickPadding() + axisTickLabels.getBounds().getHeight());
// g.setColor(Color.red);
// g.draw(bounds);
g.setColor(Color.red);
g.draw(bounds);
}
......
......@@ -142,6 +142,7 @@ public abstract class AxisTickCalculator {
sampleLabel = tickLabels.get(i);
}
}
// System.out.println("longestLabel: " + sampleLabel);
TextLayout textLayout = new TextLayout(sampleLabel, styleManager.getAxisTickLabelsFont(), new FontRenderContext(null, true, false));
AffineTransform rot = styleManager.getXAxisLabelRotation() == 0 ? null : AffineTransform.getRotateInstance(-1 * Math.toRadians(styleManager.getXAxisLabelRotation()));
......@@ -151,7 +152,7 @@ public abstract class AxisTickCalculator {
// System.out.println("largestLabelWidth: " + largestLabelWidth);
// System.out.println("tickSpacingHint: " + tickSpacingHint);
return (largestLabelWidth * 1.8 < tickSpacingHint);
return (largestLabelWidth * 1.6 < tickSpacingHint);
}
}
......@@ -58,14 +58,13 @@ public class AxisTickDateCalculator extends AxisTickCalculator {
// Can tickSpacingHint be intelligently calculated by looking at the label data?
// YES. Generate the labels first, see if they "look" OK and reiterate with an increased tickSpacingHint
// TODO apply this to other Axis types including bar charts
// TODO only do this for the X-Axis
int tickSpacingHint = styleManager.getXAxisTickMarkSpacingHint() - 10;
int tickSpacingHint = styleManager.getXAxisTickMarkSpacingHint() - 5;
do {
System.out.println("calulating ticks...");
// System.out.println("calculating ticks...");
tickLabels.clear();
tickLocations.clear();
tickSpacingHint += 10;
tickSpacingHint += 5;
long gridStepHint = (long) (span / tickSpace * tickSpacingHint);
long timeUnit = dateFormatter.getTimeUnit(gridStepHint);
......@@ -85,10 +84,13 @@ public class AxisTickDateCalculator extends AxisTickCalculator {
// generate all tickLabels and tickLocations from the first to last position
for (double value = firstPosition; value <= maxValue + 2 * gridStep; value = value + gridStep) {
tickLabels.add(dateFormatter.formatDate(value, timeUnit));
// here we convert tickPosition finally to plot space, i.e. pixels
double tickLabelPosition = margin + ((value - minValue) / (maxValue - minValue) * tickSpace);
tickLocations.add(tickLabelPosition);
if (value <= maxValue && value >= minValue) {
tickLabels.add(dateFormatter.formatDate(value, timeUnit));
// here we convert tickPosition finally to plot space, i.e. pixels
double tickLabelPosition = margin + ((value - minValue) / (maxValue - minValue) * tickSpace);
tickLocations.add(tickLabelPosition);
}
}
} while (!willLabelsFitInTickSpaceHint(tickLabels, tickSpacingHint));
}
......
......@@ -59,96 +59,96 @@ public class AxisTickNumericalCalculator extends AxisTickCalculator {
// tick space - a percentage of the working space available for ticks
double tickSpace = styleManager.getAxisTickSpacePercentage() * workingSpace; // in plot space
// where the tick should begin in the working space in pixels
double margin = Utils.getTickStartOffset(workingSpace, tickSpace); // in plot space double gridStep = getGridStepForDecimal(tickSpace);
BigDecimal gridStep = BigDecimal.valueOf(getNumericalGridStep(tickSpace));
// System.out.println("***gridStep: " + gridStep);
BigDecimal cleanedGridStep = gridStep.setScale(10, RoundingMode.HALF_UP).stripTrailingZeros(); // chop off any double imprecision
// System.out.println("cleanedGridStep: " + cleanedGridStep);
BigDecimal firstPosition = BigDecimal.valueOf(getFirstPosition(cleanedGridStep.doubleValue()));
// System.out.println("firstPosition: " + firstPosition); // chop off any double imprecision
BigDecimal cleanedFirstPosition = firstPosition.setScale(10, RoundingMode.HALF_UP).stripTrailingZeros(); // chop off any double imprecision
// System.out.println("cleanedFirstPosition: " + cleanedFirstPosition);
// generate all tickLabels and tickLocations from the first to last position
for (BigDecimal value = cleanedFirstPosition; value.compareTo(BigDecimal.valueOf(maxValue + 2 * cleanedGridStep.doubleValue())) < 0; value = value.add(cleanedGridStep)) {
// System.out.println(value);
String tickLabel = numberFormatter.formatNumber(value, minValue, maxValue, axisDirection);
// System.out.println(tickLabel);
tickLabels.add(tickLabel);
// here we convert tickPosition finally to plot space, i.e. pixels
double tickLabelPosition = margin + ((value.doubleValue() - minValue) / (maxValue - minValue) * tickSpace);
tickLocations.add(tickLabelPosition);
}
}
/**
* Determine the grid step for the data set given the space in pixels allocated for the axis
*
* @param tickSpace in plot space
* @return
*/
private double getNumericalGridStep(double tickSpace) {
// this prevents an infinite loop when the plot gets sized really small.
if (tickSpace < 10) {
return 1.0;
return;
}
// where the tick should begin in the working space in pixels
double margin = Utils.getTickStartOffset(workingSpace, tickSpace); // in plot space double gridStep = getGridStepForDecimal(tickSpace);
// the span of the data
double span = Math.abs(maxValue - minValue); // in data space
int tickMarkSpaceHint = (axisDirection == Direction.X ? styleManager.getXAxisTickMarkSpacingHint() : styleManager.getYAxisTickMarkSpacingHint());
//////////////////////////
int tickSpacingHint = (axisDirection == Direction.X ? styleManager.getXAxisTickMarkSpacingHint() : styleManager.getYAxisTickMarkSpacingHint()) - 5;
// for very short plots, squeeze some more ticks in than normal
// for very short plots, squeeze some more ticks in than normal into the Y-Axis
if (axisDirection == Direction.Y && tickSpace < 160) {
tickMarkSpaceHint = 25;
tickSpacingHint = 25 - 5;
}
do {
// System.out.println("calculating ticks...");
tickLabels.clear();
tickLocations.clear();
tickSpacingHint += 5;
double gridStepHint = span / tickSpace * tickSpacingHint;
// gridStepHint --> significand * 10 ** exponent
// e.g. 724.1 --> 7.241 * 10 ** 2
double significand = gridStepHint;
int exponent = 0;
if (significand == 0) {
exponent = 1;
}
else if (significand < 1) {
while (significand < 1) {
significand *= 10.0;
exponent--;
}
}
else {
while (significand >= 10 || significand == Double.NEGATIVE_INFINITY) {
significand /= 10.0;
exponent++;
}
}
double gridStepHint = span / tickSpace * tickMarkSpaceHint;
// gridStepHint --> significand * 10 ** exponent
// e.g. 724.1 --> 7.241 * 10 ** 2
double significand = gridStepHint;
int exponent = 0;
if (significand == 0) {
exponent = 1;
}
else if (significand < 1) {
while (significand < 1) {
significand *= 10.0;
exponent--;
// calculate the grid step with hint.
double gridStep;
if (significand > 7.5) {
// gridStep = 10.0 * 10 ** exponent
gridStep = 10.0 * Utils.pow(10, exponent);
}
}
else {
while (significand >= 10 || significand == Double.NEGATIVE_INFINITY) {
significand /= 10.0;
exponent++;
else if (significand > 3.5) {
// gridStep = 5.0 * 10 ** exponent
gridStep = 5.0 * Utils.pow(10, exponent);
}
}
else if (significand > 1.5) {
// gridStep = 2.0 * 10 ** exponent
gridStep = 2.0 * Utils.pow(10, exponent);
}
else {
// gridStep = 1.0 * 10 ** exponent
gridStep = Utils.pow(10, exponent);
}
//////////////////////////
BigDecimal gridStepBigDecimal = BigDecimal.valueOf(gridStep);
// System.out.println("***gridStep: " + gridStep);
BigDecimal cleanedGridStep = gridStepBigDecimal.setScale(10, RoundingMode.HALF_UP).stripTrailingZeros(); // chop off any double imprecision
// System.out.println("cleanedGridStep: " + cleanedGridStep);
BigDecimal firstPosition = BigDecimal.valueOf(getFirstPosition(cleanedGridStep.doubleValue()));
// System.out.println("firstPosition: " + firstPosition); // chop off any double imprecision
BigDecimal cleanedFirstPosition = firstPosition.setScale(10, RoundingMode.HALF_UP).stripTrailingZeros(); // chop off any double imprecision
// System.out.println("cleanedFirstPosition: " + cleanedFirstPosition);
// generate all tickLabels and tickLocations from the first to last position
for (BigDecimal value = cleanedFirstPosition; value.compareTo(BigDecimal.valueOf(maxValue + 2 * cleanedGridStep.doubleValue())) < 0; value = value.add(cleanedGridStep)) {
if (value.compareTo(BigDecimal.valueOf(maxValue)) <= 0 && value.compareTo(BigDecimal.valueOf(minValue)) >= 0) {
// System.out.println(value);
String tickLabel = numberFormatter.formatNumber(value, minValue, maxValue, axisDirection);
// System.out.println(tickLabel);
tickLabels.add(tickLabel);
// here we convert tickPosition finally to plot space, i.e. pixels
double tickLabelPosition = margin + ((value.doubleValue() - minValue) / (maxValue - minValue) * tickSpace);
tickLocations.add(tickLabelPosition);
}
}
} while (!willLabelsFitInTickSpaceHint(tickLabels, tickSpacingHint));
// calculate the grid step with hint.
double gridStep;
if (significand > 7.5) {
// gridStep = 10.0 * 10 ** exponent
gridStep = 10.0 * Utils.pow(10, exponent);
}
else if (significand > 3.5) {
// gridStep = 5.0 * 10 ** exponent
gridStep = 5.0 * Utils.pow(10, exponent);
}
else if (significand > 1.5) {
// gridStep = 2.0 * 10 ** exponent
gridStep = 2.0 * Utils.pow(10, exponent);
}
else {
// gridStep = 1.0 * 10 ** exponent
gridStep = Utils.pow(10, exponent);
}
return gridStep;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment