JFreeChart: How to auto adjust range axis min/max while panning (with fixed domain axis range) - jfreechart

I have a time series chart where domain range is auto-fixed, and due to this only part of large data-set is visible in the chart at a time. I have enabled only domain panning.
I am looking for a functionality such that when user pan in either direction, i.e. left or right, the range axis min/max values should adjust automatically as per the data visible in chart (and not as per complete data-set). Does JFreeChart-1.0.19 provide any such functionality out of the box?
On the other hand, I know that when dynamic data is added in time series (with fixed domain axis range), then setAutoAdjust on range axis will automatically adjust the min/max as per data visible on chart.
Similar question was asked here.
Please find below the sample code: TimeSeriesDemo.java
import java.text.SimpleDateFormat;
import javax.swing.JPanel;
import org.jfree.ui.ApplicationFrame;
import org.jfree.chart.JFreeChart;
import org.jfree.data.xy.XYDataset;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.chart.axis.DateAxis;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;
import org.jfree.data.time.Day;
import org.jfree.chart.ChartPanel;
import org.jfree.ui.RefineryUtilities;
public class TimeSeriesDemo extends ApplicationFrame {
public TimeSeriesDemo1(String title) {
super(title);
JPanel chartPanel = createDemoPanel();
chartPanel.setPreferredSize(new java.awt.Dimension(500, 270));
setContentPane(chartPanel);
}
private static JFreeChart createChart(XYDataset dataset) {
JFreeChart chart = ChartFactory.createTimeSeriesChart(
"Nifty 50 Close", // title
"Date", // x-axis label
"Value", // y-axis label
dataset, // data
true, // create legend?
true, // generate tooltips?
false // generate URLs?
);
XYPlot plot = (XYPlot) chart.getPlot();
plot.setDomainPannable(true);
plot.setRangePannable(false);
XYItemRenderer r = plot.getRenderer();
if (r instanceof XYLineAndShapeRenderer) {
XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) r;
renderer.setBaseShapesVisible(false);
}
DateAxis axis = (DateAxis) plot.getDomainAxis();
axis.setDateFormatOverride(new SimpleDateFormat("dd-MMM-yyyy"));
axis.setFixedAutoRange(6.0 * 24 * 60 * 60 * 1000);
return chart;
}
private static XYDataset createDataset() {
TimeSeries s1 = new TimeSeries("Nifty 50");
s1.add(new Day(11,1,2017),8380.65);
s1.add(new Day(12,1,2017),8407.2);
s1.add(new Day(13,1,2017),8400.35);
s1.add(new Day(16,1,2017),8412.8);
s1.add(new Day(17,1,2017),8398);
s1.add(new Day(18,1,2017),8417);
s1.add(new Day(19,1,2017),8435.1);
s1.add(new Day(20,1,2017),8349.35);
s1.add(new Day(23,1,2017),8391.5);
s1.add(new Day(24,1,2017),8475.8);
s1.add(new Day(25,1,2017),8602.75);
s1.add(new Day(27,1,2017),8641.25);
s1.add(new Day(30,1,2017),8632.75);
s1.add(new Day(31,1,2017),8561.3);
s1.add(new Day(1,2,2017),8716.4);
s1.add(new Day(2,2,2017),8734.25);
s1.add(new Day(3,2,2017),8740.95);
s1.add(new Day(6,2,2017),8801.05);
s1.add(new Day(7,2,2017),8768.3);
s1.add(new Day(8,2,2017),8769.05);
TimeSeriesCollection dataset = new TimeSeriesCollection();
dataset.addSeries(s1);
return dataset;
}
public static JPanel createDemoPanel() {
JFreeChart chart = createChart(createDataset());
ChartPanel cp = new ChartPanel(chart);
cp.setDomainZoomable(false);
cp.setRangeZoomable(false);
return cp;
}
public static void main(String[] args) {
TimeSeriesDemo1 demo = new TimeSeriesDemo1("Time Series Demo 1");
demo.pack();
RefineryUtilities.centerFrameOnScreen(demo);
demo.setVisible(true);
}
}

Related

How to create hollow shapes in JFreeChart Scatter Plot

