Code for multiline Axis label / text

A discussion forum for JFreeChart (a 2D chart library for the Java platform).
Locked
tomkieffer
Posts: 24
Joined: Wed May 16, 2007 8:20 am

Code for multiline Axis label / text

Post by tomkieffer » Wed Jan 16, 2008 2:05 pm

Hi,

to generate multiline axis labels I changed the following two methods (getTextBounds, drawRotatedString) in org.jfree.text.TextUtilities as it seemed more comfortable to me than trying to change the axis class to use textblocks (although this was a lot of work, too, despite the small result). "\n" in the axis label works as a line separator.

I did not check the result of these changement with any other thing than JFreeChart, so pay attention. The text is rotated as a block, not as single lines.
For the rest, feel free to use it and mail the problems, if you encounter any.

Code: Select all

/**
     * A utility method for drawing rotated text.
     * <P>
     * A common rotation is -Math.PI/2 which draws text 'vertically' (with the 
     * top of the characters on the left).
     * tk:
     * This method has been modified to draw multiline strings.
     * (textX, textY) is the left point on the baseline of the downmost line of text
     *
     * @param text  the text.
     * @param g2  the graphics device.
     * @param textX  the x-coordinate for the text (before rotation).
     * @param textY  the y-coordinate for the text (before rotation).
     * @param angle  the angle of the (clockwise) rotation (in radians).
     * @param rotateX  the point about which the text is rotated.
     * @param rotateY  the point about which the text is rotated.
     */
    public static void drawRotatedString(final String text, final Graphics2D g2,
            final float textX, final float textY, final double angle,
            final float rotateX, final float rotateY) {

        if ((text == null) || (text.equals(""))) {
            return;
        }
        
        float textHeight = g2.getFontMetrics().getLineMetrics(text, g2).getHeight();
        String[] lineBreakText = text.split("\n");
        //an offset is needed to shift the (not rotated yet) text in -y direction
        //the original y value (may be transformed by derive TextBoundsAnchorOffsets)
        //is the left point on the baseline of the downmost line of text!!
        float changedTextY = textY;
        if (lineBreakText.length>1) {
            float multilineOffset = ((lineBreakText.length-1) * textHeight);  
            changedTextY -= multilineOffset;
        }
        
        final AffineTransform saved = g2.getTransform();
        
        // apply the rotation...
        final AffineTransform rotate = AffineTransform.getRotateInstance(
                angle, rotateX, rotateY);
        g2.transform(rotate);
        
        for (int i = 0; i < lineBreakText.length; i++) {
            if (useDrawRotatedStringWorkaround) {
                // workaround for JDC bug ID 4312117 and others...
                    final TextLayout tl = new TextLayout(lineBreakText[i], g2.getFont(), 
                    g2.getFontRenderContext());
                    tl.draw(g2, textX, (changedTextY + i*textHeight));
            } 
            else {
                // replaces this code...
                //here the text is drawn. Drawing starts at the offset and goes from top to bottom
                g2.drawString(lineBreakText[i], textX, changedTextY + i*textHeight);
            }
        }
        
        g2.setTransform(saved);

    }

Code: Select all

/**
     * Returns the bounds for the specified text.
     * Method is modified for multiline text, the separator is "\n".
     * 
     * @param text  the text (<code>null</code> permitted).
     * @param g2  the graphics context (not <code>null</code>).
     * @param fm  the font metrics (not <code>null</code>).
     * 
     * @return The text bounds (<code>null</code> if the <code>text</code> 
     *         argument is <code>null</code>).
     */
    public static Rectangle2D getTextBounds(final String text, 
            final Graphics2D g2, final FontMetrics fm) {
        
        Rectangle2D bounds;
        String[] multiLineText = text.split("\n");
        double maxWidth;
        
        if (TextUtilities.useFontMetricsGetStringBounds) {
            
            bounds = fm.getStringBounds(text, g2);
            maxWidth = 0;
            
            for (int i = 0; i < multiLineText.length; i++) {
                //the width must correspond to the length of the longest line  to stay
                //aligned centered as fm.getStringBounds() doesn't understand \n
                //(or else the String shifts on the y-Axis as it gets longer).
                if ( fm.getStringBounds(multiLineText[i], g2).getWidth() > maxWidth ) {
                    maxWidth = fm.stringWidth(multiLineText[i]);
                }
            }
            
            // getStringBounds() can return incorrect height for some Unicode
            // characters...see bug parade 6183356, let's replace it with 
            // something correct
            LineMetrics lm = fm.getFont().getLineMetrics(text,
                    g2.getFontRenderContext());
            
            bounds.setRect(bounds.getX(), bounds.getY(),
                    maxWidth, lm.getHeight()*(multiLineText.length));
            
            //System.out.println("bounds: " + bounds);
        }
        
        else {

            maxWidth = 0;
            
            for (int i = 0; i < multiLineText.length; i++) {
                //see above
                if ( fm.stringWidth(multiLineText[i]) > maxWidth ) {
                    maxWidth = fm.stringWidth(multiLineText[i]);
                }
            }

            final double height = fm.getHeight();
            if (logger.isDebugEnabled()) {
                logger.debug("Height = " + height);
            }
                //font metrics are baseline relative
                bounds = new Rectangle2D.Double(0.0, -fm.getAscent(), 
                        maxWidth, height*(multiLineText.length));
        }
        
        
        return bounds;
    }

Locked