Distorted domain axis with DateTickMarkPosition.END

A discussion forum for JFreeChart (a 2D chart library for the Java platform).
Locked
gandhi
Posts: 11
Joined: Wed Nov 17, 2004 5:24 pm

Distorted domain axis with DateTickMarkPosition.END

Post by gandhi » Fri Jan 07, 2005 4:00 pm

I have a linechart based on a XYDataset (TimeSeriesCollection) containing data on a per month basis. It displays fine, if I use DateTickMarkPosition.START for the domain axis tick mark position (excerpt):

Code: Select all

...
domainAxis.setTickUnit( new DateTickUnit(DateTickUnit.YEAR, 1, axisXTickLabelDateFormat) );
domainAxis.setTickMarkPosition(DateTickMarkPosition.START);
...
This will show the tick marks & labels at the January of each year as expected.

If I change it to use DateTickMarkPosition.END, ticks are still displayed on the January position, and not December, as I would expect.
Also, after this change the first tick label is 2002 instead of 2001 and the actual tick label for 2002 is ommitted, which means that between the first tick label (2002 which should be 2001) and the second tick label (2003, this is correct) I have the values for 24 months instead of twelve months.

I use jfreechart-0.9.21. Any help would be appreciated. I can post more of the code as well as images, in case this helps.

gandhi
Posts: 11
Joined: Wed Nov 17, 2004 5:24 pm

Post by gandhi » Fri Jan 07, 2005 5:20 pm

I hope the following graphs make it easier to understand. "x" is a data value for each month. "O" is a data value for december of the year (this is actually a second series using another shape that has same values like the first series but only for december, so that december values get highlighted).

Chart with domainAxis.setTickMarkPosition(DateTickMarkPosition.START);

Code: Select all

|
|
|                                      xxxxxxxxxO
|              xxxxxxxxxOxxxxxxxxxxxOxx
|      xxxxxOxx
|  xxxx
|xx
|
--------------------------------------------------------------
 |           |           |           |           |
2001        2002        2003        2004        2005

Chart with domainAxis.setTickMarkPosition(DateTickMarkPosition.END);

Code: Select all

|
|
|                                      xxxxxxxxxO
|              xxxxxxxxxOxxxxxxxxxxxOxx
|      xxxxxOxx
|  xxxx
|xx
|
--------------------------------------------------------------
             |                       |           |
            2002                    2003        2004

david.gilbert
JFreeChart Project Leader
Posts: 11734
Joined: Fri Mar 14, 2003 10:29 am
antibot: No, of course not.
Contact:

Post by david.gilbert » Fri Jan 07, 2005 5:32 pm

I have been able to reproduce the problem with the following code. Don't know (yet) what causes the problem...

Code: Select all

import java.awt.Color;
import java.text.DateFormat;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.TimeZone;

import javax.swing.JPanel;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.axis.DateTickMarkPosition;
import org.jfree.chart.axis.DateTickUnit;
import org.jfree.chart.labels.StandardXYToolTipGenerator;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.data.time.Month;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;
import org.jfree.data.xy.IntervalXYDataset;
import org.jfree.ui.ApplicationFrame;
import org.jfree.ui.RefineryUtilities;

public class TestMonthlyChart extends ApplicationFrame {

    /**
     * Constructs the demo application.
     *
     * @param title  the frame title.
     */
    public TestMonthlyChart(String title) {

        super(title);
        IntervalXYDataset dataset = createDataset();
        JFreeChart chart = createChart(dataset);
        ChartPanel chartPanel = new ChartPanel(chart);
        chartPanel.setPreferredSize(new java.awt.Dimension(500, 300));
        setContentPane(chartPanel);

    }
    
    private static JFreeChart createChart(IntervalXYDataset dataset) {
        JFreeChart chart = ChartFactory.createXYBarChart(
            "Random Weekly Data",
            "Year",
            true,
            "Number of People",
            dataset,
            PlotOrientation.VERTICAL,
            true,
            true,
            false
        );

        // then customise it a little...
        chart.setBackgroundPaint(Color.white);
        
        XYPlot plot = chart.getXYPlot();     
        plot.setBackgroundPaint(Color.lightGray);
        plot.setRangeGridlinePaint(Color.white);
        XYItemRenderer renderer = plot.getRenderer();
        renderer.setToolTipGenerator(new StandardXYToolTipGenerator("{1}. {2}", DateFormat.getInstance(), NumberFormat.getInstance()));
        
        DateAxis axis = (DateAxis) plot.getDomainAxis();
        axis.setTickUnit(new DateTickUnit(DateTickUnit.YEAR, 1, new SimpleDateFormat("yyyy")));
        axis.setTickMarkPosition(DateTickMarkPosition.END);         
        axis.setLowerMargin(0.01);
        axis.setUpperMargin(0.01);
        return chart;
    }
    