JFreeChart version used: 1.5.0
I tried the following to obtain hollow shapes in a scatter plot:
PlotFrame.java file content:
package javaapplication1;
import javax.swing.JFrame;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
/**
*
* #author tarik
*/
public class PlotFrame extends javax.swing.JFrame {
public static void main(String[] argv) {
PlotFrame plotFrame = new PlotFrame();
plotFrame.setVisible(true);
}
/**
* Creates new form PlotFrame
*/
public PlotFrame() {
this.setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JFreeChart chart = createChart();
ChartPanel panel = new ChartPanel(chart);
this.add(panel);
pack();
}
private JFreeChart createChart() {
// Create dataset
XYDataset dataset = createDataset();
// Create chart
JFreeChart chart = ChartFactory.createScatterPlot(
"Boys VS Girls weight comparison chart",
"X-Axis", "Y-Axis", dataset);
XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) chart.getXYPlot().getRenderer();
//renderer.setBaseShapesFilled(false);
renderer.setUseFillPaint(false);
return chart;
}
private XYDataset createDataset() {
XYSeriesCollection dataset = new XYSeriesCollection();
//Boys (Age,weight) series
XYSeries series1 = new XYSeries("Boys");
series1.add(1, 72.9);
series1.add(2, 81.6);
series1.add(3, 88.9);
series1.add(4, 96);
series1.add(5, 102.1);
series1.add(6, 108.5);
series1.add(7, 113.9);
series1.add(8, 119.3);
series1.add(9, 123.8);
series1.add(10, 124.4);
dataset.addSeries(series1);
//Girls (Age,weight) series
XYSeries series2 = new XYSeries("Girls");
series2.add(1, 72.5);
series2.add(2, 80.1);
series2.add(3, 87.2);
series2.add(4, 94.5);
series2.add(5, 101.4);
series2.add(6, 107.4);
series2.add(7, 112.8);
series2.add(8, 118.2);
series2.add(9, 122.9);
series2.add(10, 123.4);
dataset.addSeries(series2);
return dataset;
}
}
It did not work, the shapes are still filled.
Recall that Graphics2D can draw() the outline of a Shape as well as fill() its interior. By limiting rendering to draw(), #micro's approach is sound in this regard, but it assumes version 1.0.19. When migrating to version 1.5 and later, note that "many methods getBaseXXX()/setBaseXXX() have been renamed setDefaultXXX()/getDefaultXXX()." The correct formulation is illustrated below:
renderer.setDefaultShapesFilled(false);
renderer.setUseFillPaint(false);
Other changes:
Construct and manipulate Swing GUI objects only on the event dispatch thread.
Override getPreferredSize() to set the chart's initial preferred size.
As tested:
import java.awt.Dimension;
import java.awt.EventQueue;
import javax.swing.JFrame;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
/**
* #see https://stackoverflow.com/q/58812592/230513
* #author trashgod
* #author tarik
*/
public class PlotFrame extends javax.swing.JFrame {
public static void main(String[] argv) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
PlotFrame plotFrame = new PlotFrame();
plotFrame.setLocationRelativeTo(null);
plotFrame.setVisible(true);
}
});
}
/**
* Creates new form PlotFrame
*/
public PlotFrame() {
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JFreeChart chart = createChart();
ChartPanel panel = new ChartPanel(chart){
#Override
public Dimension getPreferredSize() {
return new Dimension(640, 480);
}
};
this.add(panel);
this.pack();
}
private JFreeChart createChart() {
// Create dataset
XYDataset dataset = createDataset();
// Create chart
JFreeChart chart = ChartFactory.createScatterPlot(
"Boys VS Girls weight comparison chart",
"X-Axis", "Y-Axis", dataset);
XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) chart.getXYPlot().getRenderer();
renderer.setDefaultShapesFilled(false);
renderer.setUseFillPaint(false);
return chart;
}
private XYDataset createDataset() {
XYSeriesCollection dataset = new XYSeriesCollection();
//Boys (Age,weight) series
XYSeries series1 = new XYSeries("Boys");
series1.add(1, 72.9);
series1.add(2, 81.6);
series1.add(3, 88.9);
series1.add(4, 96);
series1.add(5, 102.1);
series1.add(6, 108.5);
series1.add(7, 113.9);
series1.add(8, 119.3);
series1.add(9, 123.8);
series1.add(10, 124.4);
dataset.addSeries(series1);
//Girls (Age,weight) series
XYSeries series2 = new XYSeries("Girls");
series2.add(1, 72.5);
series2.add(2, 80.1);
series2.add(3, 87.2);
series2.add(4, 94.5);
series2.add(5, 101.4);
series2.add(6, 107.4);
series2.add(7, 112.8);
series2.add(8, 118.2);
series2.add(9, 122.9);
series2.add(10, 123.4);
dataset.addSeries(series2);
return dataset;
}
}
From source code:
if (getItemShapeFilled(series, item)) {
if (this.useFillPaint) {
g2.setPaint(getItemFillPaint(series, item));
}
else {
g2.setPaint(getItemPaint(series, item));
}
g2.fill(shape);
}
For the first if there is no else, so we need the getItemShapeFilled to return false. It looks like this:
public boolean getItemShapeFilled(int series, int item) {
Boolean flag = getSeriesShapesFilled(series);
if (flag != null) {
return flag;
}
return this.baseShapesFilled;
}
Easiest way is to use this method:
public void setBaseShapesFilled(boolean flag) {
this.baseShapesFilled = flag;
fireChangeEvent();
}
So the actual code is:
import javax.swing.JFrame;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
/**
*
* #author tarik
*/
public class PlotFrame extends javax.swing.JFrame {
public static void main(String[] argv) {
PlotFrame plotFrame = new PlotFrame();
plotFrame.setVisible(true);
}
/**
* Creates new form PlotFrame
*/
public PlotFrame() {
this.setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JFreeChart chart = createChart();
ChartPanel panel = new ChartPanel(chart);
this.add(panel);
pack();
}
private JFreeChart createChart() {
// Create dataset
XYDataset dataset = createDataset();
// Create chart
JFreeChart chart = ChartFactory.createScatterPlot(
"Boys VS Girls weight comparison chart",
"X-Axis", "Y-Axis", dataset);
XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) chart.getXYPlot().getRenderer();
renderer.setBaseShapesFilled(false);
renderer.setUseFillPaint(false);
return chart;
}
private XYDataset createDataset() {
XYSeriesCollection dataset = new XYSeriesCollection();
//Boys (Age,weight) series
XYSeries series1 = new XYSeries("Boys");
series1.add(1, 72.9);
series1.add(2, 81.6);
series1.add(3, 88.9);
series1.add(4, 96);
series1.add(5, 102.1);
series1.add(6, 108.5);
series1.add(7, 113.9);
series1.add(8, 119.3);
series1.add(9, 123.8);
series1.add(10, 124.4);
dataset.addSeries(series1);
//Girls (Age,weight) series
XYSeries series2 = new XYSeries("Girls");
series2.add(1, 72.5);
series2.add(2, 80.1);
series2.add(3, 87.2);
series2.add(4, 94.5);
series2.add(5, 101.4);
series2.add(6, 107.4);
series2.add(7, 112.8);
series2.add(8, 118.2);
series2.add(9, 122.9);
series2.add(10, 123.4);
dataset.addSeries(series2);
return dataset;
}
}
My result is:
Isn't that what you meant?

