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
ReplyDelete