Search code examples
jfreechart

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:

enter image description here

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?


Solution

  • 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:

    ChartTick

    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;
        }