Runtime error when adding XYPlot to CombinedXYPlot

A discussion forum for JFreeChart (a 2D chart library for the Java platform).
Locked
Doug

Runtime error when adding XYPlot to CombinedXYPlot

Post by Doug » Tue Sep 10, 2002 9:06 pm

Hello,

I have been working on an appl;ication that reads messages from
a serial port (using the JavaComm package), makes calculations
from the contents of the message, and then updates a chart that
contains a Combined XY Plot with VERTICAL orientation, so that
two sub plots (each tracking a single calculated value like
a classic strip chart recorder) would be stacked from top to
bottom. I have tried to model the code after some of the examples
in the demo directory, and have also read section 9.3 (Creating
a CombinedXYPlot) in the Developer's Guide (version 0.9.3).

My problem is a runtime error that occurs when I try to use the

void add(XYPlot subplot, int weight)

member function of the CombinedXYPlot class. The runtime error I
see is as follows:

------------------------------------------------------------------------------
C:\Users\doug>java StripChartApp
Plot compatible with axis = true
subplot_0 type = XY Plot
subplot_1 type = XY Plot
plot type = Vertical MultiXYPlot
Exception in thread "main" java.lang.NullPointerException
at com.jrefinery.data.Range.combine(Unknown Source)
at com.jrefinery.chart.CombinedXYPlot.getHorizontalDataRange(Unknown Source)
at com.jrefinery.chart.HorizontalNumberAxis.autoAdjustRange(Unknown Source)
at com.jrefinery.chart.HorizontalNumberAxis.configure(Unknown Source)
at com.jrefinery.chart.CombinedXYPlot.add(Unknown Source)
at StripChartApp.<init>(StripChartApp.java:211)
at StripChartApp.main(StripChartApp.java:93)
------------------------------------------------------------------------------
I have included the sample code below. There are three files:

1) RealTimeValues.java (uses a message received from the serial port
to calculate and store meaningful values.)

2) UpdateThread.java (Reads the messages from the serial port and
then calls an update function in StripChartApp.java
to update the datasets used for the plots.)

3) StripChartApp.java (The main part of the application that defines the
datasets, plot and subplots, and starts the thread
that reads the serial port).

NOTE: The piece of code in StripChartApp.java that actually updates
the datasets with the calculated values has not been written yet.
Currently I have a comment as a placeholder where this code will go.

The JavaComm stuff is working perfectly. The problem is in the file
StripChartApp.java, where I try to add the subplots to the plot.

In the code I have even checked (using System.out.println) to make
sure that the CombinedXYPlot that I am using is compatible with the
horizontal axis I used. I also check the types of the plot and sub
plots. In the code shown below, I have commented out the following
two offending lines, either of which results in a runtime error:

// plot.add(subplot_0, 1);
// plot.add(subplot_1, 1);

If anyone can tell me what I might be doing wrong, I would be very
grateful.

Thanks,
Doug

------------------- start StripChartApp.java -----------------------
import com.jrefinery.chart.*;
import com.jrefinery.data.*;
import com.jrefinery.ui.*;

import java.util.*;
import java.io.*;

//////////////////////////////////////////////////////
// Comment out one of the follling lines. Which one //
// will depend on the operating system being used. //
//////////////////////////////////////////////////////

//import gnu.io.*; // Linux
import javax.comm.*; // Windows

