We have seen that some stream classes represent endpoints: the
sources and destinations of data. For example a file can be a source
or destination via the FileInputStream
and FileOutputStream
classes. Other stream classes, such as buffer wrappers, modify,
handle, or monitor the data in some way as it passes through.
In this demo we create classes that allow histograms to become
the destination for a stream. In the next section we will look at
stream classes that filter the histogram data as it heads for its
destination.
The demonstration applet below illustrates how we can use the stream
framework not only to grab data from a disk file or send text to
a console but to move data within a program in a systematic, consistent
manner.
In the program HistStreamApplet
shown below, simulated data is sent to a histogram via an output
stream provided by a StreamedHistPanel,
which is a subclass of our HistPanel
component class. (See source code listings below.) The StreamedHistPanel
includes an inner class called HistogramOutputStream
that extends OutputStream.
The class HistogramOutputStream
overrides the write(int
b) method in OutputStream
with a method that adds data to the histogram in the StreamedHistPanel
object (it can access the histogram since it is an inner class of
StreamedHistPanel)
.
For HistogramOutputStream,
a histogram becomes the destination for the stream just as an array
is the destination for ByteArrayInputStream
or a string is the destination for StringWriter.
The applet, derived from the TimerHistFillApplet.java
example in Chapter 8: Tech :
Timers, simulates readings from three sensors using MakeSensorData
and streams the data to three corresponding StreamedHistPanel
components on the interface. The sensor readings include differing
offsets (called pedestals) and response sensitivities (slopes) as
is typical with real hardware.
Note that in our HistogramOutputStream
we use the full four bytes of the integer argument in write(int
b). In the normal use of this method in OutputStream
subclasses, only the lowest order byte would be written. If we wanted
to stream data from MakeSensorData
to other streams, such as to a FileOutputStream,
we would need to pack the sensor reading in only the lower order
byte (and thus lose precision), or into a byte array as we did in
the previous example.
In a similar way, you could make a histogram or histogram panel
into a source stream by creating an InputStream
subclass that reads data from the histogram.
We note that StreamedHistPanel
follows the example of streaming to a TextArea
given in Harold, 1999.
HistStreamApplet.java
-
This applet/application uses StreamedHistPanel objects
to display the contents of 3 Histograms that simulate
distributions of sensor data. The basic form of the
class goes as TimerHistFillApplet in Chapter
8: Tech. However, data is sent to the StreamedHistPanels
as OutputStreams.
Data is generated from an instance of MakeSensorData
in a thread to simulate the input of data events at
random times. The three sensors each produce a Gaussian
distribution of values but they are transformed by different
slopes and offsets (or pedestals) to represent variations
in the sensor hardware.
+ New classes:
StreamedHistPanel.java
-
This class extends HistPanel. It provides a HistPanel
destination for an output stream. It uses an instance
of HistPanel to display the data and to hold the data
values in an array.
It
uses an innter class called HistogramOutputStream
that extends OutputStream. An instance of this stream
object is provided with the getOutputStream() method.
Data written to theo HistogramOutputStream is added to
the histogram that is displayed on the panel.
Follows
similar pattern as the StreamedTextArea class in "Java
IO" by E.R.Harold.
MakeSensorData.java
-
Runnable class that generates a Guassian distributed random
data and write it to the HistogramOutputStream for display
on the histogram.
+
Previous classes:
Chapter
6:Tech: Histogram.java,
HistPanel.java
Chapter
6:Tech: PlotPanel.java,
PlotFormat.java
|
import
javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
/**
* This applet/application uses StreamedHistPanels
to display the contents
* of 3 Histograms to show distributions of sensor
data. The basic form
* of the class goes as TimerHistFill_JApplet11.
However, data is sent to
* the StreamedHistPanels as OutputStreams.
*
* Data is generated from an instance of MakeSensorData
in a thread
* to simulate the input of data events at random
times. The three
* sensors each produce a Gaussian distribution
of values but they
* are transformed by different slopes and offsets (or
pedestals)
* to represent variations in the sensor hardware.
**/
public class HistStreamApplet extends JApplet
implements ActionListener, Updateable
{
final static int NUM_HISTS = 3;
// Histograms for the panels.
Histogram [] fHistogram = new Histogram[NUM_HISTS];
// The HistPanel subclass that accept data streams.
StreamedHistPanel [] fHistPanel = new StreamedHistPanel[NUM_HISTS];
// Create a panel to hold the histogram sub-panels
JPanel fHistsPanel = null;
// Array of treams to the panels
OutputStream [] fDataOut = new OutputStream[NUM_HISTS];
// Constants for three channels of sensor data.
double [] fSlope = {0.6,
1.2, 1.1};
double [] fPedestal = {2.4, 1.3, 3.4};
int fNumDataPoints = 100;
boolean fMakingHist = false;
boolean fUpdateDisplay = false;
MakeSensorData fMakeData = null;
// Use the java.util Timer and TimerTask combo
// for timing events.
java.util.Timer fTimer = null;
// A text field for input strings
JTextField fTextField = null;
// Flag for whether the applet is in a browser
// or running via the main () below.
boolean fInBrowser=true;
//Buttons
JButton fGoButton = null;
JButton fClearButton = null;
JButton fExitButton = null;
public void init () {
Container content_pane = getContentPane
();
JPanel panel = new JPanel (new BorderLayout
());
// Use a textfield for an input parameter.
fTextField =
new JTextField (Integer.toString
(fNumDataPoints), 10);
// If return hit after entering text,
the
// actionPerformed will be invoked.
fTextField.addActionListener (this);
fGoButton = new JButton ("Go");
fGoButton.addActionListener (this);
fClearButton = new JButton ("Clear");
fClearButton.addActionListener (this);
fExitButton = new JButton ("Exit");
fExitButton.addActionListener (this);
JPanel control_panel = new JPanel
();
control_panel.add (fTextField);
control_panel.add (fGoButton);
control_panel.add (fClearButton);
control_panel.add (fExitButton);
// Create a panel to hold the histogram
sub-panels
fHistsPanel = new JPanel (new GridLayout
(1,3));
// Create the HistPanels and their
histograms.
for (int i=0; i < NUM_HISTS; i++)
{
fHistogram[i]
= new Histogram ("Sensor "+i,
"Data",
25,0.0,10.0);
fHistPanel[i]
= new StreamedHistPanel (fHistogram[i]);
// Add the
histogram panels to the container panel
fHistsPanel.add
(fHistPanel[i]);
// Get the
output streams for each panel.
fDataOut[i]
= fHistPanel[i].getOutputStream ();
}
makeHist ();
fGoButton.setText ("Stop");
fClearButton.setEnabled (false);
if (fInBrowser) fExitButton.setEnabled
(false);
panel.add (fHistsPanel,"Center");
panel.add (control_panel,"South");
// Add text area with scrolling to
the contentPane.
content_pane.add (panel);
} // init
public void actionPerformed (ActionEvent e) {
Object source = e.getSource ();
if (source == fGoButton || source
== fTextField) {
String str_num_data_points
= fTextField.getText ();
try {
fNumDataPoints
= Integer.parseInt (str_num_data_points);
} catch (NumberFormatException
ex) {
// Could open
an error dialog here but just
// display
a message on the browser status line.
showStatus
("Bad input value");
return;
}
if (!fMakingHist) {
makeHist ();
fGoButton.setText
("Stop");
fClearButton.setEnabled
(false);
} else { // Stop button
has been pushed
fGoButton.setText
("Go");
fClearButton.setEnabled
(true);
fMakingHist
= false;
}
} else if (source == fClearButton
) {
for (int i=0;
i < NUM_HISTS; i++)
{
fHistogram[i].clear ();}
repaint ();
}else if (!fInBrowser)
System.exit
(0);
} // actionPerformed
/** Create the histograms and start the timers.
**/
void makeHist () {
// Only allow for one data maker at
a time.
if (fMakingHist) return;
fMakingHist = true;
// Create the runnable object to fill
the histograms
// The data will simualte sensors
that return data with
// slightly different slopes and pedestals.
// The maximum delay between data
readings will be 500msecs.
fMakeData =
new MakeSensorData (this,
fDataOut, fNumDataPoints,
fSlope,
fPedestal, 500);
Thread data_thread = new Thread (fMakeData);
// Before starting the filling, create
the timer task
// that will cause the histogram display
to update
// during the filling.
// Create a timer. TimerTask created
in MakeHist ()
fTimer = new java.util.Timer ();
// Start the timer after 100ms and
then repeat calls
// to run in PaintHistTask object
every 250ms.
fTimer.schedule (new PaintHistTask
(), 100, 250);
// Now start the data filling.
data_thread.start ();
return;
} // makeHist
/**
* Use the inner class technique to
define the
* TimerTask subclass for signalling
that the display
* should be updated.
**/
class PaintHistTask extends java.util.TimerTask
{
public void run () {
fUpdateDisplay = true;
}
} // class PaintHistTask
/**
* Repaint the histogram
display and turn off the
* update display flag.
Return the makingHist flag
* to indicate if updating
should continue.
**/
public boolean update (Object obj) {
// Don't update the display until
the timer
// turns on the updateDisplay flag.
if ( fUpdateDisplay) {
// Possible this method
called before outputPanel
// created in the init
(). So check if it exists
// before attempting to
repaint.
if (fHistsPanel != null)
fHistsPanel.repaint ();
fUpdateDisplay = false;
}
return fMakingHist;
} // update
/** Called when the histogram filling is finished.
**/
public void done () {
fMakingHist = false;
// Stop the histogram display updates.
fTimer.cancel ();
// Update one last time
fHistsPanel.repaint ();
// Reset the buttons.
fGoButton.setText ("Go");
fClearButton.setEnabled (true);
} // done
public static void main (String[] args) {
int frame_width=450;
int frame_height=300;
// Create applet and add to frame.
HistStreamApplet applet = new HistStreamApplet
();
applet.fInBrowser = false;
applet.init ();
// Following anonymous class used
to close window & exit program
JFrame f = new JFrame ("Demo");
f.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
// Add applet to the frame
f.getContentPane ().add ( applet);
f.setSize (new Dimension (frame_width,frame_height));
f.setVisible (true);
} // main
} // class HistStreamApplet |
import
java.io.*;
/**
* This class provides provides a HistPanel
destination for an
* output stream. It uses an instance
of HistPanel to
* display the data and to hold the
data values in an array.
*
* Follows similar pattern as the StreamedTextArea
class in
* "Java IO" by E.R.Harold.
**/
public class StreamedHistPanel extends HistPanel
{
OutputStream fHistStream = null;
boolean fNewDataFlag = false;
/** Create the panel with the histogram. **/
public StreamedHistPanel (Histogram histogram)
{
super (histogram);
fHistStream = new HistogramOutputStream
();
} // ctor
public OutputStream getOutputStream () {
return fHistStream;
}
/**
* This internal class provides
the stream to write data.
* to the histogram destination.
The class extends the
* OutputStream so as to
use the double and float type
* write methods. When data
is written to the stream, a
* flag indicates that new
data has arrived in case the
* GUI wants to update the
display.
*
* For out needs here, we
only override the write ()
* and writeFloat () methods.
**/
class HistogramOutputStream extends OutputStream
{
/**
* In OutputStream write (int b),
only the lower byte
* of the int value is used but here
we use the full
* value
*/
public synchronized void write (int
data) {
// Convert back to double
and shift down two decimal
// places.
fHistogram.add (((double)data)/100.0);
fNewDataFlag = true;
}
} // class HistogramOutputStream
} // class StreamedHistPanel |
import
java.io.*;
/** This runnable class fills the histogram. **/
public class MakeSensorData implements Runnable
{
OutputStream [] fDataOut;
int fNumData;
Updateable fOwner;
// Maximum delay between data points with default
int fMaxDelay = 1000;
// Create an instance of the Random class for
// producing our random values.
static java.util.Random fRan = new java.util.Random
();
// Constants for three channels of sensor data.
double [] fSlope ;
double [] fPedestal;
/**
* Constructor
* @param fOwner to call back with
updates.
* @param fDataOut stream destinations
for the data.
* @param fNumData Number of data readings.
* @param fMaxDelay maximum delay in
msecs between data readings.
**/
public MakeSensorData (Updateable owner,
OutputStream [] dataOut, int numData,
double [] slope, double [] pedestal,
int maxDelay) {
fDataOut = dataOut;
fSlope = slope;
fPedestal = pedestal;
fNumData = numData;
fOwner = fOwner;
// Maximum delay time between new
data points
fMaxDelay = maxDelay;
} // ctor
/**
* Simulate data taking by generating
Gaussian distributions
* for three data channels, each with
their own fPedestal and
* fSlope.
**/
public void run () {
for (int i=0; i < fNumData; i++) {
for (int j=0; j < fDataOut.length;
j++) {
// Create some data.
Make sure it is a positive value.
double val = -1.0;
// Signal events
while (val < 0.0)
val = fRan.nextGaussian
() + 4.0;
// Simulate variations
in sensor fSlope and offsets
val = fSlope[j] * val
+ fPedestal[j];
// Shift up two decimal
places before converting
// to integer.
int ival = (int)
(100 * val);
try {
// Send data
to stream destination
fDataOut[j].write
(ival);
}
catch (IOException ioe)
{}
}
// Update the fOwner. Stop updates
if
// false returned.
if (!fOwner.update (this)) break;
// Pause for random periods between
events
try {
Thread.sleep (fRan.nextInt
(fMaxDelay));
}
catch (InterruptedException e)
{}
}
// Tell the fOwner that the data has been sent.
fOwner.done ();
} // run
} // class MakeSensorData |
References & Web
Resources
Latest update: Nov. 14, 2004
|