<commit_before>package fi.csc.microarray.client.visualisation.methods;

import java.awt.Color;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;

import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.Range;
import org.jfree.data.xy.XYDataItem;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;

import fi.csc.microarray.MicroarrayException;
import fi.csc.microarray.client.visualisation.Visualisation;
import fi.csc.microarray.client.visualisation.VisualisationFrame;
import fi.csc.microarray.client.visualisation.VisualisationMethod;
import fi.csc.microarray.client.visualisation.VisualisationMethodChangedEvent;
import fi.csc.microarray.client.visualisation.VisualisationUtilities;
import fi.csc.microarray.client.visualisation.methods.SelectableChartPanel.SelectionChangeListener;
import fi.csc.microarray.databeans.DataBean;

public class Volcanoplot extends Scatterplot implements ActionListener, PropertyChangeListener, SelectionChangeListener {

	/**
	 * Analytical expression is -log(p), where p is the first available p.* column.
	 */
	private static final String Y_AXIS_COLUMN_HEADER = "p.";
	private static final String X_AXIS_COLUMN_HEADER = "FC";

	private float ROUNDING_LIMIT;

	public Volcanoplot(VisualisationFrame frame) {
		super(frame);
	}

	@Override
	public JPanel createSettingsPanel() {

		JPanel settingsPanel = new JPanel();
		settingsPanel.setLayout(new GridBagLayout());
		settingsPanel.setPreferredSize(Visualisation.PARAMETER_SIZE);

		xBox = new JComboBox();
		yBox = new JComboBox();

		useButton = new JButton("Draw");
		useButton.addActionListener(this);
		GridBagConstraints c = new GridBagConstraints();

		c.gridy = 0;
		c.insets.set(10, 10, 10, 10);
		c.anchor = GridBagConstraints.NORTHWEST;
		c.fill = GridBagConstraints.HORIZONTAL;
		c.weighty = 0;
		c.weightx = 1.0;
		settingsPanel.add(new JLabel("Fold change "), c);
		c.gridy++;
		settingsPanel.add(xBox, c);
		c.gridy++;
		settingsPanel.add(new JLabel("p-value"), c);
		c.gridy++;
		settingsPanel.add(yBox, c);
		c.gridy++;
		settingsPanel.add(useButton, c);
		c.gridy++;
		c.fill = GridBagConstraints.BOTH;
		c.weighty = 1.0;
		settingsPanel.add(new JPanel(), c);

		xBox.addActionListener(this);
		yBox.addActionListener(this);

		return settingsPanel;
	}

	protected void refreshAxisBoxes(DataBean data) {
		if (paramPanel == null) {
			throw new IllegalStateException("must call getParameterPanel first");
		}

		Visualisation.fillCompoBox(xBox, VisualisationUtilities.getVariablesFilteredInclusive(data, X_AXIS_COLUMN_HEADER, false));
		Visualisation.fillCompoBox(yBox, VisualisationUtilities.getVariablesFilteredInclusive(data, Y_AXIS_COLUMN_HEADER, false));
	}

	@Override
	public JComponent getVisualisation(DataBean data) throws Exception {

		this.data = data;

		refreshAxisBoxes(data);

		List<Variable> vars = getFrame().getVariables();

		// If this a redraw from the settings panel, use asked columns
		if (vars != null && vars.size() == 2) {
			xBox.setSelectedItem(vars.get(0));
			yBox.setSelectedItem(vars.get(1));
		}

		xVar = (Variable) xBox.getSelectedItem();
		yVar = (Variable) yBox.getSelectedItem();

		PlotDescription description = new PlotDescription(data.getName(), "fold change", "-log(p)");

		NumberAxis domainAxis = new NumberAxis(description.xTitle);
		NumberAxis rangeAxis = new NumberAxis(description.yTitle);

		XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer();
		renderer.setLinesVisible(false);
		renderer.setShapesVisible(true);
		renderer.setSeriesPaint(0, Color.green);
		renderer.setSeriesPaint(1, Color.red);
		renderer.setSeriesPaint(2, Color.black);
		renderer.setSeriesPaint(3, Color.lightGray);
		renderer.setShape(new Ellipse2D.Float(-2, -2, 4, 4));

		plot = new XYPlot(new XYSeriesCollection(), domainAxis, rangeAxis, renderer);

		this.updateSelectionsFromApplication(false);
		
		// rounding limit is calculated in updateSelectionsFromApplication
		plot.getRangeAxis().setRange(new Range(0, -Math.log(ROUNDING_LIMIT))); 

		JFreeChart chart = new JFreeChart(description.plotTitle, plot);

		chart.removeLegend();

		application.addPropertyChangeListener(this);

		selectableChartPanel = new SelectableChartPanel(chart, this);
		return selectableChartPanel;
	}