public class StripChartApp extends ApplicationFrame
{
static CommPortIdentifier portId;
static Enumeration portList;
static InputStream inputStream;
static SerialPort serialPort;

XYSeries series_0;
XYSeries series_1;

XYSeriesCollection dataset_0;
XYSeriesCollection dataset_1;

CombinedDataset all_data;

XYPlot subplot_0;
XYPlot subplot_1;

NumberAxis timeAxis;
CombinedXYPlot plot;

RealTimeValues rtv;

//
// Starting point for the application.
//
public static void main(String[] args)
{
////////////////////////////////////////////////////////
// List all of the ports on the computer, and stop //
// when the serial port you are using has been found. //
////////////////////////////////////////////////////////
portList = CommPortIdentifier.getPortIdentifiers();

while (portList.hasMoreElements())
{
portId = (CommPortIdentifier) portList.nextElement();

if (portId.getPortType() == CommPortIdentifier.PORT_SERIAL)
{
//////////////////////////////////////////////////////
// Comment out one of the follling lines. Which one //
// will depend on the operating system being used. //
//////////////////////////////////////////////////////
if (portId.getName().equals("COM1")) // Windows
// if (portId.getName().equals("/dev/ttyS0")) // Linux
{
break;
}
}
}

///////////////////////////////////////////////
// Open the serial port, get an input stream //
// used for reading the bytes from the port, //
// and set some port parameters. //
///////////////////////////////////////////////
try {
serialPort = (SerialPort) portId.open("SimpleReadApp", 2000);
} catch (PortInUseException e) {}

try {
inputStream = serialPort.getInputStream();
} catch (IOException e) {}

try {
serialPort.setSerialPortParams(9600,
SerialPort.DATABITS_8,
SerialPort.STOPBITS_1,
SerialPort.PARITY_NONE);

serialPort.setFlowControlMode(SerialPort.FLOWCONTROL_NONE);
} catch (UnsupportedCommOperationException e) {}

///////////////////////////////
// Start up the application. //
///////////////////////////////
StripChartApp app = new StripChartApp("Strip Chart Application");
app.pack();
app.setVisible(true);
}

/////////////////////////////////////////////////////////////
// This function gets called by the thread that is reading //
// messages from the serial port. This function uses the //
// message to create an object that holds values of //
// interest, and updates the datasets used for the strip //
// charts. It also dumps the calculated values to stdout. //
// //////////////////////////////////////////////////////////
public void updateStripChart(String message)
{
///////////////////////////////////////////////////////
// Make an object from this line that represents all //
// of the real-time values of interest. Then print //
// the values to the screen delimited by commas. //
///////////////////////////////////////////////////////
rtv = new RealTimeValues(message);

////////////////////////////////////////////////////////
// This is where we update the datasets for the strip //
// charts using values stored temporarily in the Real //
// Time Values object (rtv). //
////////////////////////////////////////////////////////


// Not impelmented yet!!


////////////////////////////////////
// Dump the values to the screen. //
////////////////////////////////////
rtv.dumpValues(System.out);
}

//
// A strip chart application for tracking data on a serial port.
//
public StripChartApp(String title)
{
///////////////////////////////////////////////////////////
// Put the application's title at the top of the window. //
///////////////////////////////////////////////////////////
super(title);

/////////////////////////////////////////
// Create chart title and axis labels. //
/////////////////////////////////////////
String subtitleStr = "w/ Brake Values";
String domain = "Seconds";
String[] ranges = {"Motor Temp", "Throttle Pos"};

////////////////////////////////////////////
// Create one data series for each chart. //
////////////////////////////////////////////
series_0 = new XYSeries("Motor Temp");
series_1 = new XYSeries("Throttle Pos");

/////////////////////////////////////
// Create a dataset for each chart //
// using the chart's data series. //
/////////////////////////////////////
dataset_0 = new XYSeriesCollection(series_0);
dataset_1 = new XYSeriesCollection(series_1);

//////////////////////////////////////////////////////////////////
// Make a Combined dataset that will hold all strip chart data. //
//////////////////////////////////////////////////////////////////
all_data = new CombinedDataset();

all_data.add(dataset_0);
all_data.add(dataset_1);

////////////////////////////////////////////
// Create a vertical axis for each chart. //
////////////////////////////////////////////
VerticalNumberAxis vert_axis_0 = new VerticalNumberAxis();
VerticalNumberAxis vert_axis_1 = new VerticalNumberAxis();

///////////////////////////////////////////
// Set the range for each vertical axis. //
///////////////////////////////////////////
vert_axis_0.setRange(100.0, 400.0);
vert_axis_1.setRange(0, 10);

///////////////////////////////////////////
// Create a new XYPlot for each dataset. //
///////////////////////////////////////////
subplot_0 = new XYPlot(dataset_0, null, vert_axis_0);
subplot_1 = new XYPlot(dataset_1, null, vert_axis_1);

///////////////////////////////////////////
// Create a common horizontal time axis. //
///////////////////////////////////////////
NumberAxis timeAxis = new HorizontalNumberAxis(domain);
timeAxis.setTickMarksVisible(true);
timeAxis.setAutoRangeIncludesZero(false);
timeAxis.setCrosshairVisible(false);

//////////////////////////////////////////////////
// Make a CombinedXYPlot that will hold all //
// strip charts using the time axis from above. //
//////////////////////////////////////////////////
plot = new CombinedXYPlot(timeAxis, CombinedXYPlot.VERTICAL);

System.out.println("Plot compatible with axis = " +
plot.isCompatibleDomainAxis(timeAxis));

////////////////////////////////////////////////////////////
// Add the subplots for the charts to the CombinedXYPlot. //
////////////////////////////////////////////////////////////

System.out.println("subplot_0 type = " + subplot_0.getPlotType());
System.out.println("subplot_1 type = " + subplot_1.getPlotType());
System.out.println("plot type = " + plot.getPlotType());

// plot.add(subplot_0, 1);
// plot.add(subplot_1, 1);

/////////////////////////////////////////////
// Make sure the charts are not too close. //
/////////////////////////////////////////////
plot.setGap(20.0);

////////////////////////////////////////
// Now use the CombinedXYPlot to make //
// a Horizontally Combined Chart. //
////////////////////////////////////////
JFreeChart chart = new JFreeChart(title,
JFreeChart.DEFAULT_TITLE_FONT,
plot,
true);

///////////////////////////////////////////
// Associate the entire set of data with //
// the Horizontally Combined Chart. //
///////////////////////////////////////////
plot.setDataset(all_data);

//////////////////////////////////////////
// Setup thread to update base Dataset. //
//////////////////////////////////////////
UpdateThread update = new UpdateThread(this,
inputStream,
serialPort);

Thread thread = new Thread(update);
thread.start();

/////////////////////////////////
// Put the chart into a panel. //
/////////////////////////////////
ChartPanel chartPanel = new ChartPanel(chart);
this.setContentPane(chartPanel);
}
}
-------------------- end StripChartApp.java
-----------------------

