diff --git a/.github/workflows/build-main.yml b/.github/workflows/build-main.yml index 340ad68a..2fd83d7c 100644 --- a/.github/workflows/build-main.yml +++ b/.github/workflows/build-main.yml @@ -14,7 +14,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Set up Java - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: java-version: '8' distribution: 'zulu' diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml index ac5696fb..e65de26a 100644 --- a/.github/workflows/build-pr.yml +++ b/.github/workflows/build-pr.yml @@ -12,7 +12,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Set up Java - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: java-version: '8' distribution: 'zulu' diff --git a/README.md b/README.md index e770ff8f..5cb9c6aa 100644 --- a/README.md +++ b/README.md @@ -354,6 +354,8 @@ The plugin allows performing various operations based on the correspondence info * Menu Location: `Plugins > Lineage analysis > Show spot and division counts over time` * The command shows a plot of the number of spots and divisions over time. * It is possible to set a window size, which is used to compute a sliding average. +* Colors, visibilities, and sliding average windows size can be set in the plot and are persisted as user preference +* A context menu allows to export the plot as a PNG/SVG file. * A division is defined as a spot with more than one outgoing edge. * Example: ![spot_and_division_counts_over_time.png](doc/lineageanalysis/spot_and_division_counts_over_time.png) diff --git a/doc/lineageanalysis/spot_and_division_counts_over_time.png b/doc/lineageanalysis/spot_and_division_counts_over_time.png index 09ab21c8..20bdebc0 100644 Binary files a/doc/lineageanalysis/spot_and_division_counts_over_time.png and b/doc/lineageanalysis/spot_and_division_counts_over_time.png differ diff --git a/src/main/java/org/mastodon/mamut/tomancak/TomancakPlugins.java b/src/main/java/org/mastodon/mamut/tomancak/TomancakPlugins.java index 5297d8a8..489d0b7a 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/TomancakPlugins.java +++ b/src/main/java/org/mastodon/mamut/tomancak/TomancakPlugins.java @@ -45,7 +45,7 @@ import org.mastodon.mamut.model.Spot; import org.mastodon.mamut.plugin.MamutPlugin; import org.mastodon.mamut.tomancak.compact_lineage.CompactLineageFrame; -import org.mastodon.mamut.tomancak.divisioncount.ShowSpotAndDivisionCountsOverTimeCommand; +import org.mastodon.mamut.tomancak.divisioncount.SpotAndDivisionCountChart; import org.mastodon.mamut.tomancak.divisiontagset.CellDivisionsTagSetCommand; import org.mastodon.mamut.tomancak.export.ExportCounts; import org.mastodon.mamut.tomancak.export.ExportDivisionCountsPerTimepointCommand; @@ -70,6 +70,7 @@ import org.scijava.command.CommandService; import org.scijava.plugin.Parameter; import org.scijava.plugin.Plugin; +import org.scijava.prefs.PrefService; import org.scijava.ui.behaviour.io.gui.CommandDescriptionProvider; import org.scijava.ui.behaviour.io.gui.CommandDescriptions; import org.scijava.ui.behaviour.util.AbstractNamedAction; @@ -225,6 +226,9 @@ public void getCommandDescriptions( final CommandDescriptions descriptions ) @Parameter private CommandService commandService; + @Parameter + private PrefService prefService; + private final AbstractNamedAction exportPhyloXmlAction; private final AbstractNamedAction flipDescendantsAction; @@ -488,7 +492,7 @@ private void exportDivisionCountsPerTimepoint() private void showSpotAndDivisionCountsOverTime() { - commandService.run( ShowSpotAndDivisionCountsOverTimeCommand.class, true, "projectModel", projectModel ); + SpotAndDivisionCountChart.show( projectModel, prefService ); } private void changeBranchLabels() diff --git a/src/main/java/org/mastodon/mamut/tomancak/divisioncount/ShowSpotAndDivisionCountsOverTimeCommand.java b/src/main/java/org/mastodon/mamut/tomancak/divisioncount/ShowSpotAndDivisionCountsOverTimeCommand.java deleted file mode 100644 index b83b2c93..00000000 --- a/src/main/java/org/mastodon/mamut/tomancak/divisioncount/ShowSpotAndDivisionCountsOverTimeCommand.java +++ /dev/null @@ -1,72 +0,0 @@ -/*- - * #%L - * mastodon-tomancak - * %% - * Copyright (C) 2018 - 2025 Tobias Pietzsch - * %% - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * #L% - */ -package org.mastodon.mamut.tomancak.divisioncount; - -import java.util.List; - -import javax.swing.SwingUtilities; - -import org.apache.commons.lang3.tuple.Triple; -import org.mastodon.mamut.ProjectModel; -import org.scijava.ItemVisibility; -import org.scijava.command.Command; -import org.scijava.plugin.Parameter; -import org.scijava.plugin.Plugin; - -@Plugin( type = Command.class, label = "Show spot and division counts over time" ) -public class ShowSpotAndDivisionCountsOverTimeCommand implements Command -{ - - @Parameter( visibility = ItemVisibility.MESSAGE, required = false, persist = false ) - private String documentation = "\n" - + "\n" - + "

Show spot and division counts over time

\n" - + "

This command allows to set a window size and subsequently plot the number of spots and divisions at each timepoint together with a sliding average acknowledging the given window size.

\n" - + "

A division is defined as a spot with more than one outgoing edge.

\n" - + "\n" - + "\n"; - - @Parameter( label = "Sliding Average Window Size:", min = "1", stepSize = "1" ) - private int windowSize = 10; - - @Parameter - private ProjectModel projectModel; - - @Override - public void run() - { - List< Triple< Integer, Integer, Integer > > divisionCounts = - SpotAndDivisionCount.getSpotAndDivisionsPerTimepoint( projectModel.getModel() ); - double[] timepoints = divisionCounts.stream().mapToDouble( Triple::getLeft ).toArray(); - double[] spots = divisionCounts.stream().mapToDouble( Triple::getMiddle ).toArray(); - double[] divisions = divisionCounts.stream().mapToDouble( Triple::getRight ).toArray(); - - SwingUtilities.invokeLater( () -> new SpotAndDivisionCountChart( timepoints, spots, divisions, windowSize ).setVisible( true ) ); - } -} diff --git a/src/main/java/org/mastodon/mamut/tomancak/divisioncount/SpotAndDivisionCountChart.java b/src/main/java/org/mastodon/mamut/tomancak/divisioncount/SpotAndDivisionCountChart.java index d0eaad64..ede59b75 100644 --- a/src/main/java/org/mastodon/mamut/tomancak/divisioncount/SpotAndDivisionCountChart.java +++ b/src/main/java/org/mastodon/mamut/tomancak/divisioncount/SpotAndDivisionCountChart.java @@ -29,44 +29,105 @@ package org.mastodon.mamut.tomancak.divisioncount; import java.awt.BasicStroke; +import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.Shape; import java.awt.geom.Ellipse2D; import java.awt.geom.Rectangle2D; +import java.util.List; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JColorChooser; import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSpinner; +import javax.swing.SpinnerNumberModel; +import javax.swing.WindowConstants; +import net.miginfocom.swing.MigLayout; + +import org.apache.commons.lang3.tuple.Triple; import org.jfree.chart.ChartFactory; import org.jfree.chart.ChartPanel; import org.jfree.chart.JFreeChart; import org.jfree.chart.axis.NumberAxis; import org.jfree.chart.plot.PlotOrientation; 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.XYSeries; import org.jfree.data.xy.XYSeriesCollection; +import org.mastodon.mamut.ProjectModel; +import org.scijava.prefs.PrefService; -class SpotAndDivisionCountChart extends JFrame +public class SpotAndDivisionCountChart extends JFrame { - private final static Color DIVISION_COUNT_COLOR = new Color( 86, 180, 233 ); // Light Blue - private final static Color SPOT_COUNT_COLOR = new Color( 230, 159, 0 ); // Dark Orange + private static final String SPOT_COLOR = "spotColor"; - private final static String TITLE = "Spot and Division Counts over Time"; + private static final String DIVISION_COLOR = "divisionColor"; - private final static String SPOTS_COUNT_SERIES_NAME = "Spot Counts"; + private static final String SPOT_COUNT_VISIBILITY = "spotCountVisibility"; + + private static final String DIVISION_COUNT_VISIBILITY = "divisionCountVisibility"; + + private static final String SPOT_COUNT_AVERAGE_VISIBILITY = "spotCountAverageVisibility"; + + private static final String DIVISION_COUNT_AVERAGE_VISIBILITY = "divisionCountAverageVisibility"; + + private static final String SPOT_COUNT_SLIDING_AVERAGE_WINDOW_SIZE = "spotCountSlidingAverageWindowSize"; + + private static final String DIVISION_COUNT_SLIDING_AVERAGE_WINDOW_SIZE = "divisionCountSlidingAverageWindowSize"; + + private static final int SPOT_COUNT_DEFAULT_COLOR = new Color( 230, 159, 0 ).getRGB(); // Dark Orange + + private static final int DIVISION_COUNT_DEFAULT_COLOR = new Color( 86, 180, 233 ).getRGB(); // Light Blue + private static final int DEFAULT_SLIDING_WINDOW_SIZE = 10; + + private Color spotCountColor; // Dark Orange + + private Color divisionCountColor; // Light Blue + + private final static String TITLE = "Spot and Division Counts over Time"; + private final static String SPOTS_COUNT_SERIES_NAME = "Spot Counts"; private final static String DIVISION_COUNT_SERIES_NAME = "Division Counts"; - SpotAndDivisionCountChart( double[] timepoints, double[] spotCounts, double[] divisionCounts, int windowSize ) + private final XYPlot plot; + + private final NumberAxis leftAxis; + + private final NumberAxis rightAxis; + + private int spotWindowSize; + + private int divisionWindowSize; + + private final PrefService prefs; + + SpotAndDivisionCountChart( final double[] timepoints, final double[] spotCounts, final double[] divisionCounts, + final PrefService prefs ) { + this.prefs = prefs; + + this.spotCountColor = new Color( + prefs.getInt( SpotAndDivisionCountChart.class, SPOT_COLOR, SPOT_COUNT_DEFAULT_COLOR ) ); + this.divisionCountColor = new Color( + prefs.getInt( SpotAndDivisionCountChart.class, DIVISION_COLOR, DIVISION_COUNT_DEFAULT_COLOR ) ); + this.spotWindowSize = + prefs.getInt( SpotAndDivisionCountChart.class, SPOT_COUNT_SLIDING_AVERAGE_WINDOW_SIZE, DEFAULT_SLIDING_WINDOW_SIZE ); + this.divisionWindowSize = + prefs.getInt( SpotAndDivisionCountChart.class, DIVISION_COUNT_SLIDING_AVERAGE_WINDOW_SIZE, DEFAULT_SLIDING_WINDOW_SIZE ); + XYSeriesCollection spotCountsSeries = createSeries( - timepoints, spotCounts, SPOTS_COUNT_SERIES_NAME, windowSize ); + timepoints, spotCounts, SPOTS_COUNT_SERIES_NAME, spotWindowSize ); XYSeriesCollection divisionCountsSeries = createSeries( - timepoints, divisionCounts, DIVISION_COUNT_SERIES_NAME, windowSize ); + timepoints, divisionCounts, DIVISION_COUNT_SERIES_NAME, divisionWindowSize ); JFreeChart chart = ChartFactory.createXYLineChart( TITLE, @@ -79,49 +140,218 @@ class SpotAndDivisionCountChart extends JFrame false ); - XYPlot plot = chart.getXYPlot(); + plot = chart.getXYPlot(); // Customize the left y-axis (Spot Counts) - NumberAxis leftAxis = ( NumberAxis ) plot.getRangeAxis(); - customizeAxis( leftAxis, SPOTS_COUNT_SERIES_NAME, SPOT_COUNT_COLOR ); + leftAxis = ( NumberAxis ) plot.getRangeAxis(); + customizeAxis( leftAxis, SPOTS_COUNT_SERIES_NAME, spotCountColor ); plot.setRangeAxis( 0, leftAxis ); plot.setDataset( 0, spotCountsSeries ); plot.mapDatasetToRangeAxis( 0, 0 ); // Add the right y-axis (Division Counts) - NumberAxis rightAxis = new NumberAxis(); - customizeAxis( rightAxis, DIVISION_COUNT_SERIES_NAME, DIVISION_COUNT_COLOR ); + rightAxis = new NumberAxis(); + customizeAxis( rightAxis, DIVISION_COUNT_SERIES_NAME, divisionCountColor ); plot.setRangeAxis( 1, rightAxis ); plot.setDataset( 1, divisionCountsSeries ); plot.mapDatasetToRangeAxis( 1, 1 ); XYLineAndShapeRenderer spotCountRenderer = createRenderer( - SPOT_COUNT_COLOR, new Rectangle2D.Double( -2, -2, 4, 4 ) ); + spotCountColor, new Rectangle2D.Double( -2, -2, 4, 4 ) ); plot.setRenderer( 0, spotCountRenderer ); XYLineAndShapeRenderer divisionCountRenderer = createRenderer( - DIVISION_COUNT_COLOR, new Ellipse2D.Double( -2, -2, 4, 4 ) ); + divisionCountColor, new Ellipse2D.Double( -2, -2, 4, 4 ) ); plot.setRenderer( 1, divisionCountRenderer ); // Set up the chart panel ChartPanel chartPanel = new ChartPanel( chart ); chartPanel.setPreferredSize( new Dimension( 800, 600 ) ); - setContentPane( chartPanel ); - setChartColors( plot ); + + // Add color chooser and visibility controls + JPanel controlPanel = createControlPanel( timepoints, spotCounts, divisionCounts ); + + // Set up the frame layout + setLayout( new BorderLayout() ); + add( chartPanel, BorderLayout.CENTER ); + add( controlPanel, BorderLayout.SOUTH ); // Set up the frame setTitle( TITLE ); - setSize( 800, 600 ); + setSize( 800, 700 ); setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE ); setLocationRelativeTo( null ); + + repaint(); + } + + public static void show( final ProjectModel projectModel, final PrefService prefService ) + { + List< Triple< Integer, Integer, Integer > > divisionCounts = + SpotAndDivisionCount.getSpotAndDivisionsPerTimepoint( projectModel.getModel() ); + double[] timepoints = divisionCounts.stream().mapToDouble( Triple::getLeft ).toArray(); + double[] spots = divisionCounts.stream().mapToDouble( Triple::getMiddle ).toArray(); + double[] divisions = divisionCounts.stream().mapToDouble( Triple::getRight ).toArray(); + + SpotAndDivisionCountChart chart = new SpotAndDivisionCountChart( timepoints, spots, divisions, prefService ); + chart.setDefaultCloseOperation( WindowConstants.DISPOSE_ON_CLOSE ); + chart.setVisible( true ); } /** - * Creates a reusable renderer for datasets. - * - * @param color The color for the raw data points and the line. - * @param shape The shape for the raw data points. - * @return A configured XYLineAndShapeRenderer. + * Creates a control panel with color choosers and checkboxes for visibility controls. + */ + private JPanel createControlPanel( final double[] timepoints, final double[] spotCounts, final double[] divisionCounts ) + { + JPanel controlPanel = new JPanel( new MigLayout( "fill, wrap 5", "[grow]", "[]10[]10[]10[]10[]" ) ); + + // Spot-related controls + JButton spotColorButton = new JButton( "Choose Spot Color" ); + spotColorButton.addActionListener( e -> { + Color newColor = JColorChooser.showDialog( null, "Choose Spot Color", spotCountColor ); + if ( newColor != null ) + { + spotCountColor = newColor; + prefs.put( SpotAndDivisionCountChart.class, SPOT_COLOR, spotCountColor.getRGB() ); + updateChartColors(); + } + } ); + + boolean spotCountVisibility = prefs.getBoolean( SpotAndDivisionCountChart.class, SPOT_COUNT_VISIBILITY, true ); + boolean spotCountAverageVisibility = prefs.getBoolean( SpotAndDivisionCountChart.class, SPOT_COUNT_AVERAGE_VISIBILITY, true ); + JCheckBox showSpotCounts = new JCheckBox( "Show Spot Counts", spotCountVisibility ); + JCheckBox showSpotAverage = new JCheckBox( "Show Spot Sliding Average", spotCountAverageVisibility ); + + showSpotCounts.addActionListener( e -> { + plot.getRenderer( 0 ).setSeriesVisible( 0, showSpotCounts.isSelected() ); + prefs.put( SpotAndDivisionCountChart.class, SPOT_COUNT_VISIBILITY, showSpotCounts.isSelected() ); + updateAxisVisibility(); + } ); + + showSpotAverage.addActionListener( e -> { + plot.getRenderer( 0 ).setSeriesVisible( 1, showSpotAverage.isSelected() ); + prefs.put( SpotAndDivisionCountChart.class, SPOT_COUNT_AVERAGE_VISIBILITY, showSpotAverage.isSelected() ); + updateAxisVisibility(); + } ); + + JSpinner spotWindowSpinner = new JSpinner( new SpinnerNumberModel( spotWindowSize, 1, Integer.MAX_VALUE, 1 ) ); + spotWindowSpinner.addChangeListener( e -> { + spotWindowSize = ( int ) spotWindowSpinner.getValue(); + prefs.put( SpotAndDivisionCountChart.class, SPOT_COUNT_SLIDING_AVERAGE_WINDOW_SIZE, spotWindowSize ); + updateSlidingAverage( timepoints, spotCounts, divisionCounts ); + } ); + + // Division-related controls + JButton divisionColorButton = new JButton( "Choose Division Color" ); + divisionColorButton.addActionListener( e -> { + Color newColor = JColorChooser.showDialog( null, "Choose Division Color", divisionCountColor ); + if ( newColor != null ) + { + divisionCountColor = newColor; + prefs.put( SpotAndDivisionCountChart.class, DIVISION_COLOR, divisionCountColor.getRGB() ); + updateChartColors(); + } + } ); + + boolean divisionCountVisibility = prefs.getBoolean( SpotAndDivisionCountChart.class, DIVISION_COUNT_VISIBILITY, true ); + boolean divisionAverageVisibility = prefs.getBoolean( SpotAndDivisionCountChart.class, DIVISION_COUNT_AVERAGE_VISIBILITY, true ); + JCheckBox showDivisionCounts = new JCheckBox( "Show Division Counts", divisionCountVisibility ); + JCheckBox showDivisionAverage = new JCheckBox( "Show Division Sliding Average", divisionAverageVisibility ); + + showDivisionCounts.addActionListener( e -> { + plot.getRenderer( 1 ).setSeriesVisible( 0, showDivisionCounts.isSelected() ); + prefs.put( SpotAndDivisionCountChart.class, DIVISION_COUNT_VISIBILITY, showDivisionCounts.isSelected() ); + updateAxisVisibility(); + } ); + + showDivisionAverage.addActionListener( e -> { + plot.getRenderer( 1 ).setSeriesVisible( 1, showDivisionAverage.isSelected() ); + prefs.put( SpotAndDivisionCountChart.class, DIVISION_COUNT_AVERAGE_VISIBILITY, showDivisionAverage.isSelected() ); + updateAxisVisibility(); + } ); + + JSpinner divisionWindowSpinner = new JSpinner( new SpinnerNumberModel( divisionWindowSize, 1, Integer.MAX_VALUE, 1 ) ); + divisionWindowSpinner.addChangeListener( e -> { + divisionWindowSize = ( int ) divisionWindowSpinner.getValue(); + prefs.put( SpotAndDivisionCountChart.class, DIVISION_COUNT_SLIDING_AVERAGE_WINDOW_SIZE, divisionWindowSize ); + updateSlidingAverage( timepoints, spotCounts, divisionCounts ); + } ); + + // Add components to the control panel + controlPanel.add( spotColorButton, "growx" ); + controlPanel.add( showSpotCounts, "growx" ); + controlPanel.add( showSpotAverage, "growx" ); + controlPanel.add( new JLabel( "Window Size:" ), "align right" ); + controlPanel.add( spotWindowSpinner, "wmax 50" ); + + controlPanel.add( divisionColorButton, "growx" ); + controlPanel.add( showDivisionCounts, "growx" ); + controlPanel.add( showDivisionAverage, "growx" ); + controlPanel.add( new JLabel( "Window Size:" ), "align right" ); + controlPanel.add( divisionWindowSpinner, "wmax 50" ); + + // Add description + controlPanel.add( new JLabel( + "This windows shows the number of spots and divisions at each timepoint together with a sliding average.
A division is defined as a spot with more than one outgoing edge." ), + "span 5" ); + + plot.getRenderer( 0 ).setSeriesVisible( 0, spotCountVisibility ); + plot.getRenderer( 0 ).setSeriesVisible( 1, spotCountAverageVisibility ); + plot.getRenderer( 1 ).setSeriesVisible( 0, divisionCountVisibility ); + plot.getRenderer( 1 ).setSeriesVisible( 1, divisionAverageVisibility ); + + updateAxisVisibility(); + + return controlPanel; + } + + /** + * Updates the chart colors based on the user's selection. + */ + private void updateChartColors() + { + // Update the renderer colors + XYLineAndShapeRenderer spotCountRenderer = createRenderer( + spotCountColor, new Rectangle2D.Double( -2, -2, 4, 4 ) ); + plot.setRenderer( 0, spotCountRenderer ); + + XYLineAndShapeRenderer divisionCountRenderer = createRenderer( + divisionCountColor, new Ellipse2D.Double( -2, -2, 4, 4 ) ); + plot.setRenderer( 1, divisionCountRenderer ); + + // Update axis label colors + customizeAxis( leftAxis, SPOTS_COUNT_SERIES_NAME, spotCountColor ); + customizeAxis( rightAxis, DIVISION_COUNT_SERIES_NAME, divisionCountColor ); + + // Repaint the chart + repaint(); + } + + /** + * Updates the visibility of the axis labels based on the state of the checkboxes. */ + private void updateAxisVisibility() + { + XYItemRenderer spotCountRenderer = plot.getRenderer( 0 ); + Boolean spotCountVisible = spotCountRenderer.getSeriesVisible( 0 ); + Boolean spotAverageVisible = spotCountRenderer.getSeriesVisible( 1 ); + boolean isSpotCountVisible = spotCountVisible != null && spotCountVisible; + boolean isSpotAverageVisible = spotAverageVisible != null && spotAverageVisible; + boolean showSpotAxis = isSpotCountVisible || isSpotAverageVisible; + leftAxis.setLabel( showSpotAxis ? SPOTS_COUNT_SERIES_NAME : null ); + leftAxis.setVisible( showSpotAxis ); + + XYItemRenderer divisionCountRenderer = plot.getRenderer( 1 ); + Boolean divisionCountVisible = divisionCountRenderer.getSeriesVisible( 0 ); + Boolean divisionAverageVisible = divisionCountRenderer.getSeriesVisible( 1 ); + boolean isDivisionCountVisible = divisionCountVisible != null && divisionCountVisible; + boolean isDivisionAverageVisible = divisionAverageVisible != null && divisionAverageVisible; + boolean showDivisionAxis = isDivisionCountVisible || isDivisionAverageVisible; + rightAxis.setLabel( showDivisionAxis ? DIVISION_COUNT_SERIES_NAME : null ); + rightAxis.setVisible( showDivisionAxis ); + + repaint(); + } + private XYLineAndShapeRenderer createRenderer( final Color color, final Shape shape ) { XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer(); @@ -176,11 +406,18 @@ private static XYSeriesCollection createSeries( final double[] xValues, final do return dataset; } - private static void setChartColors( final XYPlot plot ) + /** + * Updates the sliding average series based on the new window sizes. + */ + private void updateSlidingAverage( double[] timepoints, double[] spotCounts, double[] divisionCounts ) { - plot.setBackgroundPaint( Color.WHITE ); - plot.setDomainGridlinePaint( Color.BLACK ); - plot.setRangeGridlinePaint( Color.BLACK ); + XYSeriesCollection spotCountsSeries = createSeries( + timepoints, spotCounts, SPOTS_COUNT_SERIES_NAME, spotWindowSize ); + XYSeriesCollection divisionCountsSeries = createSeries( + timepoints, divisionCounts, DIVISION_COUNT_SERIES_NAME, divisionWindowSize ); + + plot.setDataset( 0, spotCountsSeries ); + plot.setDataset( 1, divisionCountsSeries ); } private static double[] calculateSlidingAverage( double[] values, int windowSize )