Search code examples
javaswingjfreechartswingworker

Multiple graphs in multiple figures using jFreeChart


I am trying to use jFreechart to generate two figures each of which with 12 graphs (being referred as series in jFreeChart ). However some of the graphs get simply skipped! I know I have synchronization issue here and tried to used the method the user @trashgod provided me here however I failed. I know the way I use swingworker is wrong! I dont know how to fix it

Each figure should contain 10 graphs which are parallel horizontal straight lines. As you see in the attached image some of the lines are missing. The two figures have to be identical too ( which are not). In practice I will have to generate multiple graphs in several locations of my applications at various times(random time interval between each figure and even graphs of individual figures) Any help will be very much appreciated

Exception in thread "AWT-EventQueue-0" java.lang.IllegalArgumentException: This dataset already contains a series with the key Plot 11
    at org.jfree.data.xy.XYSeriesCollection.addSeries(XYSeriesCollection.java:154)
    at swing.FastChart2$MySwingWorker.process(FastChart2.java:192)
    at javax.swing.SwingWorker$3.run(SwingWorker.java:414)
    at sun.swing.AccumulativeRunnable.run(AccumulativeRunnable.java:112)
    at javax.swing.SwingWorker$DoSubmitAccumulativeRunnable.run(SwingWorker.java:832)
    at sun.swing.AccumulativeRunnable.run(AccumulativeRunnable.java:112)
    at javax.swing.SwingWorker$DoSubmitAccumulativeRunnable.actionPerformed(SwingWorker.java:842)
    at javax.swing.Timer.fireActionPerformed(Timer.java:312)
    at javax.swing.Timer$DoPostEvent.run(Timer.java:244)
    at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:251)
    at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:733)
    at java.awt.EventQueue.access$200(EventQueue.java:103)
    at java.awt.EventQueue$3.run(EventQueue.java:694)
    at java.awt.EventQueue$3.run(EventQueue.java:692)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:76)
    at java.awt.EventQueue.dispatchEvent(EventQueue.java:703)
    at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:242)
    at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:161)
    at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:150)
    at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:146)
    at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:138)
    at java.awt.EventDispatchThread.run(EventDispatchThread.java:91) 

enter image description here

package swing;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.Ellipse2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
import org.jfree.chart.*;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.util.ShapeUtilities;

public class FastChart2 extends JFrame {
   private XYSeries [] xySeries ;
   private XYPlot xyPlot;
   private XYSeriesCollection xySeriesCollection;
   private String title;
   private static int instanceNum=0;
   private int figNum=0;

   private ChartPanel chartPanel;

   public  FastChart2(String s) {
      super(s);
      figNum = instanceNum;
      instanceNum++;
      init(s);
   }

   private void init(String s){
      title = s;
      xySeries = new XYSeries[12];
      for (int i = 0; i < xySeries.length; i++) {
         xySeries[i]    = new XYSeries("Plot "+i);  
      }
      xySeriesCollection = new XYSeriesCollection();
      JFreeChart chart = ChartFactory.createScatterPlot(
            title, "X", "Y", xySeriesCollection,
            PlotOrientation.VERTICAL, true, true, false);

      xyPlot =  chart.getXYPlot();
      xyPlot.setDomainCrosshairVisible(true);
      xyPlot.setRangeCrosshairVisible(true);

      chartPanel = createChartPanel(chart);

      add(chartPanel, BorderLayout.CENTER);
      JPanel control = new JPanel();

      add(control, BorderLayout.SOUTH);

      setDefaultCloseOperation(DISPOSE_ON_CLOSE); 
      pack();
      setLocationRelativeTo(null);
      setVisible(true);
   }

   private  ChartPanel createChartPanel(JFreeChart chart) {

      XYItemRenderer renderer = xyPlot.getRenderer();
      renderer.setSeriesPaint(0, Color.magenta);
      renderer.setSeriesPaint(1, Color.green);
      renderer.setSeriesPaint(2, Color.blue);
      renderer.setSeriesPaint(4, Color.black);
      renderer.setSeriesPaint(3, Color.yellow);
      Shape cross = ShapeUtilities.createDiagonalCross(3, 0);
      Shape plus = ShapeUtilities.createRegularCross(4,0);

      for (int i = 0; i <=3; i++) {
         renderer.setSeriesShape(0+i,    new Rectangle(-1, -1, 2, 2));
         renderer.setSeriesShape(4+i,    new Ellipse2D.Float(-2F, -2F, 5F, 5F));  
         renderer.setSeriesShape(8+i,    cross);
      }
      NumberAxis domain = (NumberAxis) xyPlot.getDomainAxis();
      domain.setRange(0,1000);
      NumberAxis range = (NumberAxis) xyPlot.getRangeAxis();
      range.setRange(0,1200);
      return new ChartPanel(chart);
   }


