1

in JFreeChart I have multiple series on a linechart and I would like to have a legend below the plot area aligned center.

Right now it looks like this: enter image description here

But I want to position it center between these red lines: enter image description here

Y axis labels are dynamically generated so setting a fixed padding or margin is not the right solution for me now.

Here is the code to reproduce it easily:

import java.awt.Color;
import java.awt.EventQueue;
import java.awt.image.BufferedImage;
import java.text.SimpleDateFormat;

import javax.swing.ImageIcon;
import javax.swing.JLabel;

import org.jfree.chart.ChartRenderingInfo;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.chart.ui.ApplicationFrame;
import org.jfree.data.time.Minute;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;


public class TimeChart extends ApplicationFrame {

    public TimeChart(String applicationTitle, String chartTitle) {
        super(applicationTitle);

        DateAxis timeAxis = new DateAxis("Timestamp");
        timeAxis.setAutoTickUnitSelection(true);
        timeAxis.setAutoRange(true);
        timeAxis.setUpperMargin(DateAxis.DEFAULT_UPPER_MARGIN);
        timeAxis.setLowerMargin(DateAxis.DEFAULT_LOWER_MARGIN);
        timeAxis.setDateFormatOverride(new SimpleDateFormat("YYYY-MM-dd HH:mm"));
        NumberAxis numberAxis = new NumberAxis("Number");


        numberAxis.setAutoTickUnitSelection(true);
        numberAxis.setAutoRangeIncludesZero(false);

        XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer(true, false);
        XYPlot plot = new XYPlot(createDataset(), timeAxis, numberAxis, renderer);
        JFreeChart lineChart = new JFreeChart(chartTitle, plot);
        lineChart.setBackgroundPaint(Color.white);

        plot.setBackgroundPaint(Color.lightGray);
        plot.setDomainGridlinePaint(Color.white);
        plot.setRangeGridlinePaint(Color.white);




        ChartRenderingInfo info = new ChartRenderingInfo();
        BufferedImage bi = lineChart.createBufferedImage(1529,
                538, BufferedImage.TRANSLUCENT, info);

        JLabel picLabel = new JLabel(new ImageIcon(bi));
        add(picLabel);        
    }

    private TimeSeriesCollection createDataset() {
        TimeSeriesCollection collection = new TimeSeriesCollection();

        for (int i=0; i<20; i++) {
            TimeSeries type = new TimeSeries("Temperatures" + i);
            type.add(Minute.parseMinute("2019-09-03 00:00"), 20.88);
            type.add(Minute.parseMinute("2019-09-03 01:00"), 20.24);
            type.add(Minute.parseMinute("2019-09-03 02:00"), 20.03);
            type.add(Minute.parseMinute("2019-09-03 03:00"), 19.87);
            type.add(Minute.parseMinute("2019-09-03 04:00"), 19.93);
            type.add(Minute.parseMinute("2019-09-03 05:00"), 20.08);
            type.add(Minute.parseMinute("2019-09-03 06:00"), 19.73);
            type.add(Minute.parseMinute("2019-09-03 07:00"), 21.49);
            type.add(Minute.parseMinute("2019-09-03 08:00"), 21.9);
            type.add(Minute.parseMinute("2019-09-03 09:00"), 22.12);
            type.add(Minute.parseMinute("2019-09-03 10:00"), 22.25);
            type.add(Minute.parseMinute("2019-09-03 11:00"), 22.51);
            type.add(Minute.parseMinute("2019-09-03 12:00"), 22.81);
            type.add(Minute.parseMinute("2019-09-03 13:00"), 23.18);
            type.add(Minute.parseMinute("2019-09-03 14:00"), 23.11);
            type.add(Minute.parseMinute("2019-09-03 15:00"), 22.72);
            type.add(Minute.parseMinute("2019-09-03 16:00"), 22.76);
            type.add(Minute.parseMinute("2019-09-03 17:00"), 22.77);
            type.add(Minute.parseMinute("2019-09-03 18:00"), 22.85);
            type.add(Minute.parseMinute("2019-09-03 19:00"), 22.73);
            type.add(Minute.parseMinute("2019-09-03 20:00"), 22.4);
            type.add(Minute.parseMinute("2019-09-03 21:00"), 22.51);
            type.add(Minute.parseMinute("2019-09-03 23:00"), 23.05);
            collection.addSeries(type);
        }

        return collection;
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                TimeChart chart = new TimeChart(
                    "Date axis demo",
                    "Date axis legend position is wrong");

                chart.pack();
                chart.setVisible(true);
            }
        });
    }
}

I have tried

lineChart.getLegend().setHorizontalAlignment(HorizontalAlignment.CENTER);

but it did not change anything.

Daniel
  • 2,318
  • 2
  • 22
  • 53
  • +1 for the MVCE. But as far as I looked into this, this may be difficult: The relevant parts of the layout (namely, the width of the y axis label are) are computed "on the fly", and I don't think that there is a reasonable way to sneak this information as an offset into the legend. I only found one (crude) workaround: Doing the `createBufferedImage` call, then fetch the `info.getPlotInfo().getDataArea().getX()`, set this as the padding for the legend, and then create the (actual, final) buffered image. The result looks fine, but the approach looks quirky... – Marco13 Sep 25 '19 at 21:20
  • @Marco13, thanks I also thought on this. What do you think, would it be easier to let those red margins go and just align the legend to center? (In this case both rows need to be aligned to center, that would also look better than the actual) – Daniel Sep 25 '19 at 22:37
  • What read margins do you mean? The lines that you drew into the screenshot? However, the actual legend seems to have the full width of the available area in any case, and the contents (i.e. the "Temperature"-strings) seem to be arranged with a http://www.jfree.org/jfreechart/api/javadoc/org/jfree/chart/block/FlowArrangement.html , which, similar to a `FlowLayout`, covers the full width until it wraps into the next row - so I'm not sure where or how you'd change the alignment here... – Marco13 Sep 25 '19 at 23:12
  • Yes, the red margins I drew by hand. For center alignment please check this image which is not related to charting but shows the alignment with two rows: https://help.eclipse.org/kepler/topic/org.eclipse.wb.swing.doc.user/html/layoutmanagers/swing/images/fl_gaps.gif – Daniel Sep 26 '19 at 07:04
  • From the API docs: hAlign - the horizontal alignment (**currently ignored**). – Daniel Sep 26 '19 at 07:06
  • Also consider the approach shown [here](https://stackoverflow.com/a/13309587/230513). – trashgod Sep 26 '19 at 09:41
  • (also @trashgod ) From diving a bit into the code, I think the main problem is that JFreeChart internally does some layout computation where the result is *roughly (!) equivalent* to the BorderLayout: https://docs.oracle.com/javase/tutorial/figures/uiswing/layout/BorderLayoutDemo.png In this case, the y-axis is in the WEST and the legend is in the SOUTH, but the SOUTH has no way of "knowing" how wide the WEST will be. (*Inside* the SOUTH, the labels are arranged with something resembling a FlowLayout, and except for adding a margin at the left, there's no way to constrain that) – Marco13 Sep 26 '19 at 12:15
  • OK, let's focus on arranging the items to center. Try changing i's max from 20 to 4 in my code (in _createDataset_). Now the legend is aligned center. Once it has more rows it starts to be left-aligned. Why? – Daniel Sep 26 '19 at 15:18
  • Okay, I have to create my own Arrangement subclass to solve this. I can create a LegendTitle with a custom Arrangement so that shall be fine. I'll post my solution once finished. My intention is to override the _arrangeFN_ function. – Daniel Sep 26 '19 at 16:12

0 Answers0