EgLeO
EgLeO

Reputation: 21

Jfreechart indexoutofbounds exception

This is my first time posting on StackOverFlow so please excuse any mistakes in my post. Coming to the topic I have a program which is supposed to plot certain X and Y co-ordinates. I am currently using JFREECHART for XYChart. In order to meet high speed update requirements I have 2 series. Series1 accumulates 1000 datapoints and then gets added to the chart to display it.After this series2 accumaltes 1000 datapoints and once it is finised it clears series1 (to prepare it for new 1000 datapoints) and attaches series2 to the chart(which now has 1000 datapoints)...and this cycle goes on. The issue is with the XYSeries.clear and XYSeries.add functions. Apparently they create their own threads and before they can finish execution I already spawn a new thread since my application is very fast. This I think is causing indexoutobounds error. I was able to guess this with: http://www.jfree.org/phpBB2/viewtopic.php?p=68111 Similar error. The solution seems to be: Swing Thread Safe Programming

Here the solution is to use SwingUtilities.invokeLater. But I fail to understand how to use this in my application. My code is:

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import static java.lang.Thread.sleep;
import org.jfree.chart.axis.ValueAxis;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberTickUnit;
import org.jfree.chart.plot.XYPlot;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;

public class dataPlotter {

    private static int x = 0;
    private static boolean thread_start = false;
    private static final double PI = 3.1428571;
    private static boolean thread_run = true;
    private static double voltage = 0;
    private static boolean useSeries1 = true;

    public static void main(String[] args) {

        //create and configure window
        JFrame window = new JFrame();
        window.setTitle("Data Plotter GUI");
        window.setSize(600, 400);
        window.setLayout(new BorderLayout());
        window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        //create a drop down box and connect button, then place that at the top of the window 
        JButton connectButton = new JButton("Start");
        JPanel topPanel = new JPanel();
        topPanel.add(connectButton);
        window.add(topPanel, BorderLayout.NORTH);

        //create the line graph
        XYSeries series1 = new XYSeries("Voltage Characteristics");
        XYSeries series2 = new XYSeries("Voltage Characteristics");
        XYSeriesCollection dataset = new XYSeriesCollection(series1);
        JFreeChart chart = ChartFactory.createXYLineChart("Sine Wave", "Time", "Voltage", dataset);
        window.add(new ChartPanel(chart), BorderLayout.CENTER);

        //set range of x axis and range of series 
        XYPlot xyPlot = chart.getXYPlot();
        ValueAxis domainAxis = xyPlot.getDomainAxis();
        ValueAxis rangeAxis = xyPlot.getRangeAxis();
        rangeAxis.setRange(-1.5, 1.5);
        domainAxis.setRange(0, 1000);

        //Declaring thread
        Thread thread;
        thread = new Thread() {
            @Override
            public void run() {
                thread_start = true;
                int count_y = 0;
                thread_run = true;
                int count = 0;
                while (thread_run) {
                    //create "Y" datapoint i.e. voltage
                    if (count_y < 800) {
                        count_y++;
                    } else {
                        count_y = 0;
                    }
                    voltage = Math.sin(2 * PI * count_y / 800);

                    //add datapoint to CHART
                    try {
                        //use series 1 to accumulate 1000 datapoints
                        if(useSeries1){
                            synchronized (series1) {
                                if (series1.getItemCount() > 1000) {
                                    domainAxis.setRange(x - 1001, x);
                                }
                                series1.add(x++, voltage);
                                sleep(1);
                            }
                            synchronized (series1) {
                                window.repaint();
                            }

                            synchronized (series1) {
                                if (series1.getItemCount() ==1001) {

                                    dataset.removeAllSeries();
                                    series2.clear();
                                    domainAxis.setRange(0, 1000);
                                    dataset.addSeries(series1);
                                    useSeries1 = false;
                                    sleep(5);
                                    x = 0;
                                    window.repaint();
                                }
                            }
                        } else{
                            synchronized (series2) {
                                if (series2.getItemCount() > 1000) {
                                    domainAxis.setRange(x - 1001, x);
                                }
                                series2.add(x++, voltage);
                                sleep(1);
                            }
                            synchronized (series2) {
                                window.repaint();
                            }
                            synchronized (series2) {
                                if (series2.getItemCount() == 1001) {
                                    dataset.removeAllSeries();
                                    series1.clear();
                                    domainAxis.setRange(0, 1000);
                                    dataset.addSeries(series2);
                                    useSeries1 = true;
                                    sleep(5);
                                    x = 0;
                                    window.repaint();
                                 }
                            }
                        }
                    } catch (Exception er) { }
                }
                thread_start = false;
             }
            };

        //configure the connect button and use another thread to listen for data
        connectButton.addActionListener(new ActionListener() {
            @Override
            @SuppressWarnings("empty-statement")
            public void actionPerformed(ActionEvent e) {
                if (connectButton.getText().equals("Start")) {

                    connectButton.setText("Stop");

                    thread.start();
                } else {
                    //disconnect from the serial port
                    thread_run = false;

                    //while (thread_start);
                    connectButton.setText("Start");
                    //synchronized (series) {
                    //series.clear();
                    //}
                    //x = 0;

                }
            }

        });

        //Display winow
        window.setVisible(true);

    }
}

