JFreeCharts轴:勾选标签对齐

时间:2012-10-16 13:08:36

标签: java alignment jfreechart

我正在使用jFreeChart 1.0.14。我有一个横向DateAxis的图表。我试图将刻度标签居中,以便它们在两个后续刻度内。

JFreeChart以这种方式对齐常见情况下的刻度标签:

----+------------+------------+----
   Mon          Tue          Wed

alt text

但我想以这种方式对齐刻度标签:

----+------------+------------+------------+----
         Mon          Tue          Wed

alt text

图像上的刻度标签通过编辑原始图像在图形编辑器中手动对齐。它不是jFreeChart库的输出。

有什么办法,怎么通过DateAxis的API来做?任何帮助表示赞赏...... :)

非常感谢任何帮助或想法。 Honza(sporak)

1 个答案:

答案 0 :(得分:7)

由于没有人帮助我任何技巧,我试图覆盖DateAxis以获得带有刻度标签对齐的Axis。我将其命名为“AlignedDateAxis”。它扩展DateAxis并仅覆盖用于刻度标签呈现的方法。它可以通过两种方式呈现刻度标签:以标准方式将标签置于刻度线下,并以所需方式将标签置于间隔中间。

因为我对JFreeCharts库了解不多,所以对我来说一些类和方法并不容易。我希望它在所有常见情况下都能正常工作。 我的课程只包含我母语的javadoc和评论,所以我不会在这里发布完毕。

package x.y.z;

import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.util.Date;
import java.util.List;

import org.jfree.chart.axis.AxisState;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.axis.DateTick;
import org.jfree.chart.axis.DateTickUnit;
import org.jfree.chart.axis.TickType;
import org.jfree.chart.axis.ValueTick;
import org.jfree.text.TextUtilities;
import org.jfree.ui.RectangleEdge;

/**
 * Extension of DateAxis for jFreeChart graphs that can render tick labels in two ways: 
 *   1) labels under ticks (common way, same as DateAxis)
 *   2) labels in the middle of interval (new feature)
 * @author Honza Spurny, Czech Republic
 * @version 1.0
 */
public class AlignedDateAxis extends DateAxis {

    /**
     * Tick labels alignment setting.
     */
    private TickLabelPosition tickLabelPosition = TickLabelPosition.DEFAULT_VALUE;

    /**
     * Value for interface Serializable.
     */
    static private final long serialVersionUID = 1;

    // ***********************
    // ***      Enums      ***
    // ***********************

    /**
     * Tick label alignment modes.
     */
    public enum TickLabelPosition {
        /**
         * Tick label is rendered under/next by own tick. 
         * (common rendering same as in DateAxis)
         */
        INTERVAL_START,
        /**
         * Tick label is placed in the middle of interval
         * (between two subsequent ticks) 
         */
        INTERVAL_MIDDLE;

        static public final TickLabelPosition DEFAULT_VALUE = INTERVAL_START;
    }

    // ******************************
    // ***      Constructors      ***
    // ******************************

    /**
     * Default constructor.
     */
    public AlignedDateAxis() {
        super();
    }

    /**
     * Constructor.
     * @param tickLabelPos tick label alignment mode setting for this axis.
     */
    public AlignedDateAxis(TickLabelPosition tickLabelPos) {
        this();
        this.setTickLabelPosition(tickLabelPos);
    }

    // *********************************
    // ***      GET/SET methods      ***
    // *********************************

    public TickLabelPosition getTickLabelPosition() {
        return this.tickLabelPosition;
    }

    public void setTickLabelPosition(TickLabelPosition value) {
        this.tickLabelPosition = value;
    }

    // *******************************************************
    // ***      Overrided methods for label rendering      ***
    // *******************************************************

    /**
     * Auxiliary method to calculate tick label position of given tick.
     * @param tick tick we need calculate label position for (DateTick is expected)
     * @param cursor the cursor
     * @param dataArea area to draw the ticks and labels in
     * @param edge edge of dataArea
     * @return Returns coordinates [x,y] where label should be placed.
     */
    @Override
    protected float[] calculateAnchorPoint(ValueTick tick, double cursor, Rectangle2D dataArea, RectangleEdge edge) {
        float[] resultAnchor = super.calculateAnchorPoint(tick, cursor, dataArea, edge);

        // Time of tick
        Date tickDate = (tick instanceof DateTick) ? 
                        ((DateTick) tick).getDate() : 
                        new Date((long)tick.getValue());

        // Tick label shift.
        // (for INTERVAL_START it is 0, for INTERVAL_MIDDLE it is calculated)
        double labelShift;

        switch (this.getTickLabelPosition()) {
            case INTERVAL_MIDDLE:
                // Getting next tick value...
                DateTickUnit unit = this.getTickUnit();
                Date nextTickDate = unit.addToDate(tickDate, this.getTimeZone());
                double nextTickVal = this.valueToJava2D(nextTickDate.getTime(), dataArea, edge);

                // Shifting label in between ticks...
                labelShift = (nextTickVal - resultAnchor[0]) / 2;
                break;

            case INTERVAL_START:
            default:
                labelShift = 0;
                break;
        }

        // Edge defines which coordinate is shifted.
        if (RectangleEdge.isTopOrBottom(edge)) {
            resultAnchor[0] += labelShift;
        } else if (RectangleEdge.isLeftOrRight(edge)) {
            resultAnchor[1] += labelShift;
        }

        return resultAnchor;
    }

