Tuesday, July 17, 2012

Real-time signal drawing

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:


4 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. Not quite correct. You have two big mistakes in your MVC example:

    1. 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.

    ReplyDelete
  3. Excellent example, can I use it in my project.

    ReplyDelete
  4. I dont get how to draw signals from retrieving data which is of two columns 1.sample 2.Voltage. please suggest me solution

    ReplyDelete