Search code examples
javatextchartspositionjfreechart

Jfreechart place text/annotations/labels on desired position and line over outline of the chart


I'm trying to recreate a chart similar to the one in the following picture:

desired chart

For the moment, I managed to get this:

work in progress chart

How to add missing elements e.g axis labels out of setLabelLocation() method allowed boundaries. Also how to add value labels on shown positions?

And for the last green vertical line it seems that the line is covered by the dashed outline, here I tried to plot a secondary axis at the right side of the chart but the line is still in background, any idea to solve that?

Here is the code of the attempt :

package javaapplication;

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Stroke;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Locale;
import javax.swing.JFrame;
import javax.swing.JPanel;
import org.jfree.chart.ChartColor;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.annotations.XYLineAnnotation;
import org.jfree.chart.axis.AxisLabelLocation;
import org.jfree.chart.axis.AxisLocation;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.ValueMarker;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.chart.title.TextTitle;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.ui.HorizontalAlignment;
import org.jfree.ui.Layer;
import org.jfree.ui.RectangleEdge;
import org.jfree.ui.RectangleInsets;

public class JavaApplication { 
    public static void main(String[] args){
        PlotWindow pw = new PlotWindow();
        pw.setVisible(true);
    }
    
}

public class PlotWindow extends JFrame
{
    final double t_init = 0;
    final double step = 0.1;
    final double t_fin = 10;
    int lengthValues;
    double [] yValues;
    double [] xValues;

    //params
    double Fmax = 5;
    double Tau = 3;
    double alpha = 3;
    double deltaX;
    
            
    public PlotWindow()
    {
      super("Test JFreechart"); 
      this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      this.setLocationRelativeTo(null);
      
      // x y values
      this.lengthValues = (int) ((this.t_fin-t_init)/this.step) + 1;
      this.setTimeValues();
      this.computeDeltaX();
      this.setForceValues();
      
      
      JPanel jp = createChartPanel();
      jp.setPreferredSize(new java.awt.Dimension(546, 447));
      this.add(jp, BorderLayout.CENTER);

      this.pack();
      this.setPreferredSize(new Dimension(546, 447));
    }
    
    private void setTimeValues()
    {
        this.xValues = new double[this.lengthValues];
        for(int i = 0; i < this.lengthValues; ++i)
        {
          this.xValues[i] = this.t_init + i*this.step;    
        }
    }
   
    private void computeDeltaX()
    {
        this.deltaX = Math.sqrt(-this.Tau*Math.log(this.alpha/(1+this.alpha)));
    }
    
    private void setForceValues()
    {
        this.yValues = new double[lengthValues];
        for(int i = 0; i < lengthValues; ++i)
        {
          double A = this.Fmax*(1+1/this.alpha);
          double B = 1-Math.exp(-Math.pow(this.xValues[i]-this.deltaX, 2)/this.Tau);
          this.yValues[i] = A*B-this.Fmax/this.alpha;      
        }
    }
    