	private Iterator<Float> getXValueIterator() throws MicroarrayException {

		return data.queryFeatures(xVar.getExpression()).asFloats().iterator();
	}

	private Iterator<Float> getYValueIterator() throws MicroarrayException {

		return new YValueIterator();
	}

	/**
	 * Class tries to find out the rounding limit of y-values and changes zero values into this limit. Iterated values are also translated
	 * with -log().
	 * 
	 * @author klemela
	 * 
	 */
	private class YValueIterator implements Iterator<Float> {

		private static final float DEFAULT_ROUNDING_LIMIT = 0.0001f;
		// "/column/" part of the query comes from the getExpression function
		Iterator<Float> original;

		public YValueIterator() throws MicroarrayException {

			original = data.queryFeatures(yVar.getExpression()).asFloats().iterator();

			// Find smallest non-zero value to find out rounding limit
			float min = Float.MAX_VALUE;

			while (original.hasNext()) {

				float y = original.next();
				if (y < min && y > 0) {
					min = y;
				}
			}

			// Rounding to the nearest 1*10^-n below
			ROUNDING_LIMIT = (float) Math.pow(10, Math.floor(Math.log10(min)));

			// Sanity check
			if (ROUNDING_LIMIT <= 0 || ROUNDING_LIMIT > 1) {
				ROUNDING_LIMIT = DEFAULT_ROUNDING_LIMIT;
			}

			original = data.queryFeatures(yVar.getExpression()).asFloats().iterator();

		}

		public boolean hasNext() {
			return original.hasNext();
		}

		public Float next() {
			float y = original.next();

			if (y < ROUNDING_LIMIT) {
				y = ROUNDING_LIMIT;
			}
			return (float) -Math.log(y);
		}

		public void remove() {
			original.remove();
		}
	};
<commit_msg>rounding problems hidden behind p-value upper limit<commit_after>package fi.csc.microarray.client.visualisation.methods;

import java.awt.Color;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;

import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.Range;
import org.jfree.data.xy.XYDataItem;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;

import fi.csc.microarray.MicroarrayException;
import fi.csc.microarray.client.visualisation.Visualisation;
import fi.csc.microarray.client.visualisation.VisualisationFrame;
import fi.csc.microarray.client.visualisation.VisualisationMethod;
import fi.csc.microarray.client.visualisation.VisualisationMethodChangedEvent;
import fi.csc.microarray.client.visualisation.VisualisationUtilities;
import fi.csc.microarray.client.visualisation.methods.SelectableChartPanel.SelectionChangeListener;
import fi.csc.microarray.databeans.DataBean;

public class Volcanoplot extends Scatterplot implements ActionListener, PropertyChangeListener, SelectionChangeListener {

	/**
	 * Analytical expression is -log(p), where p is the first available p.* column.
	 */
	private static final String Y_AXIS_COLUMN_HEADER = "p.";
	private static final String X_AXIS_COLUMN_HEADER = "FC";

	private float ROUNDING_LIMIT;

	public Volcanoplot(VisualisationFrame frame) {
		super(frame);
	}

	@Override
	public JPanel createSettingsPanel() {

		JPanel settingsPanel = new JPanel();
		settingsPanel.setLayout(new GridBagLayout());
		settingsPanel.setPreferredSize(Visualisation.PARAMETER_SIZE);

		xBox = new JComboBox();
		yBox = new JComboBox();

		useButton = new JButton("Draw");
		useButton.addActionListener(this);
		GridBagConstraints c = new GridBagConstraints();

		c.gridy = 0;
		c.insets.set(10, 10, 10, 10);
		c.anchor = GridBagConstraints.NORTHWEST;
		c.fill = GridBagConstraints.HORIZONTAL;
		c.weighty = 0;
		c.weightx = 1.0;
		settingsPanel.add(new JLabel("Fold change "), c);
		c.gridy++;
		settingsPanel.add(xBox, c);
		c.gridy++;
		settingsPanel.add(new JLabel("p-value"), c);
		c.gridy++;
		settingsPanel.add(yBox, c);
		c.gridy++;
		settingsPanel.add(useButton, c);
		c.gridy++;
		c.fill = GridBagConstraints.BOTH;
		c.weighty = 1.0;
		settingsPanel.add(new JPanel(), c);

		xBox.addActionListener(this);
		yBox.addActionListener(this);

		return settingsPanel;
	}