    /**
     * Renders this axis with ticks and labels.
     * @param g2 graphics to draw the axis in.
     * @param cursor the cursor
     * @param plotArea area to draw the chart in
     * @param dataArea area to draw this axis in
     * @param edge edge of dataArea
     * @return Returns state of axis.
     */
    @Override
    protected AxisState drawTickMarksAndLabels(Graphics2D g2, double cursor, Rectangle2D plotArea, Rectangle2D dataArea, RectangleEdge edge) {
        AxisState state = new AxisState(cursor);
        if (this.isAxisLineVisible()) this.drawAxisLine(g2, cursor, dataArea, edge);

        List<DateTick> ticks = this.refreshTicks(g2, state, dataArea, edge);
        state.setTicks(ticks);
        g2.setFont(this.getTickLabelFont());

        for (DateTick tick: ticks) {        
            if (this.isTickLabelsVisible()) {
                g2.setPaint(this.getTickLabelPaint());
                float anchorPoint[] = this.calculateAnchorPoint(tick, cursor, dataArea, edge);

                // TextUtilities.drawRotatedString(tick.getText(), g2, anchorPoint[0], anchorPoint[1], tick.getTextAnchor(), tick.getAngle(), tick.getRotationAnchor());
                // Commented code above is original code from DateAxis.

                // ---[Override]---
                // Position of tick label is shifted so it can point outside the dataArea.
                // We have to check whether the tick label on this position is drawable into the given area.                            
                Shape labelBounds = TextUtilities.calculateRotatedStringBounds(tick.getText(), g2, anchorPoint[0], anchorPoint[1], tick.getTextAnchor(), tick.getAngle(), tick.getRotationAnchor());
                double labelEdge = (RectangleEdge.isTopOrBottom(edge)) ? (labelBounds.getBounds2D().getMaxX()) : (labelBounds.getBounds2D().getMaxY());
                double dataAreaBound = (RectangleEdge.isTopOrBottom(edge)) ? (dataArea.getMaxX()) : (dataArea.getMaxY());

                // Magic constant 5: tick label can be rendered although it exceeds area edge for max. 5px
                // (it still looks good - visualy tested :-)
                if ((labelEdge - 5) <= dataAreaBound) {
                    TextUtilities.drawRotatedString(tick.getText(), g2, anchorPoint[0], anchorPoint[1], tick.getTextAnchor(), tick.getAngle(), tick.getRotationAnchor());
                }
                // ---[/Override]---
            }

            if ( (this.isTickMarksVisible() && tick.getTickType().equals(TickType.MAJOR)) || (this.isMinorTickMarksVisible() && tick.getTickType().equals(TickType.MINOR)) ) {
                double ol = tick.getTickType().equals(TickType.MINOR) ? this.getMinorTickMarkOutsideLength() : this.getTickMarkOutsideLength();
                double il = tick.getTickType().equals(TickType.MINOR) ? this.getMinorTickMarkInsideLength() : this.getTickMarkInsideLength();
                float tickVal = (float)this.valueToJava2D(tick.getValue(), dataArea, edge);
                Line2D mark = null;
                g2.setStroke(this.getTickMarkStroke());
                g2.setPaint(this.getTickMarkPaint());
                if(edge == RectangleEdge.LEFT) {
                    mark = new Line2D.Double(cursor - ol, tickVal, cursor + il, tickVal);
                } else if(edge == RectangleEdge.RIGHT) {
                    mark = new Line2D.Double(cursor + ol, tickVal, cursor - il, tickVal);
                } else if(edge == RectangleEdge.TOP) {
                    mark = new Line2D.Double(tickVal, cursor - ol, tickVal, cursor + il);
                } else if(edge == RectangleEdge.BOTTOM) {
                    mark = new Line2D.Double(tickVal, cursor + ol, tickVal, cursor - il);
                }
                g2.draw(mark);
            }
        }

        if (this. isTickLabelsVisible()) {
            double used = 0.0;            
                if (edge == RectangleEdge.LEFT) {
                    used += this.findMaximumTickLabelWidth(ticks, g2, plotArea, this.isVerticalTickLabels());
                    state.cursorLeft(used);
                } else if (edge == RectangleEdge.RIGHT) {
                    used = this.findMaximumTickLabelWidth(ticks, g2, plotArea, this.isVerticalTickLabels());
                    state.cursorRight(used);
                } else if (edge == RectangleEdge.TOP) {
                    used = this.findMaximumTickLabelHeight(ticks, g2, plotArea, this.isVerticalTickLabels());
                    state.cursorUp(used);
                } else if (edge == RectangleEdge.BOTTOM) {
                    used = this.findMaximumTickLabelHeight(ticks, g2, plotArea, this.isVerticalTickLabels());
                    state.cursorDown(used);
                }
        }

        return state;
    }
}

这个轴类(AlignedDateAxis)对我来说很好,我在我的项目中使用它。

Honza(sporak)