diff --git a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/Axis.java b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/Axis.java index 1deb8df425321c6e1af69f9e2904c5364a14d011..1cdfa4362e22ac8c86657326ade35a075826f1b6 100644 --- a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/Axis.java +++ b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/Axis.java @@ -270,6 +270,8 @@ public class Axis implements ChartPart { if (getChartPainter().getStyleManager().isXAxisTicksVisible()) { // get some real tick labels + // System.out.println("XAxisHeightHint"); + // System.out.println("workingSpace: " + workingSpace); AxisTickCalculator axisTickCalculator = axisTick.getAxisTickCalculator(workingSpace); String sampleLabel = " "; // find the longest String in all the labels @@ -279,11 +281,13 @@ public class Axis implements ChartPart { } } + // get the height of the label including rotation TextLayout textLayout = new TextLayout(sampleLabel, getChartPainter().getStyleManager().getAxisTickLabelsFont(), new FontRenderContext(null, true, false)); AffineTransform rot = getChartPainter().getStyleManager().getXAxisLabelRotation() == 0 ? null : AffineTransform.getRotateInstance(-1 * Math.toRadians(getChartPainter().getStyleManager() .getXAxisLabelRotation())); Shape shape = textLayout.getOutline(rot); Rectangle2D rectangle = shape.getBounds(); + axisTickLabelsHeight = rectangle.getHeight() + getChartPainter().getStyleManager().getAxisTickPadding() + getChartPainter().getStyleManager().getAxisTickMarkLength(); } return titleHeight + axisTickLabelsHeight; diff --git a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTick.java b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTick.java index ec0f3236942d18b49d25f4bd764f69762915f770..6331a8fd115b071e5e7aab4a36f3ddc331f91216 100644 --- a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTick.java +++ b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTick.java @@ -74,6 +74,8 @@ public class AxisTick implements ChartPart { // System.out.println("workingspace= " + workingSpace); } + System.out.println("AxisTick: " + axis.getDirection()); + // System.out.println("workingSpace: " + workingSpace); axisTickCalculator = getAxisTickCalculator(workingSpace); if (axis.getDirection() == Axis.Direction.Y && getChartPainter().getStyleManager().isYAxisTicksVisible()) { @@ -83,15 +85,15 @@ public class AxisTick implements ChartPart { bounds = new Rectangle2D.Double( - axisTickLabels.getBounds().getX(), + axisTickLabels.getBounds().getX(), - axisTickLabels.getBounds().getY(), + axisTickLabels.getBounds().getY(), - axisTickLabels.getBounds().getWidth() + getChartPainter().getStyleManager().getAxisTickPadding() + axisTickMarks.getBounds().getWidth(), + axisTickLabels.getBounds().getWidth() + getChartPainter().getStyleManager().getAxisTickPadding() + axisTickMarks.getBounds().getWidth(), - axisTickMarks.getBounds().getHeight() + axisTickMarks.getBounds().getHeight() - ); + ); // g.setColor(Color.red); // g.draw(bounds); @@ -102,9 +104,8 @@ public class AxisTick implements ChartPart { axisTickLabels.paint(g); axisTickMarks.paint(g); - bounds = - new Rectangle2D.Double(axisTickMarks.getBounds().getX(), axisTickMarks.getBounds().getY(), axisTickLabels.getBounds().getWidth(), axisTickMarks.getBounds().getHeight() - + getChartPainter().getStyleManager().getAxisTickPadding() + axisTickLabels.getBounds().getHeight()); + 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); diff --git a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTickCalculator.java b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTickCalculator.java index ad5f53bd9a3ece3f1fae06f88789b727590f1d39..8f5d5723ca996ea9a7d939c8b781dc1f17e52fee 100644 --- a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTickCalculator.java +++ b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTickCalculator.java @@ -15,6 +15,11 @@ */ package com.xeiam.xchart.internal.chartpart; +import java.awt.Shape; +import java.awt.font.FontRenderContext; +import java.awt.font.TextLayout; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; import java.util.LinkedList; import java.util.List; @@ -116,4 +121,37 @@ public abstract class AxisTickCalculator { return tickLabels; } + /** + * Given the generated tickLabels, will they fit side-by-side without overlapping each other and looking bad? Sometimes the given tickSpacingHint is simply too small. + * + * @param tickLabels + * @param tickSpacingHint + * @return + */ + boolean willLabelsFitInTickSpaceHint(List<String> tickLabels, int tickSpacingHint) { + + // Assume that for Y-Axis the ticks will all fit based on their tickSpace hint because the text is usually horizontal and "short". This more applies to the X-Axis. + if (this.axisDirection == Direction.Y) { + return true; + } + + String sampleLabel = " "; + // find the longest String in all the labels + for (int i = 0; i < tickLabels.size(); i++) { + if (tickLabels.get(i) != null && tickLabels.get(i).length() > sampleLabel.length()) { + sampleLabel = tickLabels.get(i); + } + } + + 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())); + Shape shape = textLayout.getOutline(rot); + Rectangle2D rectangle = shape.getBounds(); + double largestLabelWidth = rectangle.getWidth(); + // System.out.println("largestLabelWidth: " + largestLabelWidth); + // System.out.println("tickSpacingHint: " + tickSpacingHint); + + return (largestLabelWidth * 1.8 < tickSpacingHint); + + } } diff --git a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTickDateCalculator.java b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTickDateCalculator.java index 40b6d956e911a403f02e188758ccddbb559504bd..fbc7f0218f04a3f60cc7f14a04c8817e446fb6b3 100644 --- a/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTickDateCalculator.java +++ b/xchart/src/main/java/com/xeiam/xchart/internal/chartpart/AxisTickDateCalculator.java @@ -55,28 +55,42 @@ public class AxisTickDateCalculator extends AxisTickCalculator { // the span of the data long span = (long) Math.abs(maxValue - minValue); // in data space - long gridStepHint = (long) (span / tickSpace * styleManager.getXAxisTickMarkSpacingHint()); - - long timeUnit = dateFormatter.getTimeUnit(gridStepHint); - double gridStep = 0.0; - int[] steps = dateFormatter.getValidTickStepsMap().get(timeUnit); - for (int i = 0; i < steps.length - 1; i++) { - if (gridStepHint < (timeUnit * steps[i] + timeUnit * steps[i + 1]) / 2.0) { - gridStep = timeUnit * steps[i]; - break; + // 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; + do { + + System.out.println("calulating ticks..."); + tickLabels.clear(); + tickLocations.clear(); + tickSpacingHint += 10; + long gridStepHint = (long) (span / tickSpace * tickSpacingHint); + + long timeUnit = dateFormatter.getTimeUnit(gridStepHint); + double gridStep = 0.0; + int[] steps = dateFormatter.getValidTickStepsMap().get(timeUnit); + for (int i = 0; i < steps.length - 1; i++) { + if (gridStepHint < (timeUnit * steps[i] + timeUnit * steps[i + 1]) / 2.0) { + gridStep = timeUnit * steps[i]; + break; + } } - } - double firstPosition = getFirstPosition(gridStep); + // System.out.println("gridStep: " + gridStep); - // generate all tickLabels and tickLocations from the first to last position - for (double value = firstPosition; value <= maxValue + 2 * gridStep; value = value + gridStep) { + double firstPosition = getFirstPosition(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); - } + // 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); + } + } while (!willLabelsFitInTickSpaceHint(tickLabels, tickSpacingHint)); } }