TimeSeries notify and auto range problems

A discussion forum for JFreeChart (a 2D chart library for the Java platform).
Locked
dweems
Posts: 12
Joined: Sat Feb 20, 2010 9:59 pm
antibot: No, of course not.

TimeSeries notify and auto range problems

Post by dweems » Wed Mar 03, 2010 12:19 am

I'm creating a CombinedDomainXYPlot with a line chart above a bar chart with a TimeSeries domain axis.

Code: Select all

    this.upperDataSet.addSeries( this.priceTimeSeries );
    this.lowerDataSet.addSeries( this.volumeTimeSeries );
    
    XYToolTipGenerator toolTipGenerator = 
      StandardXYToolTipGenerator.getTimeSeriesInstance();

    // Create the renderer for the upper plot
    
    XYLineAndShapeRenderer upperRenderer = new XYLineAndShapeRenderer();
    upperRenderer.setBaseToolTipGenerator( toolTipGenerator );
    upperRenderer.setSeriesLinesVisible( 0, true );
    upperRenderer.setSeriesShapesVisible( 0, true );
    upperRenderer.setSeriesShapesFilled( 0, true );
    upperRenderer.setSeriesShape( 0, new Rectangle2D.Double( -1.0, -1.0, 2.0, 2.0 ) );

    // Create the upper plot
    
    NumberAxis upperRangeAxis = new NumberAxis( "Price" );
    upperRangeAxis.setAutoRange( true );
    upperRangeAxis.setAutoRangeIncludesZero( false );
    XYPlot upperPlot = new XYPlot( this.upperDataSet, null, upperRangeAxis, 
                                   upperRenderer );
    upperPlot.setBackgroundPaint( Color.lightGray );
    upperPlot.setDomainGridlinePaint( Color.white );
    upperPlot.setRangeGridlinePaint( Color.white );

    // Create the renderer for the lower plot.
    
    XYBarRenderer lowerRenderer = new XYBarRenderer() {
      public Paint getItemPaint( int series, int item ) {
        return Color.black;
      }
    };
    
    lowerRenderer.setSeriesPaint( 0, Color.black );
    lowerRenderer.setDrawBarOutline( false );
    lowerRenderer.setBaseToolTipGenerator( toolTipGenerator );

    // Create the lower plot
    
    this.lowerRangeAxis = new NumberAxis();
    this.lowerRangeAxis.setAutoRange( true );
    this.lowerRangeAxis.setLabel( "Volume" );
    XYPlot lowerPlot = new XYPlot( this.lowerDataSet, null, this.lowerRangeAxis, 
                                   lowerRenderer );
    lowerPlot.setBackgroundPaint( Color.lightGray );
    lowerPlot.setDomainGridlinePaint( Color.white );
    lowerPlot.setRangeGridlinePaint( Color.white );

    // Create the domain axis that will be used by both plots.
    
    DateAxis domainAxis = new DateAxis( "Date Time" );
    domainAxis.setLowerMargin( 0.0 );
    domainAxis.setUpperMargin( 0.02 );
    domainAxis.setDateFormatOverride( this.dateFormat );
    
    // Create the combined domain plot with the common domain axis, the upper plot
    // and the lower plot.
    
    CombinedDomainXYPlot cplot = new CombinedDomainXYPlot( domainAxis );
    cplot.add( upperPlot, 3 );
    cplot.add( lowerPlot, 1 );
    cplot.setGap( 8.0 );
    cplot.setDomainGridlinePaint( Color.white );
    cplot.setDomainGridlinesVisible( true );
    cplot.setDomainPannable( false );

    // Create the new chart
    
    this.chart = new JFreeChart( "Contract Name", cplot );
    ChartUtilities.applyCurrentTheme( this.chart );
    upperRenderer.setSeriesPaint( 0, Color.blue );
    lowerRenderer.setBarPainter( new StandardXYBarPainter() );
    lowerRenderer.setShadowVisible( false );
    
    // Put the chart in a new ChartPanel, set the panel attributes and return it.
    
    ChartPanel combinedChartPanel = new ChartPanel( this.chart );
    combinedChartPanel.setBorder( new EtchedBorder( EtchedBorder.RAISED ) );
    combinedChartPanel.setPreferredSize( this.combinedChartPanelDim  );
I'm populating my charts with a great deal of data, in the thousands of data points per chart view. I have found that if I add TimeSeriesDataItem objects to the TimeSeries object with the notify parameter set to true (explicitly or by default) that rendering can take an inordinate amount of time. Running a debugger indicates this is probably because for every data point added to the time series the XYPlot recomputes the domain and range. That's an n-factorial number of operations and if n is several thousand, well, you can do the math. So I changed my code so that the TimeSeriesDataItem objects are added to the TimeSeries with the notify option set to false and execute the fireSeriesChanged() method on the TimeSeries after all the data has been loaded. Here's the code I use in loading the upper data set. The code for the lower data set is equivalent with appropriate data substitutions.

Code: Select all

    Millisecond millisecondTime;
    
    try {
      this.priceTimeSeries.clear();
      
      for ( int i = 0; i < this.dataNumValues; i++ ) {
        GregorianCalendar timestamp = this.dataTimestamps[ i ];
        millisecondTime = new Millisecond( timestamp.get( Calendar.MILLISECOND ),
                                           timestamp.get( Calendar.SECOND ),
                                           timestamp.get( Calendar.MINUTE ),
                                           timestamp.get( Calendar.HOUR_OF_DAY ),
                                           timestamp.get( Calendar.DAY_OF_MONTH ),
                                           timestamp.get( Calendar.MONTH ) + 1,
                                           timestamp.get( Calendar.YEAR ) );
          
        this.priceTimeSeries.add( millisecondTime, this.dataPrices[ i ], false );
      }
      
      this.priceTimeSeries.fireSeriesChanged();
    }
I get the performance I want to see but now I have a new problem. The extents of the range for the upper chart are not being calculated when the SeriesChangeEvent is fired even though AutoRange is set to true and AutoRangeIncludesZero is set to false. Two of the functions I employ are pan and zoom. I can't use the pan and zoom functions provided by JFreeChart because there is just too much data (a complete data set could have millions of data points), so I perform those functions by replacing the data with the appropriate subset of data and fire the SeriesChangeEvent flag to redisplay the charts. If I pan or zoom, the range calculations are performed sometimes and sometimes not. In the latter case I'll get part of the data displayed but part missing because the range axis is not properly rescaling.

I have been stepping through the code and I've only found one thing so far that explains the behavior and this only explains why the first display is missing. I found the problem in the AbstractXYItemRenderer while executing findRangeBounds. When it executes xAxis = plot.getDomainAxisForDataset(index) it comes back with a domain set to December 31, 1969 at 5:00 for both the minimum and maximum. Where it gets those values is beyond me because all of my Calendar dates are in 2008. After I pan or zoom the domain range comes back appropriate. Indeed, it does so for the lower bar chart on the first display.

I hope someone with a deep understanding of this code understands why the domain and range calculations have problems when the SeriesChangeEvent flag is fired only after loading all of the data into the TimeSeries object. I'm at my wits end. thanx

dweems
Posts: 12
Joined: Sat Feb 20, 2010 9:59 pm
antibot: No, of course not.

Re: TimeSeries notify and auto range problems

Post by dweems » Wed Mar 03, 2010 11:02 pm

Work Around Found: this should be considered a bug but it looks like the solution to the problem was simply to fire the fireSeriesChange() method twice after loading the data for the upper chart/data series. Now all the data appears on the charts as expected and in a timely manner.

Locked