   public  void multiPlot(){
      Thread thread = null;
      thread = new Thread (){
         public void run() {
            final double [] x = new double[1000];
            final double [] y = new double[1000];

            try{    
               for (int k = 0; k < 12; k++) {

                  for (int i = 0; i < y.length; i++) {
                     x[i] = i;
                     y[i] = k*100;
                  }

                  try {
                     Thread.sleep(100);
                  } catch (InterruptedException e) {
                  }

                  plot2d(k % 12, x, y," Fig:"+figNum+" Seri:"+k);
               }
            } catch (Exception e){
               System.out.println();
            }     
         }
      };
      thread.start();
   }


   public synchronized void plot2d( final int iSeriesN,  final double [] dX,  final double [] dY, final String sT){

      if (dY.length != dX.length){
         throw new IllegalArgumentException("Error! inputs x and y have to be of same size.");
      }

            MySwingWorker mySwingWorker = new MySwingWorker( iSeriesN, dX, dY, sT);

            mySwingWorker
            .addPropertyChangeListener(new PropertyChangeListener() {

               public void propertyChange(PropertyChangeEvent pcEvt) {
                  if (pcEvt.getNewValue() == SwingWorker.StateValue.DONE) {
                    System.out.println("done");
                  }
                 if ("progress".equals(pcEvt.getPropertyName())) {
                     System.out.println("progress");
                   }
               }
            });

            mySwingWorker.execute(); 

   }

   private class MySwingWorker extends SwingWorker<Void, Double> {

      private double [] dX ;
      private double [] dY ;
      private String title;
      private int iSeriesN;
      private MySwingWorker(int iSeriesN, double [] ix, double[] iy, String st){
        dX = ix.clone();
        dY = iy.clone();
        title= st;
        this.iSeriesN = iSeriesN;
        xySeriesCollection.removeAllSeries();
        System.out.println("xySeriesCollection.removeAllSeries();");
      }

      @Override
      public Void doInBackground() throws IOException {

    //    chartPanel.getChart().removeChangeListener((ChartChangeListener) chartPanel);


         xySeries[iSeriesN].clear();
         for (int i = 0; i < dX.length; i++) {
            xySeries[iSeriesN].add(dX[i], dY[i]);
         }

         for (int i = 0; i < xySeries.length; i++) {
            setProgress(i * (100 / xySeries.length));
            publish(Double.valueOf(i));
            try {
               Thread.sleep(10);
            } catch (InterruptedException e) {
            } // simulate latency

        }
         return null;
      }


      @Override
      protected void process(List<Double> chunks) {
         for (double d : chunks) {
            xySeriesCollection.addSeries(xySeries[(int) d]);
        }

      }
      @Override
      protected void done() {
          try {


     //        chartPanel.getChart().addChangeListener((ChartChangeListener) chartPanel);
             xySeries[iSeriesN].setKey(title);

          } catch (Exception ignore) {
          }
      }
   }

   public XYSeries addXY(final int iSeriesN, final double [] dX, final double [] dY){
      XYSeries series = new XYSeries("Plot ");

      for (int i = 0; i < dX.length; i++) {
         series.add(dX[i], dY[i]);
      }
      return series;
   }

   public static void main(String args[]) {
      EventQueue.invokeLater(new Runnable() {
         @Override
         public void run() {
            FastChart2 [] demo = new FastChart2[2];
            for (int i = 0; i < demo.length; i++) {
               demo[i] = new FastChart2("Figure "+i);
               demo[i].multiPlot();

            } 

         }
      });
   }
}