    private XYSeriesCollection createDataset() 
    {
        boolean autoSort = false;
        boolean allowDuplicateXValues = false;
        XYSeriesCollection dataset = new XYSeriesCollection();
        XYSeries series1 = new XYSeries("",autoSort,allowDuplicateXValues);
        
        for(int i = 0; i < lengthValues; ++i)
        {
            series1.add(this.xValues[i], this.yValues[i]);
        }
        dataset.addSeries(series1);
        return dataset;
    }
    
    
    private JPanel createChartPanel()
    {
        String chartTitle = "Wetting balance curve";
        String xAxisLabel = "";
        String yAxisLabel = "";
        //String yAxisLabel = "Fr(mN)";

        XYSeriesCollection dataset = createDataset();
                
        JFreeChart chart = ChartFactory.createXYLineChart(chartTitle,
                xAxisLabel, yAxisLabel, dataset,PlotOrientation.VERTICAL,false,true,false);

        XYPlot plot = chart.getXYPlot();
        
        
        //title
        TextTitle tt = new TextTitle();
        tt.setText("C:\\MENISCO ST60\\Mesures\\22-5912-100.PM1");
        tt.setPaint(Color.BLUE);
        tt.setFont( new java.awt.Font( "SansSerif", java.awt.Font.PLAIN, 10 ) );
        chart.setTitle(tt);
        //empty subtitle (offset for the title)
        TextTitle tts = new TextTitle();
        tts.setText(" ");
        tts.setPaint(Color.BLUE);
        tts.setFont( new java.awt.Font( "SansSerif", java.awt.Font.PLAIN, 6 ) );
        chart.addSubtitle(tts);

        // norm subtitle
        //TextTitle normtt = new TextTitle("          " + "Norme : J-STD-002E");
        TextTitle normtt = new TextTitle("    " + "Norme : J-STD-002E");
        normtt.setFont(new Font("SansSerif", Font.BOLD, 12));
        normtt.setPosition(RectangleEdge.BOTTOM);
        normtt.setPaint(Color.BLACK);
        normtt.setHorizontalAlignment(HorizontalAlignment.LEFT);
        chart.addSubtitle(normtt);
        
        // fmoy subtitle
        TextTitle fmoytt = new TextTitle("    " + "Force moyenne à 0.900 S: 0.25mN");
        fmoytt.setFont(new Font("SansSerif", Font.PLAIN, 10));
        fmoytt.setPosition(RectangleEdge.BOTTOM);
        fmoytt.setPaint(Color.BLUE);
        fmoytt.setHorizontalAlignment(HorizontalAlignment.LEFT);
        chart.addSubtitle(fmoytt);
              
        // axis
        //domain axis
        plot.getDomainAxis().setLowerMargin(0.0);
        plot.getDomainAxis().setUpperMargin(0.0);
        NumberAxis domain = (NumberAxis) plot.getDomainAxis();
        NumberFormat formatterd = DecimalFormat.getInstance();
        formatterd.setMinimumFractionDigits(0);
        domain.setNumberFormatOverride(formatterd);
        domain.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
        domain.setTickMarksVisible(false);
        plot.getDomainAxis().setAxisLineVisible(false);
        
        //range axis
        plot.getRangeAxis().setLabelAngle(Math.PI/2);
        plot.getRangeAxis().setLabelLocation(AxisLabelLocation.HIGH_END);
        NumberAxis range = (NumberAxis) plot.getRangeAxis();
        NumberFormat formatter = DecimalFormat.getInstance(Locale.ENGLISH);
        formatter.setMinimumFractionDigits(2);
        range.setNumberFormatOverride(formatter);
        plot.getRangeAxis().setAxisLineStroke(new BasicStroke(1.5f));
        plot.getRangeAxis().setAxisLinePaint(Color.BLUE);
        plot.getRangeAxis().setTickMarksVisible(true);
        plot.getRangeAxis().setTickMarkPaint(Color.BLACK);
        plot.getRangeAxis().setTickMarkStroke(new BasicStroke(1.5f));
        float lg = plot.getRangeAxis().getTickMarkOutsideLength();
        plot.getRangeAxis().setTickMarkInsideLength(lg);
        plot.getRangeAxis().setRange(-3, 6);

        // background;gridline;outline 
        plot.setAxisOffset(new RectangleInsets(0, 0, 0, 0));
        plot.setDomainGridlinePaint(Color.DARK_GRAY);
        plot.setRangeGridlinePaint(Color.DARK_GRAY);
        plot.setBackgroundPaint(Color.white);
        plot.setOutlineStroke(plot.getDomainGridlineStroke()); //dashed outline
        plot.setOutlinePaint(Color.DARK_GRAY);
        XYItemRenderer  renderer =  plot.getRenderer();
        renderer.setSeriesPaint(0, ChartColor.VERY_DARK_GREEN); // set green color to the xyline

        // vertical lines
        ValueMarker marker0 = new ValueMarker(0.2,Color.MAGENTA,new BasicStroke(1.5f));  // position is the value on the axis
        ValueMarker marker1 = new ValueMarker(1,Color.MAGENTA,new BasicStroke(1.5f));
        ValueMarker marker2 = new ValueMarker(4,Color.GREEN,new BasicStroke(1.5f));  // position is the value on the axis
        plot.addDomainMarker(marker0, Layer.FOREGROUND);
        plot.addDomainMarker(marker1,Layer.FOREGROUND);
        plot.addDomainMarker(marker2,Layer.FOREGROUND);

        //horizontal lines
        XYLineAnnotation line = new XYLineAnnotation(0, 4, 10, 4, new BasicStroke(2.0f), Color.green);
        plot.addAnnotation(line);
        XYLineAnnotation line0 = new XYLineAnnotation(0, 0, 10, 0, new BasicStroke(1.0f), Color.BLUE);
        plot.addAnnotation(line0);
       
       
        //dashed horizontal line
        float[] dash = {10.0f, 3.0f, 3.0f};
        Stroke dashed = new BasicStroke(1.0f, BasicStroke.CAP_BUTT,
                BasicStroke.JOIN_MITER, 10.0f, dash, 0.0f);
        XYLineAnnotation line1 = new XYLineAnnotation(0, -1, 10, -1, dashed, Color.MAGENTA);
        plot.addAnnotation(line1);
        
        //right side axis
        NumberAxis range2 = new NumberAxis(" ");
        range2.setAxisLinePaint(Color.GREEN);
        range2.setAxisLineStroke(new BasicStroke(1.5f));
        range2.setTickMarksVisible(false);
        range2.setTickLabelsVisible(false);
        plot.setRangeAxis(1, range2);
        plot.setRangeAxisLocation(1, AxisLocation.BOTTOM_OR_RIGHT);
                
        return new ChartPanel(chart);
    }
    
}