    private static IntervalXYDataset createDataset() {

        TimeSeries t1 = new TimeSeries("Random Monthly Data", "Month", "Count", Month.class);
        try {
            t1.add(new Month(1, 2003), new Integer(9));
            t1.add(new Month(2, 2003), new Integer(1));
            t1.add(new Month(3, 2003), new Integer(7));
            t1.add(new Month(4, 2003), new Integer(2));
            t1.add(new Month(5, 2003), new Integer(3));
            t1.add(new Month(6, 2003), new Integer(31));
            t1.add(new Month(7, 2003), new Integer(2));
            t1.add(new Month(8, 2003), new Integer(5));
            t1.add(new Month(9, 2003), new Integer(21));
            t1.add(new Month(10, 2003), new Integer(21));
            t1.add(new Month(11, 2003), new Integer(21));
            t1.add(new Month(12, 2003), new Integer(21));
            t1.add(new Month(1, 2004), new Integer(9));
            t1.add(new Month(2, 2004), new Integer(1));
            t1.add(new Month(3, 2004), new Integer(7));
            t1.add(new Month(4, 2004), new Integer(2));
            t1.add(new Month(5, 2004), new Integer(3));
            t1.add(new Month(6, 2004), new Integer(31));
            t1.add(new Month(7, 2004), new Integer(2));
            t1.add(new Month(8, 2004), new Integer(5));
            t1.add(new Month(9, 2004), new Integer(21));
            t1.add(new Month(10, 2004), new Integer(21));
            t1.add(new Month(11, 2004), new Integer(21));
            t1.add(new Month(12, 2004), new Integer(21));
            t1.add(new Month(1, 2005), new Integer(9));
            t1.add(new Month(2, 2005), new Integer(1));
            t1.add(new Month(3, 2005), new Integer(7));
            t1.add(new Month(4, 2005), new Integer(2));
            t1.add(new Month(5, 2005), new Integer(3));
            t1.add(new Month(6, 2005), new Integer(31));
            t1.add(new Month(7, 2005), new Integer(2));
            t1.add(new Month(8, 2005), new Integer(5));
            t1.add(new Month(9, 2005), new Integer(21));
            t1.add(new Month(10, 2005), new Integer(21));
            t1.add(new Month(11, 2005), new Integer(21));
            t1.add(new Month(12, 2005), new Integer(21));
        }
        catch (Exception e) {
            System.err.println(e.getMessage());
        }
        TimeSeriesCollection tsc = new TimeSeriesCollection(t1, TimeZone.getTimeZone("America/New_York"));
        tsc.setDomainIsPointsInTime(false);
        return tsc;

    }

    /**
     * Creates a panel for the demo.
     *  
     * @return A panel.
     */
    public static JPanel createDemoPanel() {
        return new ChartPanel(createChart(createDataset()));
    }
    
    /**
     * Starting point for the demonstration application.
     *
     * @param args  ignored.
     */
    public static void main(String[] args) {

        TestMonthlyChart demo = new TestMonthlyChart("Random Monthly Data");
        demo.pack();
        RefineryUtilities.centerFrameOnScreen(demo);
        demo.setVisible(true);

    }

}
David Gilbert
JFreeChart Project Leader

:idea: Read my blog
:idea: Support JFree via the Github sponsorship program

gandhi
Posts: 11
Joined: Wed Nov 17, 2004 5:24 pm

Post by gandhi » Wed Jan 12, 2005 1:32 pm

Ok, thanks. Do you know when you will have time to look into this ?

I could also possibly do it, although I have no idea how deep into the existing code I have to go. I you have an idea where the problem be sitting this would certainly help.

david.gilbert
JFreeChart Project Leader
Posts: 11734
Joined: Fri Mar 14, 2003 10:29 am
antibot: No, of course not.
Contact:

Post by david.gilbert » Wed Jan 12, 2005 11:40 pm

It is on my list of things to do this week, but I don't want to make any promises since my time tends to evaporate pretty easily...
David Gilbert
JFreeChart Project Leader

:idea: Read my blog
:idea: Support JFree via the Github sponsorship program

gandhi
Posts: 11
Joined: Wed Nov 17, 2004 5:24 pm

Post by gandhi » Thu Jan 13, 2005 4:11 pm

David, I have been able to locate the part of the code that causes this error. It is in the method previousStandardDate in the DateAxis class:

Code: Select all

            case(DateTickUnit.YEAR) :
                if (this.tickMarkPosition == DateTickMarkPosition.START) {
                    months = 0;
                }
                else if (this.tickMarkPosition == DateTickMarkPosition.MIDDLE) {
                    months = 6;
                }
                else {
                    months = 12; 
                }
                calendar.clear(Calendar.MILLISECOND);
                calendar.set(value, months, 1, 0, 0, 0);
                return calendar.getTime();

"months = 12" is wrong here, which is a typo that probably stems from the crazy fact that the Java Calendar class starts numbering the months with 0 which makes the correct value for December equal to 11. Strange that the set-method of the Calendar class doesn't throw an exception here, it silently increases the year instead.

If I correctly understand the code and its intention, it would probably make sense to also add a months field that equals to the last day of December for DateTickMarkPosition.END, so the resulting code would look like this:

Code: Select all

            case(DateTickUnit.YEAR) :
                if (this.tickMarkPosition == DateTickMarkPosition.START) {
                    months = 0;
                    days = 1;    // ADDED
                }
                else if (this.tickMarkPosition == DateTickMarkPosition.MIDDLE) {
                    months = 6;
                    days = 1;    // ADDED
                }
                else {
                    months = 11;  // CHANGED
                    days = 31;    // ADDED
                }
                calendar.clear(Calendar.MILLISECOND);
                calendar.set(value, months, days, 0, 0, 0);  // CHANGED
                return calendar.getTime();

This works fine for me, you will surely have another look, as I can not guarantee that I understand all of the consequences introduced by the changes that hopefully fix the bug.

david.gilbert
JFreeChart Project Leader
Posts: 11734
Joined: Fri Mar 14, 2003 10:29 am
antibot: No, of course not.
Contact:

Post by david.gilbert » Fri Jan 14, 2005 10:59 am

The fix looks fine to me - thanks! I've committed the change to CVS ready for the next release.
David Gilbert
JFreeChart Project Leader

:idea: Read my blog
:idea: Support JFree via the Github sponsorship program

Locked