Skip to content

unlabeled NumberAxis can cause x or y axis grid lines to be incomplete #463

Description

@colomb001

This is an issue I was seeing in a chart. I used an agent to help me track it down. I'm running 1.5.2, but tested against 1.5.6 and saw the same behavior.

XYPlot.drawAxes stores each axis's drawing state (which carries that axis's tick list) HashMap keyed by the axis object. But for an auto-ranged axis JFreeChart's ValueAxis.equals ignores the current range, and Axis.hashCode hashes only the (here null) label — so two unlabeled, identically-configured, auto-ranged {@code NumberAxis} instances are equal with equal hash codes even if they have different ranges. The range (y) axis's state therefore overwrites the domain (x) axis's entry in the map, and the vertical grid line pass draws lines at the y-axis tick values.

import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;

import javax.swing.*;
import java.awt.*;

private static JFreeChart buildChart( java.util.function.Supplier<NumberAxis> axisFactory )
{
    XYSeries series = new XYSeries( "data" );
    // x: 0.5 .. 11.5  (so vertical gridlines belong at 1,2,..,11)
    // y: a gentle curve in 0 .. 7 (so the y ticks 0..7 land inside the x range when misused)
    for( double x = 0.5; x <= 11.5; x += 0.25 )
        series.add( x, 3.5 + 3.0 * Math.sin( x / 2.0 ) );

    XYSeriesCollection dataset = new XYSeriesCollection( series );

    NumberAxis xAxis = axisFactory.get();
    xAxis.setAutoRangeIncludesZero( false );
    NumberAxis yAxis = axisFactory.get();
    yAxis.setAutoRangeIncludesZero( false );

    XYPlot plot = new XYPlot( dataset, xAxis, yAxis, new XYLineAndShapeRenderer( true, false ) );
    plot.setOrientation( PlotOrientation.VERTICAL );
    plot.setBackgroundPaint( Color.WHITE );

    plot.setDomainGridlinesVisible( true );
    plot.setDomainGridlinePaint( Color.BLUE );
    plot.setDomainGridlineStroke( new BasicStroke( 1f ) );
    plot.setRangeGridlinesVisible( true );
    plot.setRangeGridlinePaint( new Color( 220, 220, 220 ) );
    plot.setDomainZeroBaselineVisible( false );
    plot.setRangeZeroBaselineVisible( false );

    return new JFreeChart( "", JFreeChart.DEFAULT_TITLE_FONT, plot, false );
}

 void main( String[] args )
    {
        SwingUtilities.invokeLater( () ->
        {
            JFrame frame = new JFrame( "JFreeChart axis-equals gridline bug" );
            frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
            frame.setSize( 900, 600 );

            ChartPanel chartPanel = new ChartPanel( buildChart( NumberAxis::new ) );
            JLabel status = new JLabel();
            JButton toggle = new JButton();

            // boolean[1] so the lambda can flip it
            boolean[] fixed = { false };
            Runnable apply = () ->
            {
                chartPanel.setChart( buildChart( fixed[0] ? IdentityEqualsNumberAxis::new : NumberAxis::new ) );
                status.setText( fixed[0]
                    ? "  IdentityEqualsNumberAxis — vertical gridlines correct (1..11, full width)"
                    : "  stock NumberAxis — vertical gridlines WRONG (bunched left, drawn at y-tick values)" );
                toggle.setText( fixed[0] ? "Show the bug (stock NumberAxis)" : "Apply the fix (identity-equals axis)" );
            };

            toggle.addActionListener( e ->
            {
                fixed[0] = !fixed[0];
                apply.run();
            } );
            apply.run();

            JPanel top = new JPanel( new BorderLayout() );
            top.add( toggle, BorderLayout.WEST );
            top.add( status, BorderLayout.CENTER );

            frame.setLayout( new BorderLayout() );
            frame.add( top, BorderLayout.NORTH );
            frame.add( chartPanel, BorderLayout.CENTER );
            frame.setLocationRelativeTo( null );
            frame.setVisible( true );
        } );
    }

    private static final class IdentityEqualsNumberAxis extends NumberAxis
    {
        @Override
        public boolean equals( Object obj )
        {
            return obj == this;
        }

        @Override
        public int hashCode()
        {
            return System.identityHashCode( this );
        }
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions