I want to introduce simple Java program that performs real-time signal drawing. It based on Model-View-Controller (MVC) framework. I wrote a post about  MVC usage by very simple example.
At first, we create a Buffer class, where incoming samples (came from some abstract source) are stored. The idea of how Buffer handles data is shown on Fig.1. The iWrite (iW) index points to cell were new sample is stored. Denote this sample s[0]. The sample that has came before s[0] becomes s[-1], sample before it becomes s[-2] and so on. After new sample saved, the iRead (iR) index increments subsequently to read all samples from newest to oldest to redraw signal on screen. We increment iR to read from s[0] to s[-6]. Repeat that procedure we draw the signal that slide along the screen from right to left.
|  | 
| Fig. 1 | 
The Buffer class code is shown below. It has two public methods: put() to put new sample into buffer and get() to get samples subsequently begin from newest sample. Inside this methods the border checking is performed.  
| 
package
  com.blogspot.shulgadim.drawsignal.model; 
public class Buffer
  { 
    private int N;    
    private int iWrite = 0; 
    private int iRead = 0; 
    private double buffer[];        
    private  double sample; 
    public
  Buffer(int N){             
       this.N =
  N;      
       init(); 
    } 
    private void
  init(){                       
      iRead = 0; 
      iWrite =
  0;      
       buffer = new double[N]; 
    } 
    public void put(double
  newSample){ 
      buffer[iWrite] =
  newSample; 
      iRead = iWrite; 
      iWrite++; 
      if(iWrite == N){ 
          iWrite = 0; 
       } 
    } 
    public double get(){ 
      sample = buffer[iRead]; 
      iRead--; 
      if(iRead<0){ 
          iRead = N-1; 
      }      
      return sample; 
    } 
    public int
  getLength(){ 
       return N; 
    } 
} | 
The next class is Model class which imitates how we get out samples of signals. In our case it simply generates the noisy sine wave. 
| 
package
  com.blogspot.shulgadim.drawsignal.model; 
public class Model
  {  
     private int counter = 0;        
     public double
  getSignalSample(){           
          counter++; 
          return
  50*(Math.sin(2*3.14*100*counter) +  
                      0.5*(Math.random()-0.5));           
     }     
} | 
The next step is create window where the signal will be drawn. I used Swing library, so just create View class that extends JFrame class of Swing. Inside it has the signalPanel object. The signalPanel object is used to draw signal. We set title of frame, size, add signalPanel to frame and make it visible.
| 
package
  com.blogspot.shulgadim.drawsignal.view; 
import
  javax.swing.JFrame; 
import
  com.blogspot.shulgadim.drawsignal.model.Buffer; 
public class View extends
  JFrame{              
    private
  SignalPanel signalPanel; 
    public
  View(){                       
        setTitle("Draw
  real-time signal");        
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
        setSize(400, 200);         
        signalPanel = new
  SignalPanel();                   
        getContentPane().add(signalPanel);                 
        setVisible(true);          
    } 
    public
  SignalPanel getSignalPanel(){ 
        return signalPanel; 
    } 
} | 
Look closer to SignalPanel class. It extends JPanel class. The public method drawData(Buffer buffer) is heart of this class. It gets the buffer as input argument and draw samples from newest (where iW stops at that moment ) as lines sequence by calling graphics2d.draw() method. It draws data in bufferedImage object. After that is calls repaint() method to force signalPanel redraw itself  by calling paintComponent() method.
| 
package
  com.blogspot.shulgadim.drawsignal.view; 
import
  java.awt.*; 
import
  javax.swing.*; 
import
  java.awt.image.*; 
import
  java.awt.geom.Line2D; 
import
  com.blogspot.shulgadim.drawsignal.model.Buffer; 
public class
  SignalPanel extends JPanel { 
     private
  BufferedImage bufferedImage;  
     private
  Graphics2D graphics2d;         
     private int width; 
     private int height;   
     private double sampleOld; 
    @Override 
    protected void
  paintComponent(Graphics graphics) { 
        super.paintComponent(graphics);     
        if (bufferedImage == null) { 
          initBuffer(); 
        } 
        graphics.drawImage(bufferedImage, 0, 0,
  this); 
    } 
    private void
  initBuffer(){ 
        width =
  getWidth(); 
        height =
  getHeight();                       
        bufferedImage = new
  BufferedImage(width, height,  
               BufferedImage.TYPE_INT_ARGB);                         
        graphics2d = bufferedImage.createGraphics(); 
         graphics2d.setBackground(Color.WHITE);   
         graphics2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
                                  RenderingHints.VALUE_ANTIALIAS_ON);   
    } 
    public void
  drawData(Buffer buffer){        
     graphics2d.setColor(Color.DARK_GRAY);      
     graphics2d.clearRect(0,
  0, width, height);        
    
     for(int i=0;
  i<buffer.getLength(); i++){ 
          double sample
  = buffer.get();                            
          graphics2d.draw(new
  Line2D.Double( 
                           buffer.getLength()-i,
   
                           sampleOld +height/2,                                                buffer.getLength()-(i+1),  
                           sample+ height/2));                             
          
  sampleOld = sample;                        
     }      
     repaint();             
    }   
   
} | 
The Controller method manages all  classes we created before. It takes model and view as input arguments in constructor, creates buffer (that contains 400 samples) and timer to tick the processing() method every 30 ms. In processing() method we get new sample from model.getSignalSample() method. Then we put it in buffer b and after that call drawData(b) from view object to redraw the signal.
 
| 
package
  com.blogspot.shulgadim.drawsignal.controller; 
import
  javax.swing.Timer; 
import
  com.blogspot.shulgadim.drawsignal.model.Buffer; 
import
  com.blogspot.shulgadim.drawsignal.model.Model; 
import
  com.blogspot.shulgadim.drawsignal.view.View; 
import
  java.awt.event.*; 
public class
  Controller { 
     private Timer timer; 
     private Model model; 
     private View view; 
     private Buffer
  b; 
     public
  Controller(Model model, View view){                      
          this.model =
  model; 
          this.view =
  view; 
          this.b = new
  Buffer(400);;            
          timer = new
  Timer(30, new ActionListener(){ 
                public void
  actionPerformed(ActionEvent e){ 
                     processing(); 
         } 
     });        
     timer.start();             
     }     
     private void
  processing(){ 
          double sample
  = model.getSignalSample(); 
          b.put(sample); 
          view.getSignalPanel().drawData(b); 
     } 
} | 
At the end to run out classes we write class Main which creates model, view, and controller objects to assemble program our MVC-framework program. 
 
| 
package
  com.blogspot.shulgadim.drawsignal; 
import
  javax.swing.*; 
import com.blogspot.shulgadim.drawsignal.model.*; 
import
  com.blogspot.shulgadim.drawsignal.view.View; 
import
  com.blogspot.shulgadim.drawsignal.controller.Controller; 
public class Main { 
    public static void
  main(String[] args) {           
        SwingUtilities.invokeLater(new
  Runnable() { 
            @Override 
            public void run()
  {                                  
                Model
  model = new Model();                 
                View
  view = new View();               
                new
  Controller(model, view);                                      
            } 
        });                           
    } 
} | 
The next movie shows program in action:

 
This comment has been removed by the author.
ReplyDeleteNot quite correct. You have two big mistakes in your MVC example:
ReplyDelete1. The thing you named controller “knows” about your view (you insert your view in your controller's constructor). The whole idea of the MVC paradigm is to separate business logic from UI stuff and your example blows it.
2. The thing you named model does business logic (it generates data). Business logic should be in your controllers (ideally, in the service layer of your controllers). Model should NOT do any logic. Typically, model is JavaBeans or collections of JavaBeans.
Considering all this, you can now deduce the following:
1. Your view is a VIEW – no errors here.
2. Your controller works more like a part of the outer container, which transits data from controllers to views.
3. Your model is more of a service layer of the controller, so your model is a CONTROLLER.
4. The real MODEL in your example is Buffer class.
These mistakes were made because the example you created does not truly need to follow MVC paradigm to function: it is a desktop single-user application and MVC is more of web-application stuff.
Let me provide you with another example – a servlet-based web-application, which could follow the MVC paradigm if constructed properly, e.g.:
- your servlets are CONTROLLERS, if they will do business logic.
- your JSP pages will be your VIEWS, if you will NOT do any business logic in them.
- any data-beans you will transfer inside you HttpServletRequest objects will be your MODEL
- your servlet container will be your CONTAINER, which will transfer data from servlets to JSP pages according to the provided web.xml.
You can also take a look at Spring MVC (it is a part of the Spring framework) – a very cool implementation of the MVC paradigm.
Excellent example, can I use it in my project.
ReplyDeleteI dont get how to draw signals from retrieving data which is of two columns 1.sample 2.Voltage. please suggest me solution
ReplyDeleteMany parents highly recommend Kanchi Kamakoti CHILD's Trust Hospital for pediatric neurology care. Their team of best pediatric neurologists in chennai is experienced in handling complex cases with care and precision.
ReplyDelete