Solution

  • As discussed here, one way to get the desired axis markings is to add a SymbolAxis on the right, as illustrated here and below. In addition,

    • The axis/plot overlap may reflect the use of RectangleInsets in setAxisOffset(), omitted below; a third domain marker is shown instead.

    • Tick mark position support is provided by DataAxis and DateTickMarkPosition.

    • Instead of padding titles with spaces, use setPadding(), seen here and below.

    • Adjust the chart's overall size as shown here.

    • In a Swing context, construct and manipulate GUI objects only on the event dispatch thread.

    ChartImage

    import java.awt.BasicStroke;
    import java.awt.BorderLayout;
    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.EventQueue;
    import java.awt.Font;
    import java.awt.Stroke;
    import java.text.DecimalFormat;
    import java.text.NumberFormat;
    import java.util.Locale;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import org.jfree.chart.ChartColor;
    import org.jfree.chart.ChartFactory;
    import org.jfree.chart.ChartPanel;
    import org.jfree.chart.JFreeChart;
    import org.jfree.chart.annotations.XYLineAnnotation;
    import org.jfree.chart.axis.AxisLocation;
    import org.jfree.chart.axis.NumberAxis;
    import org.jfree.chart.axis.SymbolAxis;
    import org.jfree.chart.plot.PlotOrientation;
    import org.jfree.chart.plot.ValueMarker;
    import org.jfree.chart.plot.XYPlot;
    import org.jfree.chart.renderer.xy.XYItemRenderer;
    import org.jfree.chart.title.TextTitle;
    import org.jfree.data.xy.XYSeries;
    import org.jfree.data.xy.XYSeriesCollection;
    import org.jfree.chart.ui.HorizontalAlignment;
    import org.jfree.chart.ui.Layer;
    import org.jfree.chart.ui.RectangleEdge;
    
    /**
     * @see https://stackoverflow.com/q/74592572/230513
     * @see https://github.com/jfree/jfreechart/discussions/327
     */
    public class GitHub327 {
        
        public static void main(String[] args) {
            EventQueue.invokeLater(() -> {
                PlotWindow pw = new PlotWindow();
                pw.setLocationRelativeTo(null);
                pw.setVisible(true);
            });
        }
        
        private static class PlotWindow extends JFrame {
            
            final double t_init = 0;
            final double step = 0.1;
            final double t_fin = 10;
            int lengthValues;
            double[] yValues;
            double[] xValues;
    
            //params
            double Fmax = 5;
            double Tau = 3;
            double alpha = 3;
            double deltaX;
            
            public PlotWindow() {
                super("Test JFreechart");
                this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                this.setLocationRelativeTo(null);
    
                // x y values
                this.lengthValues = (int) ((this.t_fin - t_init) / this.step) + 1;
                this.setTimeValues();
                this.computeDeltaX();
                this.setForceValues();
                
                this.add(createChartPanel(), BorderLayout.CENTER);
                this.pack();
            }
            
            private void setTimeValues() {
                this.xValues = new double[this.lengthValues];
                for (int i = 0; i < this.lengthValues; ++i) {
                    this.xValues[i] = this.t_init + i * this.step;
                }
            }
            
            private void computeDeltaX() {
                this.deltaX = Math.sqrt(-this.Tau * Math.log(this.alpha / (1 + this.alpha)));
            }
            
            private void setForceValues() {
                this.yValues = new double[lengthValues];
                for (int i = 0; i < lengthValues; ++i) {
                    double A = this.Fmax * (1 + 1 / this.alpha);
                    double B = 1 - Math.exp(-Math.pow(this.xValues[i] - this.deltaX, 2) / this.Tau);
                    this.yValues[i] = A * B - this.Fmax / this.alpha;
                }
            }
            
            private XYSeriesCollection createDataset() {
                boolean autoSort = false;
                boolean allowDuplicateXValues = false;
                XYSeriesCollection dataset = new XYSeriesCollection();
                XYSeries series1 = new XYSeries("", autoSort, allowDuplicateXValues);
                
                for (int i = 0; i < lengthValues; ++i) {
                    series1.add(this.xValues[i], this.yValues[i]);
                }
                dataset.addSeries(series1);
                var series2 = new XYSeries("S");
                dataset.addSeries(series2);
                return dataset;
            }
            
            private JPanel createChartPanel() {
                String chartTitle = "Wetting balance curve";
                String xAxisLabel = "X";
                String yAxisLabel = "Y";
                XYSeriesCollection dataset = createDataset();
                JFreeChart chart = ChartFactory.createXYLineChart(chartTitle,
                    xAxisLabel, yAxisLabel, dataset, PlotOrientation.VERTICAL, false, true, false);
                XYPlot plot = chart.getXYPlot();
    
                //title
                TextTitle tt = new TextTitle();
                tt.setText("C:\\MENISCO ST60\\Mesures\\22-5912-100.PM1");
                tt.setPaint(Color.BLUE);
                tt.setFont(new Font("SansSerif", Font.PLAIN, 12));
                tt.setPadding(8, 8, 8, 8);
                chart.setTitle(tt);
    
                // norm subtitle
                TextTitle normtt = new TextTitle("Norme : J-STD-002E");
                normtt.setFont(new Font("SansSerif", Font.BOLD, 12));
                normtt.setPosition(RectangleEdge.BOTTOM);
                normtt.setPaint(Color.BLACK);
                normtt.setHorizontalAlignment(HorizontalAlignment.LEFT);
                normtt.setPadding(0, 16, 8, 0);
                chart.addSubtitle(normtt);
    
                // fmoy subtitle
                TextTitle fmoytt = new TextTitle("Force moyenne à 0.900 S: 0.25mN");
                fmoytt.setFont(new Font("SansSerif", Font.PLAIN, 10));
                fmoytt.setPosition(RectangleEdge.BOTTOM);
                fmoytt.setPaint(Color.BLUE);
                fmoytt.setHorizontalAlignment(HorizontalAlignment.LEFT);
                fmoytt.setPadding(0, 16, 2, 0);
                chart.addSubtitle(fmoytt);
    
                // axis
                //domain axis
                plot.getDomainAxis().setLowerMargin(0.0);
                plot.getDomainAxis().setUpperMargin(0.0);
                NumberAxis domain = (NumberAxis) plot.getDomainAxis();
                NumberFormat formatterd = DecimalFormat.getInstance();
                formatterd.setMinimumFractionDigits(0);
                domain.setNumberFormatOverride(formatterd);
                domain.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
                domain.setTickMarksVisible(false);
                //domain.setAxisLineVisible(false);
    
                //range axis
                plot.getRangeAxis().setLabelAngle(Math.PI / 2);
                NumberAxis range = (NumberAxis) plot.getRangeAxis();
                NumberFormat formatter = DecimalFormat.getInstance(Locale.ENGLISH);
                formatter.setMinimumFractionDigits(2);
                range.setNumberFormatOverride(formatter);
                plot.getRangeAxis().setAxisLineStroke(new BasicStroke(1.5f));
                plot.getRangeAxis().setAxisLinePaint(Color.BLUE);
                plot.getRangeAxis().setTickMarksVisible(true);
                plot.getRangeAxis().setTickMarkPaint(Color.BLACK);
                plot.getRangeAxis().setTickMarkStroke(new BasicStroke(1.5f));
                float lg = plot.getRangeAxis().getTickMarkOutsideLength();
                plot.getRangeAxis().setTickMarkInsideLength(lg);
                plot.getRangeAxis().setRange(-3, 6);
    
                // background;gridline;outline 
                //plot.setAxisOffset(new RectangleInsets(0, 0, 0, 0));
                plot.setDomainGridlinePaint(Color.DARK_GRAY);
                plot.setRangeGridlinePaint(Color.DARK_GRAY);
                plot.setBackgroundPaint(Color.white);
                plot.setOutlineStroke(plot.getDomainGridlineStroke()); //dashed outline
                plot.setOutlinePaint(Color.DARK_GRAY);
                XYItemRenderer renderer = plot.getRenderer();
                renderer.setSeriesPaint(0, ChartColor.VERY_DARK_GREEN); // set green color to the xyline
    
                // vertical lines
                ValueMarker marker0 = new ValueMarker(0.2, Color.MAGENTA, new BasicStroke(1.5f));  // position is the value on the axis
                ValueMarker marker1 = new ValueMarker(1, Color.MAGENTA, new BasicStroke(1.5f));
                ValueMarker marker2 = new ValueMarker(4, Color.GREEN, new BasicStroke(1.5f));  // position is the value on the axis
                plot.addDomainMarker(marker0, Layer.FOREGROUND);
                plot.addDomainMarker(marker1, Layer.FOREGROUND);
                plot.addDomainMarker(marker2, Layer.FOREGROUND);
                
                var marker3 = new ValueMarker(10, Color.GREEN, new BasicStroke(1.5f));
                plot.addDomainMarker(marker3, Layer.FOREGROUND);
    
                //horizontal lines
                XYLineAnnotation line = new XYLineAnnotation(0, 4, 10, 4, new BasicStroke(2.0f), Color.green);
                plot.addAnnotation(line);
                XYLineAnnotation line0 = new XYLineAnnotation(0, 0, 10, 0, new BasicStroke(1.0f), Color.BLUE);
                plot.addAnnotation(line0);
    
                //dashed horizontal line
                float[] dash = {10.0f, 3.0f, 3.0f};
                Stroke dashed = new BasicStroke(1.0f, BasicStroke.CAP_BUTT,
                    BasicStroke.JOIN_MITER, 10.0f, dash, 0.0f);
                XYLineAnnotation line1 = new XYLineAnnotation(0, -1, 10, -1, dashed, Color.MAGENTA);
                plot.addAnnotation(line1);
    
                //right side axis
                String[] syms = new String[]{"", "", "", "t(s)", "", "", "", "4.0", "", ""};
                var range2 = new SymbolAxis("", syms);
                range2.setGridBandsVisible(false);
                plot.setRangeAxis(1, range2);
                plot.setRangeAxisLocation(1, AxisLocation.BOTTOM_OR_RIGHT);
                plot.mapDatasetToRangeAxis(1, 0);
                
                return new ChartPanel(chart) {
                    @Override
                    public Dimension getPreferredSize() {
                        return new Dimension(640, 480);
                    }
                };
            }
        }
    }