------------------- start UpdateThread.java -----------------------
import java.io.*;
import java.util.*;
import com.jrefinery.ui.ApplicationFrame;

//////////////////////////////////////////////////////
// Comment out one of the follling lines. Which one //
// will depend on the operating system being used. //
//////////////////////////////////////////////////////

//import gnu.io.*; // Linux
import javax.comm.*; // Windows

public class UpdateThread implements Runnable,
SerialPortEventListener
{
StripChartApp app;
InputStream inputStream;
String message;

public UpdateThread(StripChartApp app,
InputStream is,
SerialPort serialPort)
{
this.app = app;
this.inputStream = is;

//////////////////////////////////////////////
// Make sure that we are notified when data //
// becomes available on the serial port. //
//////////////////////////////////////////////
try {
serialPort.addEventListener(this);
} catch (TooManyListenersException e) {}

serialPort.notifyOnDataAvailable(true);
}

public void run()
{
while (true)
{
try
{
Thread.sleep(1000);

//////////////////////////////////////////////////
// Call a function that will take the message //
// received from the serial port and update the //
// datasets used for the strip charts. //
//////////////////////////////////////////////////
app.updateStripChart(message);
} catch (Exception e) { }
}
}

public void serialEvent(SerialPortEvent event)
{
//////////////////////////////////////////////////////////
// Create a StringBuffer and int to receive input data. //
//////////////////////////////////////////////////////////
StringBuffer inputBuffer = new StringBuffer();
int newData = 0;

///////////////////////////////////////////////
// Determine type of event. Throw away every //
// event except for the one that indicates //
// data is available on the serial port. //
///////////////////////////////////////////////
switch (event.getEventType())
{
case SerialPortEvent.BI:
case SerialPortEvent.OE:
case SerialPortEvent.FE:
case SerialPortEvent.PE:
case SerialPortEvent.CD:
case SerialPortEvent.CTS:
case SerialPortEvent.DSR:
case SerialPortEvent.RI:
case SerialPortEvent.OUTPUT_BUFFER_EMPTY:
break;
case SerialPortEvent.DATA_AVAILABLE:
while (newData != -1)
{
try
{
////////////////////////////////////////////////////
// Try to read another byte from the serial port. //
////////////////////////////////////////////////////
newData = inputStream.read();

//////////////////////////////////
// If there are no more bytes //
// in the stream, then get out. //
//////////////////////////////////
if (newData == -1)
{
break;
}

////////////////////////////////////////////////////
// The End of Message token is a carriage return. //
////////////////////////////////////////////////////
if ('\r' == (char)newData)
{
break;
}
else
{
/////////////////////////////////////////////////////
// Stick another byte onto the end of the message. //
/////////////////////////////////////////////////////
inputBuffer.append((char)newData);
}
}
catch (IOException ex)
{
System.err.println(ex);
return;
}
}

///////////////////////////////////////////
// Put the message into a String object. //
///////////////////////////////////////////
message = new String(inputBuffer);
break;
}
}
}
-------------------- end UpdateThread.java -------------------------