jfreechart line chart curve does not turn on inflection points

I have a line chart and have it creating a smooth curve instead of a jagged graph. (I am doing this via renderers, XYSplineRenderer as an example)
The curve should have inflection points at the data points but rises above or below and does not look good and is misleading (implying that some data exists above the max and/or below the min)
Code that produces this:
import java.awt.Color;
import java.awt.Font;
import java.awt.Point;
import java.awt.geom.Ellipse2D;
import java.io.File;
import java.io.FileOutputStream;
import java.text.SimpleDateFormat;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartUtils;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.axis.DateTickUnit;
import org.jfree.chart.axis.DateTickUnitType;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.TickUnits;
import org.jfree.chart.labels.XYItemLabelGenerator;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYSplineRenderer;
import org.jfree.data.time.Day;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;
import org.jfree.data.xy.XYDataset;
public class LineChart {
public String saveToFile(String path,String pathColor) {
try {
JFreeChart chart = getChart(pathColor,"","","",false);
Point p = new Point(400,300);
File chartFile = new File(path);
ChartUtils.writeChartAsPNG(new FileOutputStream(chartFile), chart, Double.valueOf(p.getX()).intValue(), Double.valueOf(p.getY()).intValue(),false,0);
} catch (Throwable t) {
t.printStackTrace();
}
return null;
}
private JFreeChart getChart(
String hexColor,
String title,
String category,
String value,
boolean includeLegend) {
TimeSeriesCollection dataset = new TimeSeriesCollection();
TimeSeries series1 = new TimeSeries("Total");
series1.add(new Day(7,10,2019),0);
series1.add(new Day(8,10,2019),6);
series1.add(new Day(9,10,2019),0);
series1.add(new Day(10,10,2019),2);
series1.add(new Day(11,10,2019),0);
dataset.addSeries(series1);
XYSplineRenderer renderer = new XYSplineRenderer();
renderer.setDrawOutlines(true);
renderer.setPrecision(1000);
renderer.setDrawSeriesLineAsPath(true);
renderer.setSeriesPaint(0, Color.decode(hexColor));
renderer.setSeriesShapesVisible(0, true);
renderer.setSeriesItemLabelsVisible(0, true);
renderer.setSeriesItemLabelPaint(0, Color.decode(hexColor));
renderer.setSeriesShape(0, new Ellipse2D.Double(-3, -3, 6, 6));
renderer.setSeriesItemLabelGenerator(0, new XYItemLabelGenerator() {
#Override
public String generateLabel(XYDataset dataset, int series, int item) {
Double d = dataset.getYValue(series, item);
return "" + d.intValue();
}
});
JFreeChart chart = ChartFactory.createTimeSeriesChart(title, category, value, null,includeLegend, true, false);
chart.setAntiAlias(true);
XYPlot plot = (XYPlot)chart.getPlot();
plot.setRangeGridlinePaint(new Color(155, 155, 155) );
plot.setBackgroundPaint(new Color(0, 0, 0, 0));
final TickUnits standardUnits = new TickUnits();
standardUnits.add(
new DateTickUnit(DateTickUnitType.DAY, 1, new SimpleDateFormat("dd E"))
);
DateAxis domainAxis = (DateAxis)plot.getDomainAxis();
domainAxis.setTickLabelFont(new Font("Montserrat",Font.BOLD, 10));
domainAxis.setTickLabelPaint(Color.decode("#9b9b9b"));
domainAxis.setStandardTickUnits(standardUnits);
final NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis();
rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
rangeAxis.setTickLabelFont(new Font("Montserrat",Font.BOLD, 10));
rangeAxis.setTickLabelPaint(Color.decode("#9b9b9b"));
plot.setDataset(0, dataset);
plot.setRenderer(0, renderer);
plot.setOutlinePaint(null);
return chart;
}
}