Upvotes: 0

Views: 227

Answers (2)

EgLeO
EgLeO

Reputation: 21

Thanks a lot to AUBIN I got a final and high speed working code without any errors. The final code is:

import java.awt.BorderLayout;
import org.jfree.chart.axis.ValueAxis;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.XYPlot;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;


public class Plotter implements Runnable {

   private static final String BTN_START = "Start";
   private static final String BTN_STOP  = "Stop";

   private final JFrame             window        = new JFrame();
   private final JButton            connectButton = new JButton( BTN_START );
   private final XYSeries           series1       = new XYSeries("Voltage Characteristics");
   private final XYSeries           series2       = new XYSeries("Voltage Characteristics");
   private final XYSeriesCollection dataset       = new XYSeriesCollection( series2 );
   private final JFreeChart         chart         = ChartFactory.createXYLineChart("Sine Wave", "Time", "Voltage", dataset);
   private final XYPlot             xyPlot        = chart.getXYPlot();
   private final ValueAxis          domainAxis    = xyPlot.getDomainAxis();
   private final ValueAxis          rangeAxis     = xyPlot.getRangeAxis();
   private /* */ Thread             thread        = null;
   private /* */ boolean            thread_run    = true;
   private /* */ double             voltage       = 0;
   private /* */ boolean            useSeries1    = true;
   private /* */ int                x             = 0;

   public Plotter() {
      window.setTitle("Data Plotter GUI");
      window.setSize(600, 400);
      window.setLayout(new BorderLayout());
      window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      final JPanel topPanel = new JPanel();
      topPanel.add(connectButton);
      window.add(topPanel, BorderLayout.NORTH);
      window.add(new ChartPanel(chart), BorderLayout.CENTER);
      rangeAxis.setRange(-1.5, 1.5);
      domainAxis.setRange(0, 1000);
      connectButton.addActionListener( e -> startOrStop());
      window.setVisible(true);
   }

   private void startOrStop() {
      if (connectButton.getText().equals( BTN_START )) {
         connectButton.setText( BTN_STOP );
         synchronized( series1 ) { series1.clear(); }
         synchronized( series2 ) { series2.clear(); }
         thread = new Thread( Plotter.this );
         thread.setName( "Plotter" );
         thread.setDaemon( true );
         thread.start();
      }
      else {
         thread_run = false;
         //disconnect from the serial port
         connectButton.setText( BTN_START );
         try {
            thread.join( 10_000L );
         }
         catch( final InterruptedException e ){
            e.printStackTrace();
         }
      }
   }

   private void updateSeries1() {
      if (series1.getItemCount() > 1000) {
         domainAxis.setRange(x - 1001, x);
      }
      series1.add( x++, voltage );
      //window.repaint();
      if (series1.getItemCount() ==1001) {

         dataset.removeAllSeries();
         series2.clear();
         domainAxis.setRange(0, 1000);
         dataset.addSeries(series1);
         useSeries1 = false;
         window.repaint();
         x = 0;
      }
   }

   private void updateSeries2() {
      if (series2.getItemCount() > 1000) {
         domainAxis.setRange(x - 1001, x);
      }
      series2.add( x++, voltage );
      //window.repaint();
      if( series2.getItemCount() == 1001 ) {
         dataset.removeAllSeries();
         series1.clear();
         domainAxis.setRange(0, 1000);
         dataset.addSeries(series2);
         useSeries1 = true;
         window.repaint();
         x = 0;
      }
   }

   @Override
   public void run() {
      x = 0;
      int count_y = 0;
      thread_run = true;
      while( thread_run ) {
         if (count_y < 800) {
            count_y++;
         }
         else {
            count_y = 0;
         }
         voltage = Math.sin(( 2 * Math.PI * count_y ) / 800);
         try {
            // Push a new job in the Swing queue
            if( useSeries1 ) {
               SwingUtilities.invokeAndWait(() -> updateSeries1());
            }
            else {
               SwingUtilities.invokeAndWait(() -> updateSeries2());
            }
         }
         catch (final Exception er) {
            er.printStackTrace();
         }
      }
   }

