I am using JFreeChart XYPLot for plotting a XYData set with different labels . I have created different XYSeries objects for different labels so that I can have different colors for different labels . Now I need to require the change the shapes of specific points(test data) in each XYDataSeries as below .
In the above plotting , there are two different XYSeries with blue and red color . Out of these two I need to change the shapes of some points(test data) to X instead of circle . Is it possible in JFreeChart. This post explained on how to do it for whole data set , but I want to change only specific points
Below is the code I have written so far
public static Map<String, XYSeries> createXYSeries(Data[] dataSet){
Map<String,XYSeries> xySeries = new HashMap<String, XYSeries>();
for(Data data : dataSet){
if(xySeries.get(data.actualLabel) == null){
xySeries.put(data.actualLabel, new XYSeries(data.actualLabel));
}
xySeries.get(data.actualLabel).add(data.dimensionValues[0],data.dimensionValues[1]);
}
return xySeries;
}
public XYDataset createXYSeriesCollection(Map<String, XYSeries> plottingDataSet) {
XYSeriesCollection xySeriesCollection = new XYSeriesCollection();
for (String key : plottingDataSet.keySet()) {
xySeriesCollection.addSeries(plottingDataSet.get(key));
}
return xySeriesCollection;
}
private ChartPanel createPlottingPanel(String title,
Map<String, XYSeries> plottingDataSet) {
JFreeChart jfreechart = ChartFactory.createScatterPlot(title, "X", "Y",
createSampleData(plottingDataSet), PlotOrientation.VERTICAL,
true, true, false);
XYPlot xyPlot = (XYPlot) jfreechart.getPlot();
xyPlot.setDomainCrosshairVisible(true);
xyPlot.setRangeCrosshairVisible(true);
xyPlot.setBackgroundPaint(Color.white);
return new ChartPanel(jfreechart);
}
Note : I am trying to plot the KNearestNeighbors results .(Circles for train data and X for test data)
ChartFactory.createScatterPlot() instantiates an XYLineAndShapeRenderer. You can replace the renderer with one that lets you selectively replace the Shape returned by getItemShape(), as shown below.
xyPlot.setRenderer(new XYLineAndShapeRenderer(false, true) {
#Override
public Shape getItemShape(int row, int col) {
if (row == 0 & col == N) {
return ShapeUtilities.createDiagonalCross(5, 2);
} else {
return super.getItemShape(row, col);
}
}
});
Complete example, as run:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Shape;
import java.util.*;
import javax.swing.JFrame;
import org.jfree.chart.*;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.NumberTickUnit;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
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;
import org.jfree.util.ShapeUtilities;
/**
* #see http://stackoverflow.com/a/20359200/230513
* #see http://stackoverflow.com/a/6669529/230513
*/
public class ScatterShape extends JFrame {
private static final int N = 8;
private static final int SIZE = 345;
private static final String title = "Scatter Shape Demo";
private static final Random rand = new Random();
private final XYSeries series = new XYSeries("Data");
public ScatterShape(String s) {
super(s);
final ChartPanel chartPanel = createDemoPanel();
this.add(chartPanel, BorderLayout.CENTER);
}
private ChartPanel createDemoPanel() {
JFreeChart chart = ChartFactory.createScatterPlot(
title, "X", "Y", createSampleData(),
PlotOrientation.VERTICAL, true, true, false);
XYPlot xyPlot = (XYPlot) chart.getPlot();
xyPlot.setDomainCrosshairVisible(true);
xyPlot.setRangeCrosshairVisible(true);
xyPlot.setRenderer(new XYLineAndShapeRenderer(false, true) {
#Override
public Shape getItemShape(int row, int col) {
if (row == 0 & col == N) {
return ShapeUtilities.createDiagonalCross(5, 2);
} else {
return super.getItemShape(row, col);
}
}
});
adjustAxis((NumberAxis) xyPlot.getDomainAxis(), true);
adjustAxis((NumberAxis) xyPlot.getRangeAxis(), false);
xyPlot.setBackgroundPaint(Color.white);
return new ChartPanel(chart) {
#Override
public Dimension getPreferredSize() {
return new Dimension(SIZE, SIZE);
}
};
}
private void adjustAxis(NumberAxis axis, boolean vertical) {
axis.setRange(-3.0, 3.0);
axis.setTickUnit(new NumberTickUnit(0.5));
axis.setVerticalTickLabels(vertical);
}
private XYDataset createSampleData() {
XYSeriesCollection xySeriesCollection = new XYSeriesCollection();
for (int i = 0; i < N * N; i++) {
series.add(rand.nextGaussian(), rand.nextGaussian());
}
xySeriesCollection.addSeries(series);
return xySeriesCollection;
}
public static void main(String args[]) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
ScatterShape demo = new ScatterShape(title);
demo.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
demo.pack();
demo.setLocationRelativeTo(null);
demo.setVisible(true);
}
});
}
}
Related
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?
I' m using the ChartPanel class from JFreeChart and want to know the width of the plot's data area.
The returned value from the method chartPanel.getChartRenderingInfo().getPlotInfo().getDataArea().getWidth() is 0.0d.
I'm using the JFreeChart version 1.5.0.
My suspicion was a thread-problem, which means that the plotting isn't finished, when I call the .getWidth()-method. So I tried to use ChartChange events, but the result is always a width of 0.0 pixels.
Here is a short example class I created to Show the Problem:
The println in the main() gives "xAxisLengthInPixels = 0.0":
package jFreeChartTest;
import java.awt.BorderLayout;
import javax.swing.JFrame;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.event.ChartChangeEvent;
import org.jfree.chart.event.ChartChangeListener;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
public class Panel_Chart extends JFrame implements ChartChangeListener {
private ChartPanel chartPanel = null;
private int width = 1024;
private int height = 820;
public Panel_Chart() {
this.setTitle("XY Plot");
setBounds(0, 0, width, height);
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
XYSeriesCollection xySeriesCollection = new XYSeriesCollection();
XYSeries testXYSeries = new XYSeries("Test-Series", true);
testXYSeries.add(4,10);
testXYSeries.add(5,20);
testXYSeries.add(6,30);
xySeriesCollection.addSeries(testXYSeries);
boolean includeLegend = true;
boolean useTooltips = true;
boolean useUrls = true;
JFreeChart chart = ChartFactory.createScatterPlot(//createXYLineChart(
"",
"X-Axis",
"Y-Axis",
xySeriesCollection,
PlotOrientation.VERTICAL,
includeLegend,
useTooltips,
useUrls);
this.chartPanel = new ChartPanel(chart);
getContentPane().add(chartPanel, BorderLayout.PAGE_START);
}
public Double getXAxisLengthInPixels(){
return chartPanel.getChartRenderingInfo().getPlotInfo().getDataArea().getWidth();
}
#Override
public void chartChanged(ChartChangeEvent arg0) {
// TODO Auto-generated method stub
System.out.println("ChartChangeEvent:" + arg0);
}
public static void main(String[] args){
Panel_Chart panelChart = new Panel_Chart();
panelChart.setVisible(true);
System.out.println("xAxisLengthInPixels = " + panelChart.getXAxisLengthInPixels());
}
}
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;
}
I'm using CombinedDomainXYPlot to plot the charts. I have another requirement where, I need to show the two charts horizontally.
Currently i'm having only one chart. what i need is, i need two charts in the first row.
like Chart1 Chart2
Code:
CombinedDomainXYPlot plot = new CombinedDomainXYPlot();
plot.add(getChart1(), 2);
plot.add(getChart2(), 2);
It is giving only one chart in the first row. and second chart2 in the another row.
Is there any way I can make these two charts into single row?
Edit: Actually I wanted it like your ThermometerDemo example. For that you have used JPanel, but here I'm using JFrame.
I wanted it like your ThermometerDemo example.
Based on this example, the code below adds two panels to a GridLayout(1, 0). Each panel includes it's own chart and control panel.
import java.awt.*;
import java.awt.event.ActionEvent;
import java.util.Random;
import javax.swing.*;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.plot.CombinedDomainXYPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.StandardXYItemRenderer;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
/**
* #see https://stackoverflow.com/a/20243624/230513
* #see https://stackoverflow.com/q/11870416/230513
*/
public class CombinedPlot {
private static final int MAX = 3;
private static final Random rand = new Random();
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
init();
}
});
}
private static void init() {
JFrame frame = new JFrame("Combined Plot Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new GridLayout(1, 0));
frame.add(createPanel());
frame.add(createPanel());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
private static JPanel createPanel() {
JPanel p = new JPanel(new BorderLayout());
XYItemRenderer renderer = new StandardXYItemRenderer();
XYPlot plot1 = new XYPlot(
generateData(), null, new NumberAxis("Range 1"), renderer);
XYPlot plot2 = new XYPlot(
generateData(), null, new NumberAxis("Range 2"), renderer);
final CombinedDomainXYPlot plot
= new CombinedDomainXYPlot(new NumberAxis("Domain"));
plot.add(plot1);
plot.add(plot2);
plot.setOrientation(PlotOrientation.VERTICAL);
JFreeChart chart = new JFreeChart(
"Combined Plots", JFreeChart.DEFAULT_TITLE_FONT, plot, false);
ChartPanel chartPanel = new ChartPanel(chart) {
#Override
public Dimension getPreferredSize() {
return new Dimension(320, 320);
}
};
JPanel controlPanel = new JPanel();
controlPanel.add(new JButton(new UpdateAction(plot, 0)));
controlPanel.add(new JButton(new UpdateAction(plot, 1)));
p.add(chartPanel, BorderLayout.CENTER);
p.add(controlPanel, BorderLayout.SOUTH);
return p;
}
private static class UpdateAction extends AbstractAction {
private final XYPlot plot;
public UpdateAction(CombinedDomainXYPlot plot, int i) {
super("Update plot " + (i + 1));
this.plot = (XYPlot) plot.getSubplots().get(i);
}
#Override
public void actionPerformed(ActionEvent e) {
plot.setDataset(CombinedPlot.generateData());
}
}
private static XYSeriesCollection generateData() {
XYSeriesCollection data = new XYSeriesCollection();
for (int i = 0; i < MAX; i++) {
data.addSeries(generateSeries("Series " + (i + 1)));
}
return data;
}
private static XYSeries generateSeries(String key) {
XYSeries series = new XYSeries(key);
for (int i = 0; i < 16; i++) {
series.add(rand.nextGaussian(), rand.nextGaussian());
}
return series;
}
}
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.