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

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

parent fd0ca5df
No related branches found
No related tags found
No related merge requests found
......@@ -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;
......
......@@ -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);
......
......@@ -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);
}
}
......@@ -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));
}
}
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