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