Search code examples
javalabeljfreechart

Detecting overlapping ticklabels


Overlapping labels

As you can see, when the range axis is spanning over a large area, and the ticklabels are too close together, they start overlapping. I would like to implement some kind of logic to detect if they are overlapping, and if so, just show a preferred one (or as many as will fit). I am a bit unsure of how to accomplish this, however.

The logic would be something like this:

  1. Prioritize each label after importance
  2. Get hold of the Java2d coordinates of each tick label (x,y,width,height)
  3. If the area of a label is overlapping with another, just show the most important one

But which methods are available to do this? From the API docs I bought I found this method

public double valueToJava2D(double value, Rectangle2D area, RectangleEdge edge);

But what is this area and edge I need to supply the function with? And how would I get a hold of the width and height if a ticklabel? And is this the right way to go about it, or is there a better way that will lead to success?


Solution

  • I posted the question on the JFree forums (as trashgod found) and got a few nice hints there. The final code I posted there that solved my problem was as follows:

    @SuppressWarnings({"RawUseOfParameterizedType", "serial"})
    private static class CustomTickMarkNumberAxis extends NumberAxis {
        private double[] customTicksSortedOnImportance;
    
        /**
         * A list ticks sorted after importance. In case of overlapping,
         * a higher ranked tick will trump a lower ranked tick, thus <i>not showing</i>
         * the lower ranked (overlapping) tick.
         * Other lower ranked ticks might still be shown, if they are not overlapping
         */
        public CustomTickMarkNumberAxis(double[] customTicksSortedOnImportance) {
            this.customTicksSortedOnImportance = customTicksSortedOnImportance;
        }
    
        protected List<NumberTick> refreshTicksVertical(Graphics2D g2, Rectangle2D dataArea, RectangleEdge edge) {
            List<NumberTick> result = new java.util.ArrayList<NumberTick>();
    
            //(... snip ...)
    
            Area tickLabelArea = new Area(); /** Used for overlap detection */
            for (int i = 0; i < customTicksSortedOnImportance.length; i++) {
                // The next lines of code are different from NumberAxis
                double currentTickValue = customTicksSortedOnImportance[i];
                currentTickValue = NumberUtil.round(currentTickValue, precision);
    
                //(... snip ...)
    
                /* Overlapping logic here */
                NumberTick numberTick = new NumberTick(currentTickValue, tickLabel, anchor, rotationAnchor, angle);
                Rectangle2D labelBounds = getTickBounds(numberTick, g2);
                double java2dValue = valueToJava2D(currentTickValue, g2.getClipBounds(), edge);
                labelBounds.setRect(labelBounds.getX(), java2dValue, labelBounds.getWidth(), labelBounds.getHeight());
    
                if (!tickLabelIsOverlapping(tickLabelArea, labelBounds)) {
                    result.add(numberTick);
                    tickLabelArea.add(new Area(labelBounds));
                }
    
                //(... snip ...)
            }
            return result;
        }
    
        private boolean tickLabelIsOverlapping(Area area, Rectangle2D rectangle) {
            return area.intersects(rectangle);
        }
    
        private Rectangle2D getTickBounds(NumberTick numberTick, Graphics2D g2) {
            FontMetrics fm = g2.getFontMetrics(getTickLabelFont());
            return TextUtilities.getTextBounds(numberTick.getText(), g2, fm);
        }
    }