Solution

  • I know the way I use swingworker is wrong! I dont know how to fix it

    Before start I have some tips:

    • Get rid of the arrays: you have several of them and you'll see they only mess the things up because you'll need indexes and loops everywhere to work with them and it's too easy make a mistake. I'd especially remove this one:

      private XYSeries [] xySeries; //XYSeriesCollection is intended to keep a series list, so...

    • Don't make your class extend from JFrame (or any Swing component) if you don't will add any functionality. You just can use a variable instead.

    • Besides the SwingWorker implementation needs to be fixed, it's more disturbing having a new Thread that calls this SwingWorker. Get rid of it too (it's no needed).

    • As @trahsgod pointed out in this comment, XYSeriesCollection is the chart's model so the key is working with it.

    Having said this, about your SwingWorker implementation, it should look like this:

    SwingWorker<Void, XYSeries> worker = new SwingWorker<Void, XYSeries>() {
    
       @Override
       protected Void doInBackground() throws Exception {
           /* 
            * This part is extracted from your multiPlot() method
            * I've just reduced the scale factor and get rid of double arrays
            */
           int numberOfElements = 100; // this is the number of elementes in X axis    
           for(int y = 0; y < 12; y++) { // we want 12 series
    
               XYSeries series = new XYSeries("Plot " + y);
               for (int x = 0; x < numberOfElements; x++) {
                   series.add(x, y*10); //add x,y point
               }
               publish(series);
               Thread.sleep(100);// just for animation purpose
           }
           return null;              
       }
    
       @Override
       protected void process(List<XYSeries> chunks) {
           for(XYSeries series : chunks){
               /* 
                * Add the series to the "model" here.
                * It will notify the "view" data has been changed and this last one will be updated
                * It's important make this call here to ensure the "view" is updated in the EDT.
                */
               xySeriesCollection.addSeries(series);
           }
       }
    };
    

    Working example

    Here is a complete working example based on your work that you can take as start point. Hope it be helpful :)

    import java.awt.BorderLayout;
    import java.awt.Color;
    import java.awt.FlowLayout;
    import java.awt.Rectangle;
    import java.awt.Shape;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.awt.geom.Ellipse2D;
    import java.util.List;
    import javax.swing.JButton;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.SwingUtilities;
    import javax.swing.SwingWorker;
    import org.jfree.chart.*;
    import org.jfree.chart.axis.NumberAxis;
    import org.jfree.chart.plot.PlotOrientation;
    import org.jfree.chart.plot.XYPlot;
    import org.jfree.chart.renderer.xy.XYItemRenderer;
    import org.jfree.data.xy.XYSeries;
    import org.jfree.data.xy.XYSeriesCollection;
    import org.jfree.util.ShapeUtilities;
    
    public class FreeChartDemo {
    
      XYSeriesCollection xySeriesCollection;
      String title;
    
      public FreeChartDemo(String title){
          this.title = title;
      }
    
      public void initGUI(){
    
          JButton clearChart =  new JButton("Clear chart");
          clearChart.addActionListener(new ActionListener() {
              @Override
              public void actionPerformed(ActionEvent e) {
                  xySeriesCollection.removeAllSeries();
              }
          });
    
          JButton fillChart = new JButton("Fill chart") ;
          fillChart.addActionListener(new ActionListener() {
              @Override
              public void actionPerformed(ActionEvent e) {
                  xySeriesCollection.removeAllSeries();
                  fillChart();
              }
          });
    
          JPanel controlPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
          controlPanel.add(clearChart);
          controlPanel.add(fillChart);
    
          JPanel content = new JPanel(new BorderLayout(5, 5));
          content.add(getFreeChartPanel(), BorderLayout.CENTER); //add the ChartPanel here
          content.add(controlPanel, BorderLayout.SOUTH);
    
          JFrame frame = new JFrame("JFreeChart demo");
          frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
          frame.getContentPane().add(content);
          frame.pack();
          frame.setLocationRelativeTo(null);
          frame.setVisible(true);
       }
    
       private JPanel getFreeChartPanel(){
           xySeriesCollection = new XYSeriesCollection();
    
           JFreeChart chart = ChartFactory.createScatterPlot(title, "X axis", "Y axis", xySeriesCollection, 
                                                             PlotOrientation.VERTICAL, true, true, false);       
           XYPlot plot = chart.getXYPlot();
           plot.setDomainCrosshairVisible(true);
           plot.setRangeCrosshairVisible(true);
    
           XYItemRenderer renderer = plot.getRenderer();
           renderer.setSeriesPaint(0, Color.magenta);
           renderer.setSeriesPaint(1, Color.green);
           renderer.setSeriesPaint(2, Color.blue);
           renderer.setSeriesPaint(4, Color.black);
           renderer.setSeriesPaint(3, Color.yellow);
    
           Shape cross = ShapeUtilities.createDiagonalCross(3, 0);
    
           for (int i = 0; i <= 3; i++) {
               renderer.setSeriesShape(0+i, new Rectangle(-1, -1, 2, 2));
               renderer.setSeriesShape(4+i, new Ellipse2D.Float(-2F, -2F, 5F, 5F));  
               renderer.setSeriesShape(8+i, cross);
           }
    
           NumberAxis domain = (NumberAxis) plot.getDomainAxis();
           domain.setRange(0,100);
           NumberAxis range = (NumberAxis) plot.getRangeAxis();
           range.setRange(0,120);
    
           return new ChartPanel(chart);
       }
    
       private void fillChart() {
           SwingWorker<Void, XYSeries> worker = new SwingWorker<Void, XYSeries>() {
    
               @Override
               protected Void doInBackground() throws Exception {
                   int numberOfElements = 1000;    
                   for(int y = 0; y < 12; y++) {
                       XYSeries series = new XYSeries("Plot " + y);
                       for (int x = 0; x < numberOfElements; x++) {
                           series.add(x, y*10); //add x,y point
                       }
                       publish(series);
                       Thread.sleep(100);// just for animation purpose
                   }
                   return null;              
               }
    
               @Override
               protected void process(List<XYSeries> chunks) {
                   for(XYSeries series : chunks){
                       xySeriesCollection.addSeries(series);
                   }
               }
           };
           worker.execute();
       }
    
       public static void main(String args[]) {
          SwingUtilities.invokeLater(new Runnable() {
             @Override
             public void run() {
                 new FreeChartDemo("JFreeChart #1").initGUI();
                 new FreeChartDemo("JFreeChart #2").initGUI();
             }
          });
       }
    }