How to draw a linechart on click in JFreeChart?

I am able to draw LineChart using JFreeChart. I would like to know how to draw linechart with legends. The place where I click should be dynamaically updated in the JFreeChart.
I have mentioned a image here to explain what I Mean.
In the Image I am able draw the black line what I want is the red line to be drawn on click. Can anybody suggest me something related to this?
Thanks in advance
Here is the sample code. In this I have 3 Series. and I am adding onclick datapoints to second series.
import com.sun.javafx.geom.Point2D;
import com.sun.javafx.scene.paint.GradientUtils.Point;
import java.awt.Color;
import java.awt.event.MouseEvent;
import java.awt.geom.Rectangle2D;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartMouseEvent;
import org.jfree.chart.ChartMouseListener;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.ui.ApplicationFrame;
import org.jfree.ui.RefineryUtilities;
public class ChartDemo extends ApplicationFrame implements ChartMouseListener {
/**
* Creates a new demo.
*
* #param title the frame title.
*/
public static ChartPanel chartPanel = null;
public static JFreeChart chart = null;
public static XYSeries series1,series2;
public ChartDemo(final String title) {
super(title);
final XYDataset dataset = createDataset();
final JFreeChart chart = createChart(dataset);
chartPanel = new ChartPanel(chart);
chartPanel.addChartMouseListener(this);
chartPanel.setPreferredSize(new java.awt.Dimension(500, 270));
setContentPane(chartPanel);
}
/**
* Creates a sample dataset.
*
* #return a sample dataset.
*/
private XYDataset createDataset() {
series1 = new XYSeries("First");
series1.add(1.0, 1.0);
series1.add(2.0, 4.0);
series1.add(3.0, 3.0);
series1.add(4.0, 5.0);
series1.add(5.0, 5.0);
series1.add(6.0, 7.0);
series1.add(7.0, 7.0);
series1.add(8.0, 8.0);
series2 = new XYSeries("Second");
series2.add(1.0, 5.0);
series2.add(2.0, 7.0);
series2.add(3.0, 6.0);
series2.add(4.0, 8.0);
series2.add(5.0, 4.0);
series2.add(6.0, 4.0);
series2.add(7.0, 2.0);
series2.add(8.0, 1.0);
final XYSeries series3 = new XYSeries("Third");
series3.add(3.0, 4.0);
series3.add(4.0, 3.0);
series3.add(5.0, 2.0);
series3.add(6.0, 3.0);
series3.add(7.0, 6.0);
series3.add(8.0, 3.0);
series3.add(9.0, 4.0);
series3.add(10.0, 3.0);
final XYSeriesCollection dataset = new XYSeriesCollection();
dataset.addSeries(series1);
dataset.addSeries(series2);
dataset.addSeries(series3);
return dataset;
}
/**
* Creates a chart.
*
* #param dataset the data for the chart.
*
* #return a chart.
*/
private JFreeChart createChart(final XYDataset dataset) {
// create the chart...
chart = ChartFactory.createXYLineChart(
"Chart Demo", // chart title
"X", // x axis label
"Y", // y axis label
dataset, // data
PlotOrientation.VERTICAL,
true, // include legend
true, // tooltips
false // urls
);
chart.setBackgroundPaint(Color.white);
final XYPlot plot = chart.getXYPlot();
plot.setBackgroundPaint(Color.lightGray);
// plot.setAxisOffset(new Spacer(Spacer.ABSOLUTE, 5.0, 5.0, 5.0, 5.0));
plot.setDomainGridlinePaint(Color.white);
plot.setRangeGridlinePaint(Color.white);
final XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer();
renderer.setSeriesLinesVisible(0, false);//to disable line on the graph
renderer.setSeriesShapesVisible(2, false);//to disable shape on the graph
plot.setRenderer(renderer);
final NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis();
rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
return chart;
}
public static void main(final String[] args)
{
final ChartDemo demo = new ChartDemo("Chart Demo");
demo.pack();
RefineryUtilities.centerFrameOnScreen(demo);
demo.setVisible(true);
}
#Override
public void chartMouseClicked(ChartMouseEvent cme)
{
int mouseX = cme.getTrigger().getX();
int mouseY = cme.getTrigger().getY();
System.out.println("x = " + mouseX + ", y = " + mouseY);
java.awt.geom.Point2D p = chartPanel.translateScreenToJava2D(
new java.awt.Point(mouseX, mouseY));
XYPlot plot = (XYPlot) chart.getPlot();
System.out.println("Mouse clicked!!!!!");
Rectangle2D plotArea = this.chartPanel.getChartRenderingInfo().getPlotInfo().getDataArea();
ValueAxis domainAxis = plot.getDomainAxis();
org.jfree.chart.ui.RectangleEdge domainAxisEdge = plot.getDomainAxisEdge();
ValueAxis rangeAxis = plot.getRangeAxis();
org.jfree.chart.ui.RectangleEdge rangeAxisEdge = plot.getRangeAxisEdge();
double chartX = domainAxis.java2DToValue(p.getX(), plotArea,
domainAxisEdge);
double chartY = rangeAxis.java2DToValue(p.getY(), plotArea,
rangeAxisEdge);
System.out.println("Chart: x = " + chartX + ", y = " + chartY);
series2.add(chartX, chartY);
}
#Override
public void chartMouseMoved(ChartMouseEvent cme)
{
if(cme.getTrigger().getButton() == MouseEvent.BUTTON1)
{
}
}
}

