gt:         new float[] {5f, 2.5f}, 0);
context: /*
   * Copyright 2006-2021 The MZmine Development Team
   *
   * This file is part of MZmine.
   *
   * MZmine is free software; you can redistribute it and/or modify it under the terms of the GNU
   * General Public License as published by the Free Software Foundation; either version 2 of the
   * License, or (at your option) any later version.
   *
   * MZmine is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
   * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
   * General Public License for more details.
   *
   * You should have received a copy of the GNU General Public License along with MZmine; if not,
   * write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
   *
   */
  
  package io.github.mzmine.gui.chartbasics;
  
  import java.awt.BasicStroke;
  import java.awt.Color;
  import java.awt.Paint;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.List;
  import java.util.function.DoubleFunction;
  import java.util.logging.Logger;
  import org.apache.commons.math3.analysis.function.Gaussian;
  import org.apache.commons.math3.fitting.GaussianCurveFitter;
  import org.apache.commons.math3.fitting.WeightedObservedPoints;
  import org.jfree.chart.ChartFactory;
  import org.jfree.chart.JFreeChart;
  import org.jfree.chart.plot.PlotOrientation;
  import org.jfree.chart.plot.ValueMarker;
  import org.jfree.chart.plot.XYPlot;
  import org.jfree.chart.renderer.xy.StandardXYBarPainter;
  import org.jfree.chart.renderer.xy.XYBarRenderer;
  import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
  import org.jfree.data.Range;
  import org.jfree.data.statistics.HistogramDataset;
  import org.jfree.data.xy.XYBarDataset;
  import org.jfree.data.xy.XYDataset;
  import org.jfree.data.xy.XYSeries;
  import org.jfree.data.xy.XYSeriesCollection;
  import io.github.mzmine.datamodel.DataPoint;
  import io.github.mzmine.datamodel.impl.SimpleDataPoint;
  import io.github.mzmine.util.maths.Precision;
  import it.unimi.dsi.fastutil.doubles.DoubleArrayList;
  
  public class HistogramChartFactory {
  
    private static final Logger logger = Logger.getLogger(HistogramChartFactory.class.getName());
  
    private static GaussianCurveFitter fitter = GaussianCurveFitter.create().withMaxIterations(10000);
  
    /**
     * Performs Gaussian fit on XYSeries
     *
     * @param data the data
     * @param gMin lower bound of Gaussian fit
     * @param gMax upper bound of Gaussian fit
     * @param sigDigits number of significant digits
     * @return double[] {normFactor, mean, sigma} as a result of
     *         GaussianCurveFitter.create().fit(obs.toList())
     */
    public static double[] gaussianFit(List<DataPoint> data, double gMin, double gMax) {
      // gaussian fit
      WeightedObservedPoints obs = new WeightedObservedPoints();
  
      for (int i = 0; i < data.size(); i++) {
        double x = data.get(i).getMZ();
        if (x >= gMin && x <= gMax)
          obs.add(x, data.get(i).getIntensity());
      }
      try {
        return fitter.fit(obs.toList());
      } catch (Exception e) {
        e.printStackTrace();
        logger.severe("Cannot fit Gaussian from " + gMin + " to " + gMax);
        return null;
      }
    }
  
    /**
     * Performs Gaussian fit on XYSeries
     *
     * @param series the data
     * @param gMin lower bound of Gaussian fit
     * @param gMax upper bound of Gaussian fit
     * @param sigDigits number of significant digits
     * @return double[] {normFactor, mean, sigma} as a result of
     *         GaussianCurveFitter.create().fit(obs.toList())
     */
    public static double[] gaussianFit(XYSeries series, double gMin, double gMax) {
      // gaussian fit
      WeightedObservedPoints obs = new WeightedObservedPoints();
  
      for (int i = 0; i < series.getItemCount(); i++) {
        double x = series.getX(i).doubleValue();
        if (x >= gMin && x <= gMax)
          obs.add(x, series.getY(i).doubleValue());
      }
  
      return fitter.fit(obs.toList());
    }
  
    /**
     * Performs Gaussian fit on XYSeries
     *
     * @param data the data
     * @param series the series index
     * @param gMin lower bound of Gaussian fit
     * @param gMax upper bound of Gaussian fit
     * @param sigDigits number of significant digits
     * @return double[] {normFactor, mean, sigma} as a result of
     *         GaussianCurveFitter.create().fit(obs.toList())
     */
    public static double[] gaussianFit(XYDataset data, int series, double gMin, double gMax) {
      // gaussian fit
      WeightedObservedPoints obs = new WeightedObservedPoints();
  
      for (int i = 0; i < data.getItemCount(series); i++) {
        double x = data.getXValue(series, i);
        if (x >= gMin && x <= gMax)
          obs.add(x, data.getYValue(series, i));
      }
      return fitter.fit(obs.toList());
    }
  
    /**
     * Adds a Gaussian curve to the plot
     *
     * @param plot
     * @param series the data
     * @param gMin lower bound of Gaussian fit
     * @param gMax upper bound of Gaussian fit
     * @param sigDigits number of significant digits
     * @return
     */
    public static double[] addGaussianFit(XYPlot plot, XYSeries series, double gMin, double gMax,
        int sigDigits, boolean annotations) {
      double[] fit = gaussianFit(series, gMin, gMax);
      double minval = series.getX(0).doubleValue();
      double maxval = series.getX(series.getItemCount() - 1).doubleValue();
      return addGaussianFit(plot, fit, minval, maxval, gMin, gMax, sigDigits, annotations);
    }
  
    /**
     * Adds a Gaussian curve to the plot
     *
     * @param plot
     * @param data the data
     * @param series the series index
     * @param gMin lower bound of Gaussian fit
     * @param gMax upper bound of Gaussian fit
     * @param sigDigits number of significant digits
     * @return
     */
    public static double[] addGaussianFit(XYPlot plot, XYDataset data, int series, double gMin,
        double gMax, int sigDigits, boolean annotations) {
      double[] fit = gaussianFit(data, series, gMin, gMax);
      double minval = data.getX(series, 0).doubleValue();
      double maxval = data.getX(series, data.getItemCount(series) - 1).doubleValue();
      return addGaussianFit(plot, fit, minval, maxval, gMin, gMax, sigDigits, annotations);
    }
  
    /**
     * Adds a Gaussian curve to the plot
     *
     * @param plot
     * @param fit double[] {normFactor, mean, sigma}
     * @param drawStart start of curve
     * @param drawEnd end of curve
     * @param sigDigits number of significant digits
     * @return
     */
    public static double[] addGaussianFit(XYPlot plot, double[] fit, double drawStart, double drawEnd,
        int sigDigits, boolean annotations) {
      return addGaussianFit(plot, fit, drawStart, drawEnd, drawStart, drawEnd, sigDigits,
          annotations);
    }
  
    /**
     * Adds a Gaussian curve to the plot
     *
     * @param plot
     * @param fit double[] {normFactor, mean, sigma}
     * @param drawStart start of curve
     * @param drawEnd end of curve
     * @param gMin lower bound of Gaussian fit
     * @param gMax upper bound of Gaussian fit
     * @param sigDigits number of significant digits
     * @return
     */
    public static double[] addGaussianFit(XYPlot plot, double[] fit, double drawStart, double drawEnd,
        double gMin, double gMax, int sigDigits, boolean annotations) {
      double gWidth = gMax - gMin;
  
      Gaussian g = new Gaussian(fit[0], fit[1], fit[2]);
  
      // create xy series for gaussian
      String mean = Precision.toString(fit[1], sigDigits, 7);
      String sigma = Precision.toString(fit[2], sigDigits, 7);
      String norm = Precision.toString(fit[0], sigDigits, 7);
      XYSeries gs = new XYSeries("Gaussian: " + mean + " \u00B1 " + sigma + " [" + norm
          + "] (mean \u00B1 sigma [normalisation])");
      // add lower dp number out of gaussian fit range
      int steps = 100;
      if (gMin > drawStart) {
        for (int i = 0; i <= steps; i++) {
          double x = drawStart + ((gMin - drawStart) / steps) * i;
          double y = g.value(x);
          gs.add(x, y);
        }
      }
      // add high resolution in gaussian fit area
      steps = 1000;
      for (int i = 0; i <= steps; i++) {
        double x = gMin + (gWidth / steps) * i;
        double y = g.value(x);
        gs.add(x, y);
      }
      // add lower dp number out of gaussian fit range
      steps = 100;
      if (gMax < drawEnd) {
        for (int i = 0; i <= steps; i++) {
          double x = gMax + ((drawEnd - gMax) / steps) * i;
          double y = g.value(x);
          gs.add(x, y);
        }
      }
      // add gaussian
      XYSeriesCollection gsdata = new XYSeriesCollection(gs);
      int index = plot.getDatasetCount();
      plot.setDataset(index, gsdata);
      plot.setRenderer(index, new XYLineAndShapeRenderer(true, false));
  
      if (annotations)
        addGaussianFitAnnotations(plot, fit);
  
      return fit;
    }
  
    /**
     * Adds annotations to the Gaussian fit parameters
     *
     * @param plot
     * @param fit Gaussian fit {normalisation factor, mean, sigma}
     */
    public static void addGaussianFitAnnotations(XYPlot plot, double[] fit) {
      Paint c = plot.getDomainCrosshairPaint();
      BasicStroke s = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 1,
  