【问题标题】:JFreeCharts axis : tick labels alignmentJFreeCharts 轴:刻度标签对齐
【发布时间】:2012-10-06 14:18:41
【问题描述】:

我正在使用 jFreeChart 1.0.14。我有一个水平图DateAxis。而且我正在尝试使刻度标签居中,以便它们位于随后的两个刻度内。

JFreeChart 通常以这种方式对齐刻度标签:

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

但我想这样对齐刻度标签:

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

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

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

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

【问题讨论】:

  • 是您在jfree.org/phpBB2/viewtopic.php?f=3&t=115716 中提出的问题,因为它听起来几乎相同吗? JFreeChart 项目负责人已经给出了答案,所以我认为没有人可以在这里给你更好的建议:)
  • @halex 你是对的,这是完全相同的问题。致 OP:David Gilbert 是 JFreeChart 背后的人,所以我建议在论坛上与他联系,如果他不能帮助你,没有人可以。 ;)
  • 如果PeriodAxis成功了,你可以answer your own question
  • @halex:是的,这与我在 JfreeChart 论坛上提出的问题相同。我读了大卫吉尔伯特的回答,但对我没有多大帮助。所以我在这里发布问题的原因是我希望这里有人可以帮助我,这里有人知道一些技巧如何做到这一点。 David Gilbert 建议我使用 PeriodAxis。尽管 PeriodAxis 可以根据需要将刻度标签居中,但它不适合我,因为我找不到任何方法如何为刻度标签设置我自己的 DateFormater。 (我没有找到任何名为“setDateFormatOverride()”之类的方法...)

标签: java alignment jfreechart


【解决方案1】:

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

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

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)

【讨论】:

  • 不错。像魅力一样工作:-)
  • 我不知道你是否会看到这个,但你能告诉我如何让我的图表使用这个轴吗?
猜你喜欢
  • 2021-10-29
  • 1970-01-01
  • 1970-01-01
  • 2011-03-28
  • 2018-08-30
  • 2019-07-03
  • 2023-03-25
  • 2013-07-06
  • 2012-03-14
相关资源
最近更新 更多