XYDataset with a single data point : nothing gets plotted

This is a corner case I encountered. Below's a SSCCE:
import java.util.*;
import java.io.*;
import java.awt.Color;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import org.jfree.chart.*;
import org.jfree.chart.plot.*;
import org.jfree.chart.axis.*;
import org.jfree.data.time.*;
import org.jfree.data.xy.XYDataset;
public class FooMain {
public static void main(String args[]) throws Exception {
BufferedImage img = timeAxisSSEProcessionsChart();
ImageIO.write(img, "png", new File("img.png"));
System.exit(0);
}
private static XYDataset createTimeSeriesDataset() {
TimeSeries timeSeries = new TimeSeries("series-a");
timeSeries.add(RegularTimePeriod.createInstance(Millisecond.class, new java.util.Date(0) , TimeZone.getTimeZone("Zulu")), 100);
// if below line is commented out, nothing is plotted:
timeSeries.add(RegularTimePeriod.createInstance(Millisecond.class, new java.util.Date(1000), TimeZone.getTimeZone("Zulu")), 100);
TimeSeriesCollection rv = new TimeSeriesCollection();
rv.addSeries(timeSeries);
return rv;
}
public static BufferedImage timeAxisSSEProcessionsChart() throws Exception {
XYDataset dataset = createTimeSeriesDataset();
JFreeChart chart = ChartFactory.createTimeSeriesChart("title", "date", "series-a", dataset, true, true, false);
InputStream in = new ByteArrayInputStream(imageBytesFromChart(chart, 600, 400));
return ImageIO.read(in);
}
private static byte[] imageBytesFromChart(JFreeChart chart, int width, int height) {
BufferedImage objBufferedImage = chart.createBufferedImage(width, height);
ByteArrayOutputStream bas = new ByteArrayOutputStream();
try {
ImageIO.write(objBufferedImage, "png", bas);
} catch (IOException e) {
e.printStackTrace();
}
byte[] byteArray=bas.toByteArray();
return byteArray;
}
}
The above code produces a plot as expected.
If however we comment out the line indicated in the createTimeSeriesDataset method, then nothing gets plotted:
So the question is: how can I ensure that at least a dot (or some other mark) gets printed in the corner case where the XYDataset contains only one data point?
The time series chart primarily consists of lines connecting the individual data points. When there is only one data element, there are no data points to connect. So far, so obvious.
One possible solution would be to enable the "tick shapes" for the chart when there is only one entry. I'm not sure whether this is an appropriate solution for your case. But this could be done with a method like
private static void showTickMarksForSingleElements(
XYDataset dataset, JFreeChart chart)
{
TimeSeriesCollection timeSeriesCollection =
(TimeSeriesCollection)dataset;
List<?> series = timeSeriesCollection.getSeries();
TimeSeries timeSeries = (TimeSeries) series.get(0);
if (timeSeries.getItemCount() == 1)
{
XYPlot plot = (XYPlot) chart.getPlot();
XYLineAndShapeRenderer renderer
= (XYLineAndShapeRenderer) plot.getRenderer();
renderer.setSeriesShapesVisible(0, true);
}
}
(Caution, there are obviously some assumptions about the types of the given objects involved - in doubt, check the types before doing the casts!)
The result would then be a single tick mark for time series that only contain a single element:
Here as another MVCE:
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.TimeZone;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.time.Millisecond;
import org.jfree.data.time.RegularTimePeriod;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;
import org.jfree.data.xy.XYDataset;
public class SingleElementChart {
public static void main(String args[]) throws Exception {
BufferedImage img = timeAxisSSEProcessionsChart();
show(img);
}
private static void show(final BufferedImage img)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(new JLabel(new ImageIcon(img)));
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
});
}
private static TimeSeriesCollection createTimeSeriesDataset() {
TimeSeries timeSeries = new TimeSeries("series-a");
timeSeries.add(RegularTimePeriod.createInstance(
Millisecond.class, new java.util.Date(0),
TimeZone.getTimeZone("Zulu")), 100);
// if below line is commented out, nothing is plotted:
//timeSeries.add(RegularTimePeriod.createInstance(
// Millisecond.class, new java.util.Date(1000),
// TimeZone.getTimeZone("Zulu")), 100);
TimeSeriesCollection rv = new TimeSeriesCollection();
rv.addSeries(timeSeries);
return rv;
}
public static BufferedImage timeAxisSSEProcessionsChart() throws Exception {
XYDataset dataset = createTimeSeriesDataset();
JFreeChart chart = ChartFactory.createTimeSeriesChart(
"title", "date", "series-a", dataset, true, true, false);
showTickMarksForSingleElements(dataset, chart);
InputStream in = new ByteArrayInputStream(
imageBytesFromChart(chart, 600, 400));
return ImageIO.read(in);
}
private static void showTickMarksForSingleElements(
XYDataset dataset, JFreeChart chart)
{
TimeSeriesCollection timeSeriesCollection =
(TimeSeriesCollection)dataset;
List<?> series = timeSeriesCollection.getSeries();
TimeSeries timeSeries = (TimeSeries) series.get(0);
if (timeSeries.getItemCount() == 1)
{
XYPlot plot = (XYPlot) chart.getPlot();
XYLineAndShapeRenderer renderer
= (XYLineAndShapeRenderer) plot.getRenderer();
renderer.setSeriesShapesVisible(0, true);
}
}
private static byte[] imageBytesFromChart(JFreeChart chart, int width, int height) {
BufferedImage objBufferedImage =
chart.createBufferedImage(width, height);
ByteArrayOutputStream bas = new ByteArrayOutputStream();
try {
ImageIO.write(objBufferedImage, "png", bas);
} catch (IOException e) {
e.printStackTrace();
}
byte[] byteArray=bas.toByteArray();
return byteArray;
}