	protected void refreshAxisBoxes(DataBean data) {
		if (paramPanel == null) {
			throw new IllegalStateException("must call getParameterPanel first");
		}

		Visualisation.fillCompoBox(xBox, VisualisationUtilities.getVariablesFilteredInclusive(data, X_AXIS_COLUMN_HEADER, false));
		Visualisation.fillCompoBox(yBox, VisualisationUtilities.getVariablesFilteredInclusive(data, Y_AXIS_COLUMN_HEADER, false));
	}

	@Override
	public JComponent getVisualisation(DataBean data) throws Exception {

		this.data = data;

		refreshAxisBoxes(data);

		List<Variable> vars = getFrame().getVariables();

		// If this a redraw from the settings panel, use asked columns
		if (vars != null && vars.size() == 2) {
			xBox.setSelectedItem(vars.get(0));
			yBox.setSelectedItem(vars.get(1));
		}

		xVar = (Variable) xBox.getSelectedItem();
		yVar = (Variable) yBox.getSelectedItem();

		PlotDescription description = new PlotDescription(data.getName(), "fold change", "-log(p)");

		NumberAxis domainAxis = new NumberAxis(description.xTitle);
		NumberAxis rangeAxis = new NumberAxis(description.yTitle);

		XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer();
		renderer.setLinesVisible(false);
		renderer.setShapesVisible(true);
		renderer.setSeriesPaint(0, Color.green);
		renderer.setSeriesPaint(1, Color.red);
		renderer.setSeriesPaint(2, Color.black);
		renderer.setSeriesPaint(3, Color.lightGray);
		renderer.setShape(new Ellipse2D.Float(-2, -2, 4, 4));

		plot = new XYPlot(new XYSeriesCollection(), domainAxis, rangeAxis, renderer);

		this.updateSelectionsFromApplication(false);
		
		// rounding limit is calculated in updateSelectionsFromApplication
		plot.getRangeAxis().setRange(new Range(0, -Math.log(ROUNDING_LIMIT))); 

		JFreeChart chart = new JFreeChart(description.plotTitle, plot);

		chart.removeLegend();

		application.addPropertyChangeListener(this);

		selectableChartPanel = new SelectableChartPanel(chart, this);
		return selectableChartPanel;
	}

	private Iterator<Float> getXValueIterator() throws MicroarrayException {

		return data.queryFeatures(xVar.getExpression()).asFloats().iterator();
	}

	private Iterator<Float> getYValueIterator() throws MicroarrayException {

		return new YValueIterator();
	}

	/**
	 * Class tries to find out the rounding limit of y-values and changes zero values into this limit. Iterated values are also translated
	 * with -log().
	 * 
	 * @author klemela
	 * 
	 */
	private class YValueIterator implements Iterator<Float> {

		private static final float DEFAULT_ROUNDING_LIMIT = 0.0001f;
		// "/column/" part of the query comes from the getExpression function
		Iterator<Float> original;

		public YValueIterator() throws MicroarrayException {

			original = data.queryFeatures(yVar.getExpression()).asFloats().iterator();

			// Find smallest non-zero value to find out rounding limit
			float min = Float.MAX_VALUE;

			while (original.hasNext()) {

				float y = original.next();
				if (y < min && y > 0) {
					min = y;
				}
			}

			// Rounding to the nearest 1*10^-n
			// plus one to hide points going into lines because of rounding
			ROUNDING_LIMIT = (float) Math.pow(10, Math.ceil(Math.log10(min)) + 1);
			
			System.out.println("Min: " + min + ", Limit: " + ROUNDING_LIMIT);

			// Sanity check
			if (ROUNDING_LIMIT <= 0 || ROUNDING_LIMIT > 1) {
				ROUNDING_LIMIT = DEFAULT_ROUNDING_LIMIT;
			}

			original = data.queryFeatures(yVar.getExpression()).asFloats().iterator();

		}

		public boolean hasNext() {
			return original.hasNext();
		}

		public Float next() {
			float y = original.next();

			if (y < ROUNDING_LIMIT) {
				y = ROUNDING_LIMIT;
			}
			return (float) -Math.log(y);
		}

		public void remove() {
			original.remove();
		}
	};

{'fraction_non_alphanumeric': 0.11494691820056518, 'fraction_numerical': 0.006644772015580845, 'mean_word_length': 4.832516703786191, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3525923', 'n_tokens_mistral': 4592, 'n_tokens_neox': 4134, 'n_words': 893}