------------------- start RealTimeValues.java ----------------------
import java.io.*;
import java.util.*;

public class RealTimeValues
{
private float _motorTemp;
private float _ambientTemp;
private float _barometricPressure;
private int _throttlePos;
private int _operatingMode;
private float _fieldAmpsBrake;
private float _armatureAmpsBrake;
private float _armatureAmpsPower;
private float _armatureVoltsPower;

public float getMotorTemp() { return _motorTemp; }
public float getAmbientTemp() { return _ambientTemp; }
public float getBarometricPressure() { return _barometricPressure; }
public int getThrottlePos() { return _throttlePos; }
public int getOperatingMode() { return _operatingMode; }
public float getFieldAmpsBrake() { return _fieldAmpsBrake; }
public float getArmatureAmpsBrake() { return _armatureAmpsBrake; }
public float getArmatureAmpsPower() { return _armatureAmpsPower; }
public float getArmatureVoltsPower() { return _armatureVoltsPower; }

public void setMotorTemp(float mt) { _motorTemp = mt; }
public void setAmbientTemp(float at) { _ambientTemp = at; }
public void setBarometricPressure(float bp) { _barometricPressure = bp; }
public void setThrottlePos(int tp) { _throttlePos = tp; }
public void setOperatingMode(int om) { _operatingMode = om; }
public void setFieldAmpsBrake(float fab) { _fieldAmpsBrake = fab; }
public void setArmatureAmpsBrake(float aab) { _armatureAmpsBrake = aab; }
public void setArmatureAmpsPower(float aap) { _armatureAmpsPower = aap; }
public void setArmatureVoltsPower(float avp) { _armatureVoltsPower = avp; }

protected int ah2int(String s)
{
/////////////////////////////////////////////////////
// Converts an Alphaheximal digit to an integer. //
// Alphaheximal representation is similar to a //
// Hexidecimal representation, except that 0 - 15 //
// are representated by A - P. Thus AA = 0, AB = 1 //
// BA = 16, PP = 255, etc. //
/////////////////////////////////////////////////////
return (((s.charAt(0) - 'A') << 4) | (s.charAt(1) - 'A'));
}

public void dumpValues(PrintStream ps)
{
ps.print(_motorTemp + ",");
ps.print(_ambientTemp + ",");
ps.print(_barometricPressure + ",");
ps.print(_throttlePos + ",");
ps.print(_operatingMode + ",");
ps.print(_fieldAmpsBrake + ",");
ps.print(_armatureAmpsBrake + ",");
ps.print(_armatureAmpsPower + ",");
ps.println(_armatureVoltsPower);
}

public RealTimeValues() // Default Constructor.
{
_motorTemp = 0.0f;
_ambientTemp = 0.0f;
_barometricPressure = 0.0f;
_throttlePos = 0;
_operatingMode = 0;
_fieldAmpsBrake = 0.0f;
_armatureAmpsBrake = 0.0f;
_armatureAmpsPower = 0.0f;
_armatureVoltsPower = 0.0f;
}

public RealTimeValues(String msg)
{
/////////////////////////////////////////////////////////
// First strip off the start of message character '@'. //
/////////////////////////////////////////////////////////
msg = msg.substring(1);

////////////////////////////////////////////
// Make an object from this line that can //
// be parsed easily by extracting tokens. //
////////////////////////////////////////////
StringTokenizer stok = new StringTokenizer(msg, ",");

///////////////////////////////////////////////////////////////
// The Motor Temperature is calculated from the first two //
// tokens in the message. The first token is the integer //
// portion (ip) of the temperature, and the second token is //
// the fractional portion (fp) of the temeperature scaled by //
// 256. Thus the Motor Temperature is calculated as follows: //
// //
// Motor Temperature = ip + (fp/256) //
// //
// After we determine the Ambient Temperature (AT), we will //
// have to readjust this calculation by adding the AT. //
///////////////////////////////////////////////////////////////
_motorTemp = ah2int(stok.nextToken()) +
(ah2int(stok.nextToken())/256.0f);

//////////////////////////////////////////////////////////////
// The Ambient Temperature is contained in the third token, //
// but is biased by 128 and so to get the actual reading, //
// we have to subtract 128 from the converted value. //
//////////////////////////////////////////////////////////////
_ambientTemp = ah2int(stok.nextToken()) - 128.0f;

/////////////////////////////////////////////
// Now readjust the Motor Temperature..... //
/////////////////////////////////////////////
_motorTemp += _ambientTemp;

//////////////////////////////////////////////////////
// The fourth token contains the Throttle Position. //
//////////////////////////////////////////////////////
_throttlePos = ah2int(stok.nextToken());

//////////////////////////////////////////////////
// The fifth token contains the Operating Mode. //
//////////////////////////////////////////////////
_operatingMode = ah2int(stok.nextToken());

//////////////////////////////////////////////////
// The sixth token is an A/D conversion of the //
// Motor Field Amps (active in BRAKE), where //
// each bit represents 4.01 amps. //
//////////////////////////////////////////////////
_fieldAmpsBrake = 4.01f * ah2int(stok.nextToken());

////////////////////////////////////////////////////
// The seventh token is an A/D conversion from a //
// Barometric Pressure sensor where the pressure //
// in KPa = (sensor_reading * 0.434) + 10.5556. //
////////////////////////////////////////////////////
_barometricPressure = (ah2int(stok.nextToken())) * .434f + 10.5556f;

//////////////////////////////////////////////////
// The eighth token is an A/D conversion of the //
// Motor Armature Amps (active in BRAKE), where //
// each bit represents 3.00 amps. //
//////////////////////////////////////////////////
_armatureAmpsBrake = 3.00f * ah2int(stok.nextToken());

//////////////////////////////////////////////////
// The ninth token is an A/D conversion of the //
// Motor Field Amps (active in POWER), where //
// each bit represents 6.05 amps. //
//////////////////////////////////////////////////
_armatureAmpsPower = 6.05f * ah2int(stok.nextToken());

//////////////////////////////////////////////////
// The tenth token is an A/D conversion of the //
// Motor Field Volts (active in POWER), where //
// each bit represents 5.002 volts. //
//////////////////////////////////////////////////
_armatureVoltsPower = 5.002f * ah2int(stok.nextToken());
;
}
}
-------------------- end RealTimeValues.java ----------------------

David Gilbert

Re: Runtime error when adding XYPlot to CombinedXYPlot

Post by David Gilbert » Tue Sep 10, 2002 11:23 pm

Can you confirm that you are using JFreeChart 0.9.3? There was a bug in the previous release that would most likely explain this. The bug was in the combine(...) method of the com.jrefinery.data.Range class, it didn't work when both arguments were null. But it was fixed for 0.9.3.

Regards,

DG.

Doug

Re: Runtime error when adding XYPlot to CombinedXYPlot

Post by Doug » Wed Sep 11, 2002 1:42 am

David,

Wow!! I was using version 0.9.2 and after downloading version 0.9.3
my code is now working. I have to tell you that I am very impressed with
this product. More than that, your quick response and solution to my
problem is exciting.

Thank you SO MUCH.
Doug

btahlor

Re: Runtime error when adding XYPlot to CombinedXYPlot

Post by btahlor » Wed Oct 02, 2002 7:45 am

Hi,

I read through your code and I am looking for an example on "how to update the dataset in a subplot"

I can easly update a simple single plot with

-----------------------------------------------------
.
.
.

XYPlot plot2 = chartC.getXYPlot();
Axis axis2 = plot2.getRangeAxis();
axis2.setLabel("Price in ($)");

/// Change Vertical Scaling
VerticalNumberAxis Vaxis2 = (VerticalNumberAxis)plot2.getRangeAxis();
Vaxis2.setAutoRangeIncludesZero(false);

// convert to proper dataset
HighLowDataset data1 = createHiLoDataX(days,allData,str1);

// Update plot with new data
chartC.getPlot().setDataset(data1);
XYPlot plot2 = chartC.getXYPlot();

.
.
.
---------------------------------------------------------
but I am having a problem updating subplots of a Combined plot!!!


Can you please send me the "missing" section of your example

thanks

barry

Locked