JFreeChart MouseListener doesn't resolve chart elements

I've got stacked bar chart where I want to be able to select individual bars in the stack. But ChartMouseListener doesn't resolve ChartMouseEvent into corresponding ChartEntity. Here's the listener snippet :
public void chartMouseClicked(ChartMouseEvent event){
ChartEntity entity = event.getEntity();
if(entity != null && (entity instanceof XYItemEntity) ){
XYItemEntity item = (XYItemEntity)entity;
renderer.select(item.getSeriesIndex(), item.getItem());
return;
}
// deselect
renderer.select(-1,-1);
}
The problem is that event.getEntity() returns null when I am obviously clicking on some of the bars. Note that NOT all the bars fail. The further I go to the right end of the chart, the more obvious is the shift in coordinates. Snap shot below showing that selected bar actually appears when clicking outside of it. I am using JFreeChart within SWT composite. Can anyone confirm that this is a buggy behavior or is there a workaround?
Below is complete sscce, after you run it and click on bars - it will show up pinky. Then re-size the window and try to select bars - it will miss. And I think the miss is the function of the new size.
import java.awt.Color;
import java.awt.Paint;
import java.util.Random;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.jfree.chart.ChartMouseEvent;
import org.jfree.chart.ChartMouseListener;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.axis.DateTickMarkPosition;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.entity.ChartEntity;
import org.jfree.chart.entity.XYItemEntity;
import org.jfree.chart.event.RendererChangeEvent;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.StackedXYBarRenderer;
import org.jfree.chart.renderer.xy.StandardXYBarPainter;
import org.jfree.data.time.Day;
import org.jfree.data.time.Hour;
import org.jfree.data.time.TimeTableXYDataset;
import org.jfree.data.xy.TableXYDataset;
import org.jfree.experimental.chart.swt.ChartComposite;
public class StackedChartSwt {
private StackedRenderer renderer;
private Color[] colors = new Color[]{
new Color(230,240,255),
new Color(240,255,240),
new Color(255,255,255),
new Color(255,255,240),
new Color(255,240,240),
new Color(240,240,240)
};
public StackedChartSwt(){
Display display = new Display();
final Shell shell = new Shell(display);
shell.setLayout(new FillLayout());
JFreeChart chart = createStackedChart(createStackedDataset());
ChartComposite chartComposite = new ChartComposite(shell, SWT.NONE, chart, false, false, false, false, false);
chartComposite.setLayoutData(new GridData(GridData.FILL_BOTH));
chartComposite.setRangeZoomable(false);
chartComposite.setMenu(null);
chartComposite.addChartMouseListener(new ThisMouseListener());
shell.setSize(800, 600);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
private JFreeChart createStackedChart(TableXYDataset tablexydataset) {
DateAxis dateaxis = new DateAxis();
dateaxis.setTickMarkPosition(DateTickMarkPosition.MIDDLE);
NumberAxis numberaxis = new NumberAxis("Event counts");
renderer = new StackedRenderer();
XYPlot plot = new XYPlot(tablexydataset, dateaxis, numberaxis, renderer);
plot.setBackgroundPaint(Color.white);
plot.setDomainGridlinePaint(Color.lightGray);
plot.setDomainGridlinesVisible(true);
plot.setRangeGridlinesVisible(true);
plot.setRangeGridlinePaint(Color.lightGray);
JFreeChart chart = new JFreeChart(null, plot);
chart.setBackgroundPaint(Color.white);
chart.setBorderVisible(false);
chart.setBorderPaint(null);
return chart;
}
class StackedRenderer extends StackedXYBarRenderer{
int selectedRow=-1, selectedCol=-1;
public StackedRenderer(){
setDrawBarOutline(true);
setBarPainter(new StandardXYBarPainter());
setShadowVisible(false);
setSeriesPaint(0, Color.blue);
setMargin(0.2);
}
public void select(int row, int col){
selectedRow = row;
selectedCol = col;
notifyListeners(new RendererChangeEvent(this));
}
#Override
public Paint getItemPaint(final int row, final int col){
if(row == selectedRow && col == selectedCol)
return Color.pink;
return colors[row];
}
}
class ThisMouseListener implements ChartMouseListener{
public void chartMouseMoved(ChartMouseEvent event){
}
public void chartMouseClicked(ChartMouseEvent event){
ChartEntity entity = event.getEntity();
if(entity != null && (entity instanceof XYItemEntity) ){
XYItemEntity item = (XYItemEntity)entity;
renderer.select(item.getSeriesIndex(), item.getItem());
return;
}
// deselect
renderer.select(-1,-1);
}
}
private TableXYDataset createStackedDataset(){
Random random = new Random(0);
TimeTableXYDataset ds = new TimeTableXYDataset();
Day day = new Day();
for( int i = 0; i < 24; i++ ){
Hour hour = new Hour( i, day );
ds.add(hour, random.nextInt( 20 ), "A");
ds.add(hour, random.nextInt( 20 ), "B");
ds.add(hour, random.nextInt( 20 ), "C");
ds.add(hour, random.nextInt( 20 ), "D");
ds.add(hour, random.nextInt( 20 ), "E");
ds.add(hour, random.nextInt( 20 ), "F");
}
return ds;
}
public static void main(String[] args){
new StackedChartSwt();
}
}
Your exemplary renderer has the correct geometry when run from Swing, as shown below. I'm unsure why things are awry with SWT, but this result may narrow the search.
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Paint;
import java.util.Random;
import javax.swing.JFrame;
import org.jfree.chart.ChartMouseEvent;
import org.jfree.chart.ChartMouseListener;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.axis.DateTickMarkPosition;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.entity.ChartEntity;
import org.jfree.chart.entity.XYItemEntity;
import org.jfree.chart.event.RendererChangeEvent;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.StackedXYBarRenderer;
import org.jfree.chart.renderer.xy.StandardXYBarPainter;
import org.jfree.data.time.Day;
import org.jfree.data.time.Hour;
import org.jfree.data.time.TimeTableXYDataset;
import org.jfree.data.xy.TableXYDataset;
public class StackedChartSwing {
private ChartPanel panel;
private StackedRenderer renderer;
private Color[] colors = new Color[]{
new Color(230, 240, 255),
new Color(240, 255, 240),
new Color(255, 255, 255),
new Color(255, 255, 240),
new Color(255, 240, 240),
new Color(240, 240, 240)
};
public StackedChartSwing() {
JFreeChart chart = createStackedChart(createStackedDataset());
panel = new ChartPanel(chart);
panel.addChartMouseListener(new ThisMouseListener());
}
private JFreeChart createStackedChart(TableXYDataset tablexydataset) {
DateAxis dateaxis = new DateAxis();
dateaxis.setTickMarkPosition(DateTickMarkPosition.MIDDLE);
NumberAxis numberaxis = new NumberAxis("Event counts");
renderer = new StackedRenderer();
XYPlot plot = new XYPlot(tablexydataset, dateaxis, numberaxis, renderer);
plot.setBackgroundPaint(Color.white);
plot.setDomainGridlinePaint(Color.lightGray);
plot.setDomainGridlinesVisible(true);
plot.setRangeGridlinesVisible(true);
plot.setRangeGridlinePaint(Color.lightGray);
JFreeChart chart = new JFreeChart(null, plot);
chart.setBackgroundPaint(Color.white);
chart.setBorderVisible(false);
chart.setBorderPaint(null);
return chart;
}
class StackedRenderer extends StackedXYBarRenderer {
int selectedRow = -1, selectedCol = -1;
public StackedRenderer() {
setDrawBarOutline(true);
setBarPainter(new StandardXYBarPainter());
setShadowVisible(false);
setSeriesPaint(0, Color.blue);
setMargin(0.2);
}
public void select(int row, int col) {
selectedRow = row;
selectedCol = col;
notifyListeners(new RendererChangeEvent(this));
}
#Override
public Paint getItemPaint(final int row, final int col) {
if (row == selectedRow && col == selectedCol) {
return Color.pink;
}
return colors[row];
}
}
class ThisMouseListener implements ChartMouseListener {
#Override
public void chartMouseMoved(ChartMouseEvent event) {
}
#Override
public void chartMouseClicked(ChartMouseEvent event) {
ChartEntity entity = event.getEntity();
if (entity != null && (entity instanceof XYItemEntity)) {
XYItemEntity item = (XYItemEntity) entity;
renderer.select(item.getSeriesIndex(), item.getItem());
return;
}
// deselect
renderer.select(-1, -1);
}
}
private TableXYDataset createStackedDataset() {
Random random = new Random(0);
TimeTableXYDataset ds = new TimeTableXYDataset();
Day day = new Day();
for (int i = 0; i < 24; i++) {
Hour hour = new Hour(i, day);
ds.add(hour, random.nextInt(20), "A");
ds.add(hour, random.nextInt(20), "B");
ds.add(hour, random.nextInt(20), "C");
ds.add(hour, random.nextInt(20), "D");
ds.add(hour, random.nextInt(20), "E");
ds.add(hour, random.nextInt(20), "F");
}
return ds;
}
private void display() {
JFrame f = new JFrame("Test");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(panel);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new StackedChartSwing().display();
}
});
}
}
OK, I've found the problem. In SWT there are several constructors to create ChartComposite, most of the use defaults which specify min/max width and height. In my case, whenever I've enlarged the chart above DEFAULT_MAXIMUM_DRAW_WIDTH (800px) - the coordinates start to get messy. So, the solution is to use full constructor and specify relevant boundaries for your display. It was a tough one to crack... mainly due to lack of proper documentation.

Resources