   public static void main(String[] args) {
      SwingUtilities.invokeLater(() -> new Plotter());
   }
}

Upvotes: 0

Aubin
Aubin

Reputation: 14853

Swing is not thread safe, by design. You have to use SwingUtilities.invokeLater( ... ) from another thread than Swing event pump thread as discussed here.

The tutorial about Swing may help.

Here is a proposal, object oriented. Hope this helps.

public class Plotter implements Runnable {

   private static final String BTN_START = "Start";
   private static final String BTN_STOP  = "Stop";

   private final JFrame             window        = new JFrame();
   private final JButton            connectButton = new JButton( BTN_START );
   private final XYSeries           series1       = new XYSeries("Voltage Characteristics");
   private final XYSeries           series2       = new XYSeries("Voltage Characteristics");
   private final XYSeriesCollection dataset       = new XYSeriesCollection( series1 );
   private final JFreeChart         chart         =
      ChartFactory.createXYLineChart("Sine Wave", "Time", "Voltage", dataset);
   private final XYPlot             xyPlot        = chart.getXYPlot();
   private final ValueAxis          domainAxis    = xyPlot.getDomainAxis();
   private final ValueAxis          rangeAxis     = xyPlot.getRangeAxis();
   private /* */ Thread             thread        = null;
   private /* */ boolean            thread_run    = true;
   private /* */ double             voltage       = 0;
   private /* */ boolean            useSeries1    = true;
   private /* */ int                x             = 0;

   public Plotter() {
      window.setTitle("Data Plotter GUI");
      window.setSize(600, 400);
      window.setLayout(new BorderLayout());
      window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      final JPanel topPanel = new JPanel();
      topPanel.add(connectButton);
      window.add(topPanel, BorderLayout.NORTH);
      window.add(new ChartPanel(chart), BorderLayout.CENTER);
      rangeAxis.setRange(-1.5, 1.5);
      domainAxis.setRange(0, 1000);
      connectButton.addActionListener( e -> startOrStop());
      window.setVisible(true);
   }

   private void startOrStop() {
      if (connectButton.getText().equals( BTN_START )) {
         connectButton.setText( BTN_STOP );
         synchronized( series1 ) { series1.clear(); }
         synchronized( series2 ) { series2.clear(); }
         thread = new Thread( Plotter.this );
         thread.setName( "Plotter" );
         thread.setDaemon( true );
         thread.start();
      }
      else {
         thread_run = false;
         //disconnect from the serial port
         connectButton.setText( BTN_START );
         try {
            thread.join( 10_000L );
         }
         catch( final InterruptedException e ){
            e.printStackTrace();
         }
      }
   }

   private void updateSeries1() {
      if (series1.getItemCount() > 1000) {
         domainAxis.setRange(x - 1001, x);
      }
      series1.add( x++, voltage );
      window.repaint();
      if (series1.getItemCount() ==1001) {

         dataset.removeAllSeries();
         series2.clear();
         domainAxis.setRange(0, 1000);
         dataset.addSeries(series1);
         useSeries1 = false;
         window.repaint();
         x = 0;
      }
   }

   private void updateSeries2() {
      if (series2.getItemCount() > 1000) {
         domainAxis.setRange(x - 1001, x);
      }
      series2.add( x++, voltage );
      window.repaint();
      if( series2.getItemCount() == 1001 ) {
         dataset.removeAllSeries();
         series1.clear();
         domainAxis.setRange(0, 1000);
         dataset.addSeries(series2);
         useSeries1 = true;
         window.repaint();
         x = 0;
      }
   }

   @Override
   public void run() {
      x = 0;
      int count_y = 0;
      thread_run = true;
      while( thread_run ) {
         if (count_y < 800) {
            count_y++;
         }
         else {
            count_y = 0;
         }
         voltage = Math.sin(( 2 * Math.PI * count_y ) / 800);
         try {
            // Push a new job in the Swing queue
            if( useSeries1 ) {
               SwingUtilities.invokeLater(() -> updateSeries1());
            }
            else {
               SwingUtilities.invokeLater(() -> updateSeries2());
            }
         }
         catch (final Exception er) {
            er.printStackTrace();
         }
         try {
            Thread.sleep( 2L ); // Give time to Swing to execute the job
         }
         catch( final InterruptedException e ){
            e.printStackTrace();
         }
      }
   }

   public static void main(String[] args) {
      SwingUtilities.invokeLater(() -> new Plotter());
   }
}

Upvotes: 2

Related Questions