One month ago I used the JFreeChart library. I think it is great. I’ve found some bugs there and because I had some time I decided do describe them. I hope it will be helpful to remove this bugs in futher versions and maybe it will be interesting for people who use the JFreeChart. This problem occurs in 0.9.14 version (I haven’t checked it in new versions).
1) Chart with a BarRender draws incorrectly for the secondaryDataset and secondaryRangeAxis when values are less then 0.
Example:
In file OverlaidXYPlotDemo2.java in createDataset2B(...) replace code:
I.
From:
Code: Select all
72.6
Code: Select all
-72.6
From:
Code: Select all
plot.setSecondaryRenderer(1, new StandardXYItemRenderer());
Code: Select all
plot.setSecondaryRenderer(1, new XYBarRenderer());

Problem solution:
In file XYBarRenderer.java in drawItem(...) add to first line this code:
Code: Select all
this.translatedRangeZero = rangeAxis.translateValueToJava2D(0.0, dataArea,plot.getRangeAxisEdge());

2) When only horizontal zoom is enabled: draging mouse up cause trigger autoRange.
Example:
In file OverlaidXYPlotDemo2.java after this line:
Code: Select all
ChartPanel panel = new ChartPanel(chart, true, true, true, true, true);
Code: Select all
panel.setVerticalZoom(false);
panel.setHorizontalZoom(true);
In ChartPanel in mouseReleased(...) replace:
From:
Code: Select all
if (e.getX() < zoomPoint.getX() || e.getY() < zoomPoint.getY()) {
Code: Select all
if (e.getX() < zoomPoint.getX() && horizontalZoom ||
e.getY() < zoomPoint.getY() && verticalZoom) {
Problems:
I. Incorrectly working zoom (area after zoom is different from the selected area),
II. No visible houres on the time axis.
III. For Daylight Saving Time the first hour is cut.
IV. For Daylight Saving Time incorrectly works time exception (the exception is shifted one day).
V. No visible values on the time axis – it requires implement WeekTickUnit.
V. Some others which I’ve forgotten
Example:
Code: Select all
package org.jfree.chart.demo;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.renderer.XYBarRenderer;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.axis.*;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.Hour;
import org.jfree.data.time.TimeSeriesCollection;
import org.jfree.ui.ApplicationFrame;
public class SegmentedHighLowChartDemo extends ApplicationFrame {
public SegmentedHighLowChartDemo(String title) {
super(title);
Calendar cal = GregorianCalendar.getInstance();
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
cal.set(2004, 7, 2);
Date startDate = cal.getTime();
cal.set(2004, 7, 15);
Date endDate = cal.getTime();
Date[] holidays = new Date[1];
cal.set(2004, 7, 12);
holidays[0] = cal.getTime();
SegmentedTimeline timeline = SegmentedTimeline.newFifteenMinuteTimeline();
for (int i = 0; i < holidays.length; i++) {
timeline.addBaseTimelineException(holidays[i]);
}
timeline.addBaseTimelineExclusions(startDate.getTime(), endDate.getTime());
JFreeChart chart;
TimeSeriesCollection timeCollection = new TimeSeriesCollection();
timeCollection.addSeries(createTimeSeries(startDate, endDate, timeline));
chart = ChartFactory.createTimeSeriesChart(title, "Time", "Value", timeCollection, true, true, true);
((XYPlot) chart.getPlot()).setRenderer(new XYBarRenderer());
DateAxis axis = (DateAxis) chart.getXYPlot().getDomainAxis();
axis.setTimeline(timeline);
axis.setVerticalTickLabels(true);
axis.setAutoRange(true);
TickUnits units = new TickUnits();
units.add(new DateTickUnit(DateTickUnit.HOUR, 1, DateTickUnit.MINUTE, 5,
new SimpleDateFormat("MM-dd, HH:mm")));
units.add(new DateTickUnit(DateTickUnit.HOUR, 2, DateTickUnit.MINUTE, 10,
new SimpleDateFormat("MM-dd, HH:mm")));
units.add(new DateTickUnit(DateTickUnit.HOUR, 3, DateTickUnit.MINUTE, 30,
new SimpleDateFormat("MM-dd, HH:mm")));
units.add(new DateTickUnit(DateTickUnit.HOUR, 4, DateTickUnit.HOUR, 1,
new SimpleDateFormat("MM-dd, HH:mm")));
units.add(new DateTickUnit(DateTickUnit.DAY, 1, DateTickUnit.HOUR, 1,
new SimpleDateFormat("MM-dd")));
units.add(new DateTickUnit(DateTickUnit.DAY, 2, DateTickUnit.HOUR, 1,
new SimpleDateFormat("MM-dd")));
units.add(new DateTickUnit(DateTickUnit.DAY, 7, DateTickUnit.DAY, 1, new SimpleDateFormat("MM-dd")));
units.add(new DateTickUnit(DateTickUnit.DAY, 15, DateTickUnit.DAY, 1,
new SimpleDateFormat("MM-dd")));
axis.setStandardTickUnits(units);
NumberAxis vaxis = (NumberAxis) chart.getXYPlot().getRangeAxis();
vaxis.setAutoRangeIncludesZero(false);
ChartPanel chartPanel = new ChartPanel(chart);
chartPanel.setPreferredSize(new java.awt.Dimension(500, 270));
chartPanel.setHorizontalZoom(true);
setContentPane(chartPanel);
}
/**
* Starting point for the demonstration application.
*
* @param args ignored.
*/
public static void main(String[] args) {
ApplicationFrame frame;
frame = new SegmentedHighLowChartDemo("Segmented chart");
frame.pack();
frame.setVisible(true);
}
public static TimeSeries createTimeSeries(Date startDate, Date endDate, Timeline timeline) {
TimeSeries t1 = new TimeSeries("Test", Hour.class);
Calendar cal = GregorianCalendar.getInstance();
cal.setTime(startDate);
for (; cal.getTime().before(endDate);) {
cal.add(Calendar.HOUR_OF_DAY, 1);
if (timeline.containsDomainValue(cal.getTimeInMillis() + (1000 * 60 * 60))) {
System.out.println("adding =" + cal.getTime());
t1.add(new Hour(cal.getTime()), new Double(cal.get(Calendar.HOUR_OF_DAY) + 0.3));
}
}
return t1;
}
}


Solution:
a) Add to DateAxis.java method:
Code: Select all
/**
* Zooms in on the current range.
*
* @param lowerPercent the new lower bound.
* @param upperPercent the new upper bound.
*/
public void zoomRange(double lowerPercent, double upperPercent) {
double start = timeline.toTimelineValue(new Date((long)getRange().getLowerBound()));
double length = (timeline.toTimelineValue((long)getRange().getUpperBound()) -
timeline.toTimelineValue((long)getRange().getLowerBound()));
Range adjusted = null;
if (isInverted()) {
adjusted = new DateRange(timeline.toMillisecond((long)(start +
(length * (1 - upperPercent)))),
timeline.toMillisecond((long)(start + (length * (1 - lowerPercent)))));
}
else {
adjusted = new DateRange(timeline.toMillisecond((long)(start + length * lowerPercent)),
timeline.toMillisecond((long)(start + length * upperPercent)));
}
setRange(adjusted);
}
From:
Code: Select all
public void addBaseTimelineException(Date date) {
//addBaseTimelineException(getTime(date));
addBaseTimelineException(date.getTime());
}
Code: Select all
public void addBaseTimelineException(Date date) {
addBaseTimelineException(getTime(date));
}
From:
Code: Select all
getExceptionSegmentCount(lastIndex, result.millisecond - 1)) > 0) {
Code: Select all
getExceptionSegmentCount(lastIndex, (result.millisecond / segmentSize)* segmentSize - 1)) > 0) {
From:
Code: Select all
return result.millisecond;
Code: Select all
return getTimeFromLong(result.millisecond);
d) In SegmentedTimeline.java add:
Code: Select all
public long getTimeFromLong(long date) {
long result = date;
if (this.adjustForDaylightSaving) {
workingCalendarNoDST.setTimeInMillis(date);
workingCalendar.set(workingCalendarNoDST.get(Calendar.YEAR),
workingCalendarNoDST.get(Calendar.MONTH),
workingCalendarNoDST.get(Calendar.DATE),
workingCalendarNoDST.get(Calendar.HOUR_OF_DAY),
workingCalendarNoDST.get(Calendar.MINUTE),
workingCalendarNoDST.get(Calendar.SECOND));
workingCalendar.set(Calendar.MILLISECOND,
workingCalendarNoDST.get(Calendar.MILLISECOND));
result = workingCalendar.getTimeInMillis();
}
return result;
}
e) In DateAxis.java replace:
From:
Code: Select all
public boolean isHiddenValue(long millis) {
return (this.timeline.containsDomainValue(millis) == false);
}
Code: Select all
public boolean isHiddenValue(long millis) {
return (this.timeline.containsDomainValue(new Date(millis)) == false);
}
f) In DateAxis.java replace:
From:
Code: Select all
public double translateValueToJava2D(double value, Rectangle2D dataArea,
RectangleEdge edge) {
value = timeline.toTimelineValue((long) value);
DateRange range = (DateRange) getRange();
double axisMin = timeline.toTimelineValue(range.getLowerDate().getTime());
double axisMax = timeline.toTimelineValue(range.getUpperDate());
...
Code: Select all
public double translateValueToJava2D(double value, Rectangle2D dataArea,
RectangleEdge edge) {
value = timeline.toTimelineValue(new Date((long) value));
DateRange range = (DateRange) getRange();
double axisMin = timeline.toTimelineValue(range.getLowerDate());
double axisMax = timeline.toTimelineValue(range.getUpperDate());
g) In DateAxis.java in translateJava2DtoValue(...) replace:
From:
Code: Select all
result = timeline.toTimelineValue((long) result);
return result;
Code: Select all
return timeline.toMillisecond((long) result);
h) In DateAxis.java replace:
From:
Code: Select all
protected void autoAdjustRange() {
Plot plot = getPlot();
if (plot == null) {
return; // no plot, no data
}
if (plot instanceof ValueAxisPlot) {
ValueAxisPlot vap = (ValueAxisPlot) plot;
Range r = vap.getDataRange(this);
if (r == null) {
r = new DateRange();
}
long upper = timeline.toTimelineValue((long) r.getUpperBound());
long lower;
long fixedAutoRange = (long) getFixedAutoRange();
if (fixedAutoRange > 0.0) {
lower = upper - fixedAutoRange;
}
else {
lower = timeline.toTimelineValue((long) r.getLowerBound());
...
To:
Code: Select all
protected void autoAdjustRange() {
Plot plot = getPlot();
if (plot == null) {
return; // no plot, no data
}
if (plot instanceof ValueAxisPlot) {
ValueAxisPlot vap = (ValueAxisPlot) plot;
Range r = vap.getDataRange(this);
if (r == null) {
if (timeline instanceof SegmentedTimeline) { //Timeline hasn't method getStartTime()
r = new DateRange(((SegmentedTimeline)timeline).getStartTime(),
((SegmentedTimeline)timeline).getStartTime() + 1);
} else {
r = new DateRange();
}
}
long upper = timeline.toTimelineValue(new Date((long) r.getUpperBound()));
long lower;
long fixedAutoRange = (long) getFixedAutoRange();
if (fixedAutoRange > 0.0) {
lower = upper - fixedAutoRange;
}
else {
lower = timeline.toTimelineValue(new Date((long) r.getLowerBound()));
From:
Code: Select all
protected void selectHorizontalAutoTickUnit(Graphics2D g2, Rectangle2D drawArea,
Rectangle2D dataArea, RectangleEdge edge) {
double zero = translateValueToJava2D(0.0, dataArea, edge);
double tickLabelWidth = estimateMaximumTickLabelWidth(g2, getTickUnit());
// start with the current tick unit...
TickUnitSource tickUnits = getStandardTickUnits();
TickUnit unit1 = tickUnits.getCeilingTickUnit(getTickUnit());
double x1 = translateValueToJava2D(unit1.getSize(), dataArea, edge);
double unit1Width = Math.abs(x1 - zero);
// then extrapolate...
double guess = (tickLabelWidth / unit1Width) * unit1.getSize();
DateTickUnit unit2 = (DateTickUnit) tickUnits.getCeilingTickUnit(guess);
double x2 = translateValueToJava2D(unit2.getSize(), dataArea, edge);
double unit2Width = Math.abs(x2 - zero);
tickLabelWidth = estimateMaximumTickLabelWidth(g2, unit2);
if (tickLabelWidth > unit2Width) {
unit2 = (DateTickUnit) tickUnits.getLargerTickUnit(unit2);
}
setTickUnit(unit2, false, false);
}
To:
Code: Select all
protected void selectHorizontalAutoTickUnit(Graphics2D g2, Rectangle2D drawArea,
Rectangle2D dataArea, RectangleEdge edge) {
long shift = 0;
if (timeline instanceof SegmentedTimeline) {
shift = ((SegmentedTimeline)timeline).getStartTime();
}
double zero = translateValueToJava2D(shift + 0.0, dataArea, edge);
double tickLabelWidth = estimateMaximumTickLabelWidth(g2, getTickUnit());
// start with the current tick unit...
TickUnitSource tickUnits = getStandardTickUnits();
TickUnit unit1 = tickUnits.getCeilingTickUnit(getTickUnit());
double x1 = translateValueToJava2D(shift + unit1.getSize(), dataArea, edge);
double unit1Width = Math.abs(x1 - zero);
// then extrapolate...
double guess = (tickLabelWidth / unit1Width) * unit1.getSize();
DateTickUnit unit2 = (DateTickUnit) tickUnits.getCeilingTickUnit(guess);
double x2 = translateValueToJava2D(shift + unit2.getSize(), dataArea, edge); // TODO: _2
double unit2Width = Math.abs(x2 - zero);
tickLabelWidth = estimateMaximumTickLabelWidth(g2, unit2);
if (tickLabelWidth > unit2Width) {
unit2 = (DateTickUnit) tickUnits.getLargerTickUnit(unit2);
}
setTickUnit(unit2, false, false);
}


Best regards
Grzegorz Majer