Search code examples
javagraphicspaintpaintcomponent

How to achieve non-destructive painting in Java?


I want to plot many sinusoids with different frequencies on a panel, but because painting is destructive, I cannot achieve this. I can only see the last sinusoid plotted.

In the code, the generateSinus() method is called only three times, but it might be called many times and I don't want to call g.fillOval() method for each sinusoid in the paintComponent() method.

Is there a way to achieve non-destructive painting and see all sinusioids with different frequencies?

Please see the code below:

package testdrawsinus;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;

import javax.swing.JFrame;
import javax.swing.JPanel;

public class TestDrawSinus extends JPanel 
{
   private static double[] x;
   private static double[] y;
   private static boolean buttonClicked = false;
   private static JPanel panel = new TestDrawSinus();
   
   @Override
   public void paintComponent(Graphics g) 
   {
      super.paintComponent(g);
      g.setColor(Color.GREEN);
      if (buttonClicked)
      {          
          for (int i=0; i<x.length; i++)
          {                   
             g.fillOval((int)x[i] + panel.getWidth()/2, -1*((int)y[i]) + panel.getHeight()/2, 10, 10);                          
          }
          buttonClicked = false;
      }
   }

   private static void generateSinus(int freq)
   {
      x = new double[200];
      y = new double[200];
      for (int i=0; i<=199; i++)
      {
         x[i]= (double)i;
         y[i] = 100*Math.sin(2*Math.PI*freq*i/200);
              
      }
   }

   public static void main(String[] args) 
   {
      JFrame frame = new JFrame();  
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.setLayout(null);
      frame.setSize(800, 600);
      frame.setLocationRelativeTo(null);
      frame.setVisible(true);

      panel.setLayout(null);
      panel.setBounds(20,20, 700,400);
      panel.setBackground(Color.BLACK);
      frame.add(panel);

      JButton button1 = new JButton();
      button1.setText("plot");
      button1.setBounds(300, 500, 150, 50);
      frame.add(button1);

      button1.addActionListener(new ActionListener() 
      {
         @Override
         public void actionPerformed(ActionEvent e) 
         {                   
           buttonClicked = true; 
           generateSinus(1);
           panel.repaint();
           generateSinus(2);
           panel.repaint();
           generateSinus(3);
           panel.repaint();
         }
               
      });              
   }
}

Thanks for your help.


Solution

  • There are a number of ways you might do this, one way would be to seperate each series into its own model, then have the panel paint each model, for example...

    enter image description here

    import java.awt.BorderLayout;
    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.EventQueue;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.awt.geom.Ellipse2D;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Collections;
    import java.util.List;
    import java.util.Random;
    import javax.swing.JButton;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    
    public class Main {
        public static void main(String[] args) {
            new Main();
        }
    
        private Color[] masterColors = new Color[]{
            Color.BLACK, Color.BLUE, Color.CYAN, Color.DARK_GRAY,
            Color.GREEN, Color.MAGENTA, Color.ORANGE, Color.PINK,
            Color.RED, Color.WHITE, Color.YELLOW
        };
    
        public Main() {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    TestDrawSinus sinusPane = new TestDrawSinus();
    
                    JFrame frame = new JFrame();
                    frame.add(sinusPane);
    
                    JButton button1 = new JButton();
                    button1.setText("plot");
                    frame.add(button1, BorderLayout.SOUTH);
    
                    button1.addActionListener(new ActionListener() {
                        private List<Color> colors = new ArrayList<>();
                        private int freq = 0;
    
                        @Override
                        public void actionPerformed(ActionEvent e) {
                            sinusPane.addSinusPlot(generateSinus(++freq, nextColor()));
                            sinusPane.addSinusPlot(generateSinus(++freq, nextColor()));
                            sinusPane.addSinusPlot(generateSinus(++freq, nextColor()));
                        }
    
                        protected Color nextColor() {
                            if (colors.isEmpty()) {
                                colors.addAll(Arrays.asList(masterColors));
                                Collections.shuffle(colors);
                            }
                            return colors.remove(0);
                        }
    
                    });
    
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                }
            });
        }
    
        private Random rnd = new Random();
    
        private SinusPlot generateSinus(int freq, Color color) {
            double[] x = new double[200];
            double[] y = new double[200];
            for (int i = 0; i < 200; i++) {
                x[i] = (double) i;
                y[i] = 100 * Math.sin(2d * Math.PI * freq * i / 200d);
            }
            return new SinusPlot(x, y, color);
        }
    
        public class SinusPlot {
            private double[] x;
            private double[] y;
            private Color color;
    
            public SinusPlot(double[] x, double[] y, Color color) {
                this.x = x;
                this.y = y;
                this.color = color;
            }
    
            public int getSize() {
                return x.length;
            }
    
            public double getXAt(int index) {
                return x[index];
            }
    
            public double getYAt(int index) {
                return y[index];
            }
    
            public Color getColor() {
                return color;
            }
    
        }
    
        public class TestDrawSinus extends JPanel {
            private List<SinusPlot> plots = new ArrayList<>(8);
    
            public void addSinusPlot(SinusPlot plot) {
                plots.add(plot);
                repaint();
            }
    
            @Override
            public Dimension getPreferredSize() {
                return new Dimension(800, 600);
            }
    
            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                Graphics2D g2d = (Graphics2D) g.create();
                for (SinusPlot plot : plots) {
                    System.out.println(plot.getColor());
                    g2d.setColor(plot.getColor());
                    for (int i = 0; i < plot.getSize(); i++) {
                        Ellipse2D dot = new Ellipse2D.Double(((getWidth() - plot.getSize()) / 2) + plot.getXAt(i), plot.getYAt(i) + getHeight() / 2, 10, 10);
                        g2d.fill(dot);
                    }
                }
                g2d.dispose();
            }
        }
    }
    

    null layouts aren't going to help you, take the time to learn understand how the layout management system works, it will save you lot of time and effort. See Laying Out Components Within a Container for more details.

    static is not your friend (especially in this context), make the effort to understand how to live without it (and when to use it)