org.gwaspi.gui.reports.SampleQAHetzygPlotZoom.java Source code

Java tutorial

Introduction

Here is the source code for org.gwaspi.gui.reports.SampleQAHetzygPlotZoom.java

Source

/*
 * Copyright (C) 2013 Universitat Pompeu Fabra
 *
 * This program 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 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.gwaspi.gui.reports;

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.geom.Rectangle2D;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import javax.swing.AbstractAction;
import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.LayoutStyle;
import javax.swing.SwingConstants;
import org.gwaspi.dao.MatrixService;
import org.gwaspi.global.Config;
import org.gwaspi.global.Text;
import org.gwaspi.global.Utils;
import org.gwaspi.gui.GWASpiExplorerPanel;
import org.gwaspi.gui.utils.CursorUtils;
import org.gwaspi.gui.utils.Dialogs;
import org.gwaspi.model.MatricesList;
import org.gwaspi.model.MatrixMetadata;
import org.gwaspi.model.OperationKey;
import org.gwaspi.model.SampleKey;
import org.gwaspi.reports.GenericReportGenerator;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.labels.AbstractXYItemLabelGenerator;
import org.jfree.chart.labels.ItemLabelAnchor;
import org.jfree.chart.labels.ItemLabelPosition;
import org.jfree.chart.labels.StandardXYToolTipGenerator;
import org.jfree.chart.labels.XYItemLabelGenerator;
import org.jfree.chart.labels.XYToolTipGenerator;
import org.jfree.chart.plot.Marker;
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.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.xy.XYDataset;
import org.jfree.ui.RectangleAnchor;
import org.jfree.ui.TextAnchor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public final class SampleQAHetzygPlotZoom extends JPanel {

    private final Logger log = LoggerFactory.getLogger(SampleQAHetzygPlotZoom.class);

    public static final String PLOT_SAMPLEQA_HETZYG_THRESHOLD_CONFIG = "CHART_SAMPLEQA_HETZYG_THRESHOLD";
    public static final double PLOT_SAMPLEQA_HETZYG_THRESHOLD_DEFAULT = 0.5;
    public static final String PLOT_SAMPLEQA_MISSING_THRESHOLD_CONFIG = "CHART_SAMPLEQA_MISSING_THRESHOLD";
    public static final double PLOT_SAMPLEQA_MISSING_THRESHOLD_DEFAULT = 0.5;
    private static final Color PLOT_MANHATTAN_BACKGROUND = Color.getHSBColor(0.1f, 0.0f, 0.9f);
    private static final Color PLOT_MANHATTAN_DOTS = Color.red;
    /**
     * roughly 2000MB needed per 100.000 plotted markers
     */
    private static final int DEFAULT_NUM_MARKERS // NOTE unused
            = (int) Math.round(
                    100000 * ((double) Config.getSingleton().getInteger(Config.PROPERTY_MAX_HEAP_MB, -1) / 2000));

    private final OperationKey operationKey;
    private Map<String, SampleKey> labeler;
    private Double hetzyThreshold;
    private Double missingThreshold;

    /**
     * Creates new form ManhattanPlotZoom
     *
     * @param operationKey
     */
    public SampleQAHetzygPlotZoom(OperationKey operationKey) throws IOException {

        this.operationKey = operationKey;
        this.hetzyThreshold = 0.015; // XXX Why is it different then PLOT_SAMPLEQA_HETZYG_THRESHOLD_DEFAULT?
        this.missingThreshold = PLOT_SAMPLEQA_MISSING_THRESHOLD_DEFAULT;

        initChart();

        setCursor(CursorUtils.DEFAULT_CURSOR);
    }

    private MatrixService getMatrixService() {
        return MatricesList.getMatrixService();
    }

    public void initChart() throws IOException {

        hetzyThreshold = Config.getSingleton().getDouble(PLOT_SAMPLEQA_HETZYG_THRESHOLD_CONFIG,
                PLOT_SAMPLEQA_HETZYG_THRESHOLD_DEFAULT);
        missingThreshold = Config.getSingleton().getDouble(PLOT_SAMPLEQA_MISSING_THRESHOLD_CONFIG,
                PLOT_SAMPLEQA_MISSING_THRESHOLD_DEFAULT);

        final XYDataset initXYDataset = getSampleHetzygDataset(operationKey);
        final JFreeChart zoomChart = createChart(initXYDataset);
        final ChartPanel zoomPanel = new ChartPanel(zoomChart);

        zoomPanel.setInitialDelay(10);
        zoomPanel.setDismissDelay(8000);

        initGUI(zoomChart, zoomPanel);
    }

    private void initGUI(final JFreeChart zoomChart, final ChartPanel zoomPanel) throws IOException {

        //      setCursor(CursorUtils.WAIT_CURSOR);

        final JPanel pnl_ChartNavigator = new JPanel();
        final JPanel pnl_Chart = new JPanel();
        final JScrollPane scrl_Chart = new JScrollPane();
        final JPanel pnl_Footer = new JPanel();
        final JPanel pnl_FooterGroup1 = new JPanel();
        final JPanel pnl_FooterGroup0 = new JPanel();
        final JButton btn_Save = new JButton();
        final JButton btn_Reset = new JButton();

        final JLabel lbl_thresholds = new JLabel();
        final JLabel lbl_hetzy = new JLabel();
        final JTextField txt_hetzy = new JTextField();
        final JButton btn_redraw = new JButton();
        final JLabel lbl_missing = new JLabel();
        final JTextField txt_missing = new JTextField();

        final String titlePlot = Text.Reports.smplHetzyVsMissingRat;

        pnl_ChartNavigator.setBorder(GWASpiExplorerPanel.createMainTitledBorder(titlePlot)); // NOI18N

        pnl_Chart.setBorder(GWASpiExplorerPanel.createLineBorder());

        scrl_Chart.getViewport().add(zoomPanel);
        pnl_Chart.add(scrl_Chart, BorderLayout.CENTER);

        // <editor-fold defaultstate="expanded" desc="LAYOUT1">
        GroupLayout pnl_ChartLayout = new GroupLayout(pnl_Chart);
        pnl_Chart.setLayout(pnl_ChartLayout);
        pnl_ChartLayout.setHorizontalGroup(pnl_ChartLayout.createParallelGroup(GroupLayout.Alignment.LEADING)
                .addComponent(scrl_Chart, GroupLayout.DEFAULT_SIZE, 600, Short.MAX_VALUE));
        pnl_ChartLayout.setVerticalGroup(pnl_ChartLayout.createParallelGroup(GroupLayout.Alignment.LEADING)
                .addComponent(scrl_Chart, GroupLayout.DEFAULT_SIZE, 600, Short.MAX_VALUE));

        GroupLayout pnl_ChartNavigatorLayout = new GroupLayout(pnl_ChartNavigator);
        pnl_ChartNavigator.setLayout(pnl_ChartNavigatorLayout);
        pnl_ChartNavigatorLayout.setHorizontalGroup(pnl_ChartNavigatorLayout
                .createParallelGroup(GroupLayout.Alignment.LEADING).addGroup(GroupLayout.Alignment.TRAILING,
                        pnl_ChartNavigatorLayout
                                .createSequentialGroup().addContainerGap().addComponent(pnl_Chart,
                                        GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                                .addContainerGap()));
        pnl_ChartNavigatorLayout
                .setVerticalGroup(pnl_ChartNavigatorLayout.createParallelGroup(GroupLayout.Alignment.LEADING)
                        .addGroup(pnl_ChartNavigatorLayout
                                .createSequentialGroup().addContainerGap().addComponent(pnl_Chart,
                                        GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                                .addContainerGap()));
        // </editor-fold>

        lbl_thresholds.setText(Text.Reports.thresholds);

        lbl_hetzy.setText(Text.Reports.heterozygosity);
        txt_hetzy.setText(hetzyThreshold.toString());

        lbl_missing.setText(Text.Reports.missRatio);
        txt_missing.setText(missingThreshold.toString());
        btn_redraw.setAction(new RedrawAction(txt_hetzy, txt_missing));

        final MatrixMetadata rdMatrixMetadata = getMatrixService().getMatrix(operationKey.getParentMatrixKey());
        final String originFriendlyName = rdMatrixMetadata.getFriendlyName();
        btn_Save.setAction(new SaveAsAction(
                "SampleQA_hetzyg-missingrat_" + Utils.stripNonAlphaNumeric(originFriendlyName) + ".png", scrl_Chart,
                zoomChart, this));

        btn_Reset.setAction(new ResetAction());

        //<editor-fold defaultstate="expanded" desc="FOOTER">
        GroupLayout pnl_FooterGroup0Layout = new GroupLayout(pnl_FooterGroup0);
        pnl_FooterGroup0.setLayout(pnl_FooterGroup0Layout);
        pnl_FooterGroup0Layout.setHorizontalGroup(pnl_FooterGroup0Layout
                .createParallelGroup(GroupLayout.Alignment.LEADING)
                .addGroup(pnl_FooterGroup0Layout.createSequentialGroup().addContainerGap()
                        .addGroup(pnl_FooterGroup0Layout.createParallelGroup(GroupLayout.Alignment.LEADING)
                                .addGroup(pnl_FooterGroup0Layout.createSequentialGroup()
                                        .addGroup(pnl_FooterGroup0Layout
                                                .createParallelGroup(GroupLayout.Alignment.LEADING)
                                                .addComponent(lbl_hetzy).addComponent(lbl_missing))
                                        .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
                                        .addGroup(pnl_FooterGroup0Layout
                                                .createParallelGroup(GroupLayout.Alignment.LEADING)
                                                .addComponent(txt_hetzy, GroupLayout.PREFERRED_SIZE, 113,
                                                        GroupLayout.PREFERRED_SIZE)
                                                .addGroup(pnl_FooterGroup0Layout.createSequentialGroup()
                                                        .addComponent(txt_missing, GroupLayout.PREFERRED_SIZE, 113,
                                                                GroupLayout.PREFERRED_SIZE)
                                                        .addGap(6, 6, 6).addComponent(btn_redraw,
                                                                GroupLayout.PREFERRED_SIZE, 103,
                                                                GroupLayout.PREFERRED_SIZE))))
                                .addComponent(lbl_thresholds))
                        .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)));
        pnl_FooterGroup0Layout.setVerticalGroup(pnl_FooterGroup0Layout
                .createParallelGroup(GroupLayout.Alignment.LEADING)
                .addGroup(GroupLayout.Alignment.TRAILING, pnl_FooterGroup0Layout.createSequentialGroup()
                        .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE).addComponent(lbl_thresholds)
                        .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
                        .addGroup(pnl_FooterGroup0Layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
                                .addComponent(lbl_hetzy).addComponent(txt_hetzy, GroupLayout.PREFERRED_SIZE,
                                        GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE))
                        .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
                        .addGroup(pnl_FooterGroup0Layout.createParallelGroup(GroupLayout.Alignment.LEADING, false)
                                .addComponent(btn_redraw, GroupLayout.Alignment.TRAILING, 0, 0, Short.MAX_VALUE)
                                .addGroup(GroupLayout.Alignment.TRAILING,
                                        pnl_FooterGroup0Layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
                                                .addComponent(lbl_missing).addComponent(txt_missing)))));

        GroupLayout pnl_FooterGroup1Layout = new GroupLayout(pnl_FooterGroup1);
        pnl_FooterGroup1.setLayout(pnl_FooterGroup1Layout);
        pnl_FooterGroup1Layout
                .setHorizontalGroup(pnl_FooterGroup1Layout.createParallelGroup(GroupLayout.Alignment.LEADING)
                        .addGroup(pnl_FooterGroup1Layout.createSequentialGroup().addContainerGap()
                                .addComponent(btn_Reset, GroupLayout.PREFERRED_SIZE, 79, GroupLayout.PREFERRED_SIZE)
                                .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
                                .addComponent(btn_Save, GroupLayout.PREFERRED_SIZE, 84, GroupLayout.PREFERRED_SIZE)
                                .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)));
        pnl_FooterGroup1Layout.linkSize(SwingConstants.HORIZONTAL, new Component[] { btn_Reset, btn_Save });
        pnl_FooterGroup1Layout
                .setVerticalGroup(pnl_FooterGroup1Layout.createParallelGroup(GroupLayout.Alignment.LEADING)
                        .addGroup(pnl_FooterGroup1Layout.createParallelGroup(GroupLayout.Alignment.CENTER)
                                .addComponent(btn_Reset).addComponent(btn_Save)));

        pnl_FooterGroup1Layout.linkSize(SwingConstants.VERTICAL, new Component[] { btn_Reset, btn_Save });

        GroupLayout pnl_FooterLayout = new GroupLayout(pnl_Footer);
        pnl_Footer.setLayout(pnl_FooterLayout);
        pnl_FooterLayout
                .setHorizontalGroup(pnl_FooterLayout.createParallelGroup(GroupLayout.Alignment.LEADING)
                        .addGroup(GroupLayout.Alignment.TRAILING, pnl_FooterLayout.createSequentialGroup()
                                .addComponent(pnl_FooterGroup0, GroupLayout.PREFERRED_SIZE,
                                        GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
                                .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, 174, Short.MAX_VALUE)
                                .addComponent(pnl_FooterGroup1, GroupLayout.PREFERRED_SIZE,
                                        GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
                                .addContainerGap()));
        pnl_FooterLayout.setVerticalGroup(pnl_FooterLayout.createParallelGroup(GroupLayout.Alignment.LEADING)
                .addGroup(GroupLayout.Alignment.TRAILING, pnl_FooterLayout.createSequentialGroup()
                        .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                        .addGroup(pnl_FooterLayout.createParallelGroup(GroupLayout.Alignment.TRAILING)
                                .addComponent(pnl_FooterGroup0, GroupLayout.PREFERRED_SIZE,
                                        GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
                                .addComponent(pnl_FooterGroup1, GroupLayout.PREFERRED_SIZE,
                                        GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE))
                        .addContainerGap()));
        //</editor-fold>

        //<editor-fold defaultstate="expanded" desc="LAYOUT">
        GroupLayout layout = new GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING).addGroup(layout
                .createSequentialGroup().addContainerGap()
                .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
                        .addComponent(pnl_ChartNavigator, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE,
                                Short.MAX_VALUE)
                        .addComponent(pnl_Footer, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE,
                                Short.MAX_VALUE))
                .addContainerGap()));
        layout.setVerticalGroup(
                layout.createParallelGroup(GroupLayout.Alignment.LEADING)
                        .addGroup(
                                layout.createSequentialGroup()
                                        .addComponent(pnl_ChartNavigator, GroupLayout.PREFERRED_SIZE,
                                                GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
                                        .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED)
                                        .addComponent(pnl_Footer, GroupLayout.PREFERRED_SIZE,
                                                GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
                                        .addContainerGap()));

        //</editor-fold>

        //      setCursor(CursorUtils.DEFAULT_CURSOR);
    }

    // <editor-fold defaultstate="expanded" desc="CHART GENERATOR">
    private XYDataset getSampleHetzygDataset(OperationKey operationKey) throws IOException {

        XYDataset xyd = GenericReportGenerator.getSampleHetzygDataset(this, operationKey);
        return xyd;
    }

    private JFreeChart createChart(XYDataset dataset) {
        JFreeChart chart = ChartFactory.createScatterPlot("Heterozygosity vs. Missing Ratio",
                "Heterozygosity Ratio", "Missing Ratio", dataset, PlotOrientation.VERTICAL, true, false, false);

        XYPlot plot = (XYPlot) chart.getPlot();
        plot.setNoDataMessage("NO DATA");
        plot.setDomainZeroBaselineVisible(true);
        plot.setRangeZeroBaselineVisible(true);

        // CHART BACKGROUD COLOR
        chart.setBackgroundPaint(Color.getHSBColor(0.1f, 0.1f, 1.0f)); // Hue, saturation, brightness
        plot.setBackgroundPaint(PLOT_MANHATTAN_BACKGROUND); // Hue, saturation, brightness 9

        // GRIDLINES
        plot.setDomainGridlineStroke(new BasicStroke(0.0f));
        plot.setDomainMinorGridlineStroke(new BasicStroke(0.0f));
        plot.setDomainGridlinePaint(PLOT_MANHATTAN_BACKGROUND.darker().darker()); // Hue, saturation, brightness 7
        plot.setDomainMinorGridlinePaint(PLOT_MANHATTAN_BACKGROUND); // Hue, saturation, brightness 9
        plot.setRangeGridlineStroke(new BasicStroke(0.0f));
        plot.setRangeMinorGridlineStroke(new BasicStroke(0.0f));
        plot.setRangeGridlinePaint(PLOT_MANHATTAN_BACKGROUND.darker().darker()); // Hue, saturation, brightness 7
        plot.setRangeMinorGridlinePaint(PLOT_MANHATTAN_BACKGROUND.darker()); // Hue, saturation, brightness 8

        plot.setDomainMinorGridlinesVisible(true);
        plot.setRangeMinorGridlinesVisible(true);

        // DOTS RENDERER
        XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) plot.getRenderer();
        renderer.setSeriesPaint(0, PLOT_MANHATTAN_DOTS);
        //      renderer.setSeriesOutlinePaint(0, Color.DARK_GRAY);
        //      renderer.setUseOutlinePaint(true);
        // Set dot shape of the currently appended Series
        renderer.setSeriesShape(0, new Rectangle2D.Double(-1, -1, 2, 2));

        renderer.setSeriesVisibleInLegend(0, false);

        // AXIS
        double maxHetzy = 0.005;
        for (int i = 0; i < dataset.getItemCount(0); i++) {
            if (maxHetzy < dataset.getXValue(0, i)) {
                maxHetzy = dataset.getXValue(0, i);
            }
        }
        NumberAxis hetzyAxis = (NumberAxis) plot.getDomainAxis();
        hetzyAxis.setAutoRangeIncludesZero(true);
        hetzyAxis.setAxisLineVisible(true);
        hetzyAxis.setTickLabelsVisible(true);
        hetzyAxis.setTickMarksVisible(true);
        hetzyAxis.setRange(0, maxHetzy * 1.1);

        double maxMissrat = 0.005;
        for (int i = 0; i < dataset.getItemCount(0); i++) {
            if (maxMissrat < dataset.getYValue(0, i)) {
                maxMissrat = dataset.getYValue(0, i);
            }
        }
        NumberAxis missratAxis = (NumberAxis) plot.getRangeAxis();
        missratAxis.setAutoRangeIncludesZero(true);
        missratAxis.setAxisLineVisible(true);
        missratAxis.setTickLabelsVisible(true);
        missratAxis.setTickMarksVisible(true);
        missratAxis.setRange(0, maxMissrat * 1.1);

        // Add significance Threshold to subplot
        final Marker missingThresholdLine = new ValueMarker(missingThreshold);
        missingThresholdLine.setPaint(Color.blue);

        final Marker hetzyThresholdLine = new ValueMarker(hetzyThreshold);
        hetzyThresholdLine.setPaint(Color.blue);

        // Add legend to hetzyThreshold
        hetzyThresholdLine.setLabel("hetzyg. threshold = " + hetzyThreshold);
        missingThresholdLine.setLabel("missing. threshold = " + missingThreshold);
        hetzyThresholdLine.setLabelAnchor(RectangleAnchor.TOP_LEFT);
        hetzyThresholdLine.setLabelTextAnchor(TextAnchor.TOP_RIGHT);
        missingThresholdLine.setLabelAnchor(RectangleAnchor.BOTTOM_LEFT);
        missingThresholdLine.setLabelTextAnchor(TextAnchor.TOP_LEFT);
        plot.addRangeMarker(missingThresholdLine); // THIS FOR MISSING RATIO
        plot.addDomainMarker(hetzyThresholdLine); // THIS FOR HETZY RATIO

        // Marker label if below hetzyThreshold
        XYItemRenderer lblRenderer = plot.getRenderer();

        // THRESHOLD AND SELECTED LABEL GENERATOR
        MySeriesItemLabelGenerator lblGenerator = new MySeriesItemLabelGenerator(hetzyThreshold, missingThreshold);
        lblRenderer.setSeriesItemLabelGenerator(0, lblGenerator);
        lblRenderer.setSeriesItemLabelFont(0, new Font("SansSerif", Font.PLAIN, 10));
        lblRenderer.setSeriesPositiveItemLabelPosition(0, new ItemLabelPosition(ItemLabelAnchor.CENTER,
                TextAnchor.BOTTOM_LEFT, TextAnchor.BOTTOM_LEFT, 2 * Math.PI));

        // TOOLTIP GENERATOR
        MyXYToolTipGenerator tooltipGenerator = new MyXYToolTipGenerator();

        lblRenderer.setBaseToolTipGenerator(tooltipGenerator);

        lblRenderer.setSeriesItemLabelsVisible(0, true);

        return chart;
    }

    private Map<String, SampleKey> getLabelerMap() {
        return labeler;
    }

    public void setLabelerMap(Map<String, SampleKey> labelerMap) {
        this.labeler = labelerMap;
    }

    private class MyXYToolTipGenerator extends StandardXYToolTipGenerator implements XYToolTipGenerator {
        MyXYToolTipGenerator() {
        }

        @Override
        public String generateToolTip(XYDataset dataset, int series, int item) {
            StringBuilder toolTip = new StringBuilder("<html>");
            double hetzygValue = dataset.getXValue(series, item);
            double missingRatValue = dataset.getYValue(series, item);

            if (!Double.isNaN(hetzygValue) && !Double.isNaN(missingRatValue)) {
                StringBuilder localizer = new StringBuilder();
                localizer.append(missingRatValue);
                localizer.append("_");
                localizer.append(hetzygValue);
                for (Map.Entry<String, SampleKey> entry : getLabelerMap().entrySet()) {
                    if (entry.getKey().contains(localizer.toString())) {
                        toolTip.append("Sample ID: ").append(entry.getValue().getSampleId());
                        toolTip.append(" / Family ID: ").append(entry.getValue().getFamilyId());
                        toolTip.append("<br>");
                    }
                }
                //            if(labelerMap.containsKey(localizer)){
                //               toolTip.append(labelerMap.get(localizer));
                //               toolTip.append("<br>");
                //            }

                toolTip.append("Miss. ratio: ").append(Report_Analysis.FORMAT_SCIENTIFIC.format(missingRatValue));
                toolTip.append("<br>Hetzyg. ratio: ").append(Report_Analysis.FORMAT_SCIENTIFIC.format(hetzygValue));

            }
            toolTip.append("</html>");
            return toolTip.toString();
        }
    }

    private class MySeriesItemLabelGenerator extends AbstractXYItemLabelGenerator implements XYItemLabelGenerator {

        private final double hetzygThreshold;
        private final double missingThreshold;

        /**
         * Creates a new generator that only displays labels that are greater
         * than or equal to the hetzyThreshold value.
         *
         * @param hetzyThreshold the hetzyThreshold value.
         */
        MySeriesItemLabelGenerator(double hetzygThreshold, double missingThreshold) {
            this.hetzygThreshold = hetzygThreshold;
            this.missingThreshold = missingThreshold;
        }

        /**
         * Generates a label for the specified item. The label is typically a
         * formatted version of the data value, but any text can be used.
         *
         * @param dataset the dataset (<code>null</code> not permitted).
         * @param series the series index (zero-based).
         * @param category the category index (zero-based).
         *
         * @return the label (possibly <code>null</code>).
         */
        @Override
        public String generateLabel(XYDataset dataset, int series, int item) {
            String rsLabel = null;
            double hetzygValue = dataset.getXValue(series, item);
            double missingRatValue = dataset.getYValue(series, item);
            if (!Double.isNaN(hetzygValue) && !Double.isNaN(missingRatValue)) {
                StringBuilder localizer = new StringBuilder();
                localizer.append(missingRatValue);
                localizer.append("_");
                localizer.append(hetzygValue);
                if (hetzygValue > this.hetzygThreshold || missingRatValue > this.missingThreshold) {
                    for (Map.Entry<String, SampleKey> entry : getLabelerMap().entrySet()) {
                        if (entry.getKey().contains(localizer.toString())) {
                            rsLabel = entry.getValue().getSampleId();
                        }
                    }
                }
            }
            return rsLabel;
        }
    }
    // </editor-fold>

    //<editor-fold defaultstate="expanded" desc="HELPERS">
    /**
     * Validates the threshold and redraws
     */
    private class RedrawAction extends AbstractAction {

        final JTextField txt_hetzy;
        final JTextField txt_missing;

        RedrawAction(final JTextField txt_hetzy, final JTextField txt_missing) {

            this.txt_hetzy = txt_hetzy;
            this.txt_missing = txt_missing;
            putValue(NAME, Text.Reports.redraw);
        }

        @Override
        public void actionPerformed(ActionEvent evt) {

            try {
                hetzyThreshold = Double.parseDouble(txt_hetzy.getText());
                missingThreshold = Double.parseDouble(txt_missing.getText());
                Config.getSingleton().putDouble("CHART_SAMPLEQA_HETZYG_THRESHOLD", hetzyThreshold);
                Config.getSingleton().putDouble("CHART_SAMPLEQA_MISSING_THRESHOLD", missingThreshold);
                GWASpiExplorerPanel.getSingleton().setPnlContent(new SampleQAHetzygPlotZoom(operationKey));
                GWASpiExplorerPanel.getSingleton().getScrlContent()
                        .setViewportView(GWASpiExplorerPanel.getSingleton().getPnlContent());
            } catch (IOException ex) {
                log.warn(Text.App.warnMustBeNumeric, ex);
                Dialogs.showWarningDialogue(Text.App.warnMustBeNumeric);
            }
        }
    }

    private class ResetAction extends AbstractAction {

        ResetAction() {

            putValue(NAME, Text.All.reset);
        }

        @Override
        public void actionPerformed(ActionEvent evt) {

            try {
                GWASpiExplorerPanel.getSingleton().setPnlContent(new SampleQAHetzygPlotZoom(operationKey));
                GWASpiExplorerPanel.getSingleton().getScrlContent()
                        .setViewportView(GWASpiExplorerPanel.getSingleton().getPnlContent());
            } catch (IOException ex) {
                log.error(null, ex);
            }
        }
    }

    static class SaveAsAction extends AbstractAction {

        private final Logger log = LoggerFactory.getLogger(SaveAsAction.class);

        private final String newReportFileName;
        private final Component dialogParent;
        private final JScrollPane scrl_Chart;
        private final JFreeChart zoomChart;

        SaveAsAction(final String newReportFileName, final JScrollPane scrl_Chart, final JFreeChart zoomChart,
                final Component dialogParent) {
            this.newReportFileName = newReportFileName;
            this.scrl_Chart = scrl_Chart;
            this.zoomChart = zoomChart;
            this.dialogParent = dialogParent;
            putValue(NAME, Text.All.save);
        }

        @Override
        public void actionPerformed(ActionEvent evt) {
            try {
                final File newDir = Dialogs.selectDirectoryDialog(Config.PROPERTY_EXPORT_DIR,
                        "Choose the new directory for " + newReportFileName, dialogParent);
                if (newDir == null) {
                    // the user has not choosen a directory to save to
                    return;
                }
                final File newFile = new File(newDir, newReportFileName);
                ChartUtilities.saveChartAsPNG(newFile, zoomChart, scrl_Chart.getWidth(), scrl_Chart.getHeight());
            } catch (final IOException ex) {
                Dialogs.showWarningDialogue("A plot saving error has occurred");
                log.error("A plot saving error has occurred", ex);
            }
        }
    }
    //</editor-fold>
}