Overlaying IntervalBarChart with TimeSeries

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

Overlaying IntervalBarChart with TimeSeries

Post by Paul Ramirez » Sat Nov 02, 2002 12:28 am

This is actually a reposting. I'm still not sure how to overlay the graphs descibed below. Any help would be greatly appreciated. Even just ideas of what to do would be useful.

ORIGINAL POST:

Is there a way to overlay an interval bar chart with a time series using the current 0.9.4 API? I am not sure it's possible because the BasicTimeSeries class only implements one a period and value as a parameter. The closest thing I could find in the demo code is the OverlaidXYPlotDemo. The only problem with that one is it's not an interval bar chart.

I've thought of using the DefaultHighLowDataset but I'm not sure. All that I would need from that class would be the open/close values.

What I want to plot is blood pressures (systolic/diastolic); which is sort of like open/close.



ANSWER:

I think you can use the IntervalXYDataset interface to represent the structure of your data, the only problem is that there is not a default implementation of the interface yet. The TimeSeriesCollection class does implement it, but only in a restricted way (designed for displaying simple bar charts of time series values).

If you can supply a demo dataset, I can take a look at what is required (my guess is that you are after something like a candlestick chart but without the small lines to the high and low values). Overlaying the line chart is simple once you have a renderer that can draw the interval bar chart.


MY REPLY:

Series 1 (Heart Rate)
126, 129, 123, 130, 126, 125, 123, 120, 116, 118

Series 2 (Respiratory Rate)
33, 31, 29, 31, 34, 37, 37, 25, 32, 28

Series 3 (Blood Pressure [systolic/diastolic])
123/89, 135/75, 146/82, 152/93, 148/95, 157/97, 159/96, 151/93, 146/89, 142/88

The above measurements were taken at the following times:
8:00 am, 8:15 am, 8:30 am, 8:45 am, 9:00 am, 9:15 am, 9:30 am, 9:45 am, 10:00 am, 10:15 am


For series 1&2, I would like a line graph for each. For series 3, I would like the points plotted with a veritcal line connecting them.

The graph is for doctors and I think that not only do the points matter but also the difference between them matter for blood pressure.

Given the above data:
At 8:00 am there was a heart rate of 126, a respiratory rate of 33, and a blood pressure of 123/89 (123 should be connected to 89 with a vertical line)


Thanks in advance,
Paul Ramirez

Dave Gilbert

Re: Overlaying IntervalBarChart with TimeSeries

Post by Dave Gilbert » Mon Nov 04, 2002 1:06 pm

Hi Paul,

I hope to be able to put together a small demo app for this, later today or early tomorrow.

Regards,

DG.

Dave Gilbert

Re: Overlaying IntervalBarChart with TimeSeries

Post by Dave Gilbert » Tue Nov 05, 2002 1:24 pm

Here's the demo program, and following it a new renderer (YIntervalRenderer) that the demo uses. I have this running in my (modified) source tree, I hope the code will run against the 0.9.4 release, but it might not. I will commit all my changes to CVS soon.

Regards,

DG

P.S. The demo uses a very inflexible implementation of the IntervalXYDataset interface for the dataset. Some work will be required here to make a general purpose class.

package com.jrefinery.chart.demo.premium;

import java.awt.Color;
import com.jrefinery.chart.JFreeChart;
import com.jrefinery.chart.ChartFactory;
import com.jrefinery.chart.XYPlot;
import com.jrefinery.chart.OverlaidXYPlot;
import com.jrefinery.chart.XYItemRenderer;
import com.jrefinery.chart.StandardXYItemRenderer;
import com.jrefinery.chart.ValueAxis;
import com.jrefinery.chart.DateAxis;
import com.jrefinery.chart.HorizontalDateAxis;
import com.jrefinery.chart.VerticalNumberAxis;
import com.jrefinery.chart.YIntervalRenderer;
import com.jrefinery.chart.ChartPanel;
import com.jrefinery.chart.tooltips.TimeSeriesToolTipGenerator;
import com.jrefinery.data.AbstractSeriesDataset;
import com.jrefinery.data.XYDataset;
import com.jrefinery.data.IntervalXYDataset;
import com.jrefinery.data.TimeSeriesCollection;
import com.jrefinery.data.BasicTimeSeries;
import com.jrefinery.data.Minute;
import com.jrefinery.data.Hour;
import com.jrefinery.data.Day;
import com.jrefinery.data.TimeSeriesDataPair;
import com.jrefinery.data.TimePeriod;
import com.jrefinery.date.SerialDate;
import com.jrefinery.ui.ApplicationFrame;
import com.jrefinery.ui.RefineryUtilities;

public class OverlaidChartDemo extends ApplicationFrame {

public OverlaidChartDemo(String title) {

super(title);

// create a title...
String chartTitle = "Medical Data";
XYDataset dataset1 = createDataset();
StandardXYItemRenderer renderer1 = new StandardXYItemRenderer();
renderer1.setPlotShapes(true);
renderer1.setDefaultShapeFilled(true);
renderer1.setDefaultShapeScale(5.0);
renderer1.setToolTipGenerator(new TimeSeriesToolTipGenerator());

XYPlot plot1 = new XYPlot(dataset1, null, null, renderer1);
XYDataset data = new SampleDataset();
XYItemRenderer renderer2 = new YIntervalRenderer();

XYPlot plot2 = new XYPlot(data, null, null, renderer2);
plot2.setSeriesPaint(0, Color.darkGray);
ValueAxis domainAxis = new HorizontalDateAxis("Time");
ValueAxis rangeAxis = new VerticalNumberAxis("Rate");

OverlaidXYPlot plot = new OverlaidXYPlot(domainAxis, rangeAxis);
plot.add(plot2);
plot.add(plot1);
JFreeChart chart = new JFreeChart("Medical Data",
JFreeChart.DEFAULT_TITLE_FONT,
plot, true);

ChartPanel chartPanel = new ChartPanel(chart);
chartPanel.setPreferredSize(new java.awt.Dimension(500, 270));
setContentPane(chartPanel);

}

/**
* Creates a dataset, consisting of two series of monthly data.
*
* @return the dataset.
*/
public XYDataset createDataset() {

BasicTimeSeries s1 = new BasicTimeSeries("Heart Rate", Minute.class);
Day march31 = new Day(31, SerialDate.MARCH, 2002);
s1.add(new Minute( 0, new Hour(8, march31)), 126);
s1.add(new Minute(15, new Hour(8, march31)), 129);
s1.add(new Minute(30, new Hour(8, march31)), 123);
s1.add(new Minute(45, new Hour(8, march31)), 130);
s1.add(new Minute( 0, new Hour(9, march31)), 126);
s1.add(new Minute(15, new Hour(9, march31)), 125);
s1.add(new Minute(30, new Hour(9, march31)), 123);
s1.add(new Minute(45, new Hour(9, march31)), 120);
s1.add(new Minute( 0, new Hour(10, march31)), 116);
s1.add(new Minute(15, new Hour(10, march31)), 118);

BasicTimeSeries s2 = new BasicTimeSeries("Respiratory Rate", Minute.class);
s2.add(new Minute( 0, new Hour(8, march31)), 33);
s2.add(new Minute(15, new Hour(8, march31)), 31);
s2.add(new Minute(30, new Hour(8, march31)), 29);
s2.add(new Minute(45, new Hour(8, march31)), 31);
s2.add(new Minute( 0, new Hour(9, march31)), 34);
s2.add(new Minute(15, new Hour(9, march31)), 37);
s2.add(new Minute(30, new Hour(9, march31)), 37);
s2.add(new Minute(45, new Hour(9, march31)), 25);
s2.add(new Minute( 0, new Hour(10, march31)), 32);
s2.add(new Minute(15, new Hour(10, march31)), 28);


TimeSeriesCollection dataset = new TimeSeriesCollection();
dataset.addSeries(s1);
dataset.addSeries(s2);

return dataset;

}

/**
* Starting point for the demonstration application.
*
* @param args ignored.
*/
public static void main(String[] args) {

OverlaidChartDemo demo = new OverlaidChartDemo("Medical Data");
demo.pack();
RefineryUtilities.centerFrameOnScreen(demo);
demo.setVisible(true);

}

}

class SampleDataset extends AbstractSeriesDataset implements IntervalXYDataset {

private BasicTimeSeries low;

private BasicTimeSeries high;

public SampleDataset() {

this.low = new BasicTimeSeries("Diastolic", Minute.class);
Day march31 = new Day(31, SerialDate.MARCH, 2002);
this.low.add(new Minute( 0, new Hour(8, march31)), 89);
this.low.add(new Minute(15, new Hour(8, march31)), 75);
this.low.add(new Minute(30, new Hour(8, march31)), 82);
this.low.add(new Minute(45, new Hour(8, march31)), 93);
this.low.add(new Minute( 0, new Hour(9, march31)), 95);
this.low.add(new Minute(15, new Hour(9, march31)), 97);
this.low.add(new Minute(30, new Hour(9, march31)), 96);
this.low.add(new Minute(45, new Hour(9, march31)), 93);
this.low.add(new Minute( 0, new Hour(10, march31)), 89);
this.low.add(new Minute(15, new Hour(10, march31)), 88);

this.high = new BasicTimeSeries("Systolic", Minute.class);
this.high.add(new Minute( 0, new Hour(8, march31)), 123);
this.high.add(new Minute(15, new Hour(8, march31)), 135);
this.high.add(new Minute(30, new Hour(8, march31)), 146);
this.high.add(new Minute(45, new Hour(8, march31)), 152);
this.high.add(new Minute( 0, new Hour(9, march31)), 148);
this.high.add(new Minute(15, new Hour(9, march31)), 157);
this.high.add(new Minute(30, new Hour(9, march31)), 159);
this.high.add(new Minute(45, new Hour(9, march31)), 151);
this.high.add(new Minute( 0, new Hour(10, march31)), 146);
this.high.add(new Minute(15, new Hour(10, march31)), 142);

}

/**
* Returns the number of series in the dataset.
*
* @return the number of series in the dataset.
*/
public int getSeriesCount() {
return 1;
}

/**
* Returns the name of a series.
*
* @param series the series (zero-based index).
*
* @return the series name.
*/
public String getSeriesName(int series) {
return "Blood Pressure";
}

/**
* Returns the number of items in a series.
*
* @param series the series (zero-based index).
*
* @return the number of items within the series.
*/
public int getItemCount(int series) {
return this.low.getItemCount();
}

public Number getXValue(int series, int item) {
TimeSeriesDataPair dp = this.low.getDataPair(item);
TimePeriod period = dp.getPeriod();

return new Long(period.getStart());

}

/**
* Returns the starting X value for the specified series and item.
*
* @param series the series (zero-based index).
* @param item the item within a series (zero-based index).
*
* @return the starting X value for the specified series and item.
*/
public Number getStartXValue(int series, int item) {
return getXValue(series, item);
}

/**
* Returns the ending X value for the specified series and item.
*
* @param series the series (zero-based index).
* @param item the item within a series (zero-based index).
*
* @return the ending X value for the specified series and item.
*/
public Number getEndXValue(int series, int item){
return getXValue(series, item);
}

/**
* Returns the y-value for an item within a series.
*
* @param series the series (zero-based index).
* @param item the item (zero-based index).
*
* @return the y-value.
*/
public Number getYValue(int series, int item) {
return null;
}

/**
* Returns the starting Y value for the specified series and item.
*
* @param series the series (zero-based index).
* @param item the item within a series (zero-based index).
*
* @return starting Y value for the specified series and item.
*/
public Number getStartYValue(int series, int item) {
if (series == 0) {
return this.low.getValue(item);
}
else {
return null;
}
}

/**
* Returns the ending Y value for the specified series and item.
*
* @param series the series (zero-based index).
* @param item the item within a series (zero-based index).
*
* @return the ending Y value for the specified series and item.
*/
public Number getEndYValue(int series, int item){
if (series == 0) {
return this.high.getValue(item);
}
else {
return null;
}
}

}

**********************************************************


/* ======================================
* JFreeChart : a free Java chart library
* ======================================
*
* Project Info: http://www.object-refinery.com/jfreechart/index.html
* Project Lead: David Gilbert (david.gilbert@object-refinery.com);
*
* (C) Copyright 2000-2002, by Simba Management Limited and Contributors.
*
* This library is free software; you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Foundation;
* either version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*
* ----------------------
* YIntervalRenderer.java
* ----------------------
* (C) Copyright 2002, by Simba Management Limited.
*
* Original Author: David Gilbert (for Simba Management Limited);
* Contributor(s): -;
*
* $Id: $
*
* Changes
* -------
* 05-Nov-2002 : Version 1 (DG);
*
*/

package com.jrefinery.chart;

import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.Paint;
import java.awt.Stroke;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import com.jrefinery.chart.entity.EntityCollection;
import com.jrefinery.chart.entity.XYItemEntity;
import com.jrefinery.chart.tooltips.XYToolTipGenerator;
import com.jrefinery.data.XYDataset;
import com.jrefinery.data.IntervalXYDataset;

/**
* A renderer that draws a vertical line connecting the start and end Y values.
*
* @author DG
*/
public class YIntervalRenderer extends AbstractXYItemRenderer implements XYItemRenderer {

/**
* The default constructor.
*/
public YIntervalRenderer() {
this(null);
}

/**
* Creates a new renderer with the specified tool tip generator.
*
* @param toolTipGenerator the tool tip generator.
*/
public YIntervalRenderer(XYToolTipGenerator toolTipGenerator) {
super(toolTipGenerator);
}

/**
* Draws the visual representation of a single data item.
*
* @param g2 the graphics device.
* @param dataArea the area within which the plot is being drawn.
* @param info collects information about the drawing.
* @param plot the plot (can be used to obtain standard color information etc).
* @param domainAxis the domain axis.
* @param rangeAxis the range axis.
* @param data the dataset.
* @param series the series index.
* @param item the item index.
* @param crosshairInfo information about crosshairs on a plot.
*/
public void drawItem(Graphics2D g2, Rectangle2D dataArea,
ChartRenderingInfo info,
XYPlot plot, ValueAxis domainAxis, ValueAxis rangeAxis,
XYDataset data, int series, int item,
CrosshairInfo crosshairInfo) {

// setup for collecting optional entity info...
Shape entityArea = null;
EntityCollection entities = null;
if (info != null) {
entities = info.getEntityCollection();
}

IntervalXYDataset intervalData = (IntervalXYDataset) data;

Number x = intervalData.getXValue(series, item);
Number yLow = intervalData.getStartYValue(series, item);
Number yHigh = intervalData.getEndYValue(series, item);

double xx = domainAxis.translateValueToJava2D(x.doubleValue(), dataArea);
double yyLow = rangeAxis.translateValueToJava2D(yLow.doubleValue(), dataArea);
double yyHigh = rangeAxis.translateValueToJava2D(yHigh.doubleValue(), dataArea);

Paint p = plot.getSeriesPaint(series);
Stroke s = plot.getSeriesStroke(series);

Line2D line = new Line2D.Double(xx, yyLow, xx, yyHigh);

g2.setPaint(p);
g2.setStroke(s);
g2.draw(line);

Shape top = getPlot().getShape(series, item, xx, yyHigh, 5.0);
g2.fill(top);
Shape bottom = getPlot().getShape(series, item, xx, yyLow, 5.0);
g2.fill(bottom);

// add an entity for the item...
if (entities != null) {
if (entityArea == null) {
entityArea = line.getBounds();
}
String tip = "";
if (getToolTipGenerator() != null) {
tip = getToolTipGenerator().generateToolTip(data, series, item);
}
String url = null;
if (getURLGenerator() != null) {
url = getURLGenerator().generateURL(data, series, item);
}
XYItemEntity entity = new XYItemEntity(entityArea, tip, url, series, item);
entities.addEntity(entity);
}

}

}

Locked