2929package org .mastodon .mamut .tomancak .divisioncount ;
3030
3131import java .awt .BasicStroke ;
32+ import java .awt .BorderLayout ;
3233import java .awt .Color ;
3334import java .awt .Dimension ;
3435import java .awt .Font ;
3536import java .awt .Shape ;
3637import java .awt .geom .Ellipse2D ;
3738import java .awt .geom .Rectangle2D ;
3839
40+ import javax .swing .JButton ;
41+ import javax .swing .JCheckBox ;
42+ import javax .swing .JColorChooser ;
3943import javax .swing .JFrame ;
44+ import javax .swing .JLabel ;
45+ import javax .swing .JPanel ;
46+ import javax .swing .JSpinner ;
47+ import javax .swing .SpinnerNumberModel ;
4048
49+ import net .miginfocom .swing .MigLayout ;
4150import org .jfree .chart .ChartFactory ;
4251import org .jfree .chart .ChartPanel ;
4352import org .jfree .chart .JFreeChart ;
4453import org .jfree .chart .axis .NumberAxis ;
4554import org .jfree .chart .plot .PlotOrientation ;
4655import org .jfree .chart .plot .XYPlot ;
56+ import org .jfree .chart .renderer .xy .XYItemRenderer ;
4757import org .jfree .chart .renderer .xy .XYLineAndShapeRenderer ;
4858import org .jfree .data .xy .XYSeries ;
4959import org .jfree .data .xy .XYSeriesCollection ;
5060
5161class SpotAndDivisionCountChart extends JFrame
5262{
53- private final static Color DIVISION_COUNT_COLOR = new Color ( 86 , 180 , 233 ); // Light Blue
63+ private Color divisionCountColor = new Color ( 86 , 180 , 233 ); // Light Blue
5464
55- private final static Color SPOT_COUNT_COLOR = new Color ( 230 , 159 , 0 ); // Dark Orange
65+ private Color spotCountColor = new Color ( 230 , 159 , 0 ); // Dark Orange
5666
5767 private final static String TITLE = "Spot and Division Counts over Time" ;
58-
5968 private final static String SPOTS_COUNT_SERIES_NAME = "Spot Counts" ;
60-
6169 private final static String DIVISION_COUNT_SERIES_NAME = "Division Counts" ;
6270
71+ private final XYPlot plot ;
72+
73+ private final NumberAxis leftAxis ;
74+
75+ private final NumberAxis rightAxis ;
76+
77+ private int spotWindowSize ;
78+
79+ private int divisionWindowSize ;
80+
6381 SpotAndDivisionCountChart ( double [] timepoints , double [] spotCounts , double [] divisionCounts , int windowSize )
6482 {
83+ this .spotWindowSize = windowSize ;
84+ this .divisionWindowSize = windowSize ;
85+
6586 XYSeriesCollection spotCountsSeries = createSeries (
66- timepoints , spotCounts , SPOTS_COUNT_SERIES_NAME , windowSize );
87+ timepoints , spotCounts , SPOTS_COUNT_SERIES_NAME , spotWindowSize );
6788
6889 XYSeriesCollection divisionCountsSeries = createSeries (
69- timepoints , divisionCounts , DIVISION_COUNT_SERIES_NAME , windowSize );
90+ timepoints , divisionCounts , DIVISION_COUNT_SERIES_NAME , divisionWindowSize );
7091
7192 JFreeChart chart = ChartFactory .createXYLineChart (
7293 TITLE ,
@@ -79,49 +100,176 @@ class SpotAndDivisionCountChart extends JFrame
79100 false
80101 );
81102
82- XYPlot plot = chart .getXYPlot ();
103+ plot = chart .getXYPlot ();
83104
84105 // Customize the left y-axis (Spot Counts)
85- NumberAxis leftAxis = ( NumberAxis ) plot .getRangeAxis ();
86- customizeAxis ( leftAxis , SPOTS_COUNT_SERIES_NAME , SPOT_COUNT_COLOR );
106+ leftAxis = ( NumberAxis ) plot .getRangeAxis ();
107+ customizeAxis ( leftAxis , SPOTS_COUNT_SERIES_NAME , spotCountColor );
87108 plot .setRangeAxis ( 0 , leftAxis );
88109 plot .setDataset ( 0 , spotCountsSeries );
89110 plot .mapDatasetToRangeAxis ( 0 , 0 );
90111
91112 // Add the right y-axis (Division Counts)
92- NumberAxis rightAxis = new NumberAxis ();
93- customizeAxis ( rightAxis , DIVISION_COUNT_SERIES_NAME , DIVISION_COUNT_COLOR );
113+ rightAxis = new NumberAxis ();
114+ customizeAxis ( rightAxis , DIVISION_COUNT_SERIES_NAME , divisionCountColor );
94115 plot .setRangeAxis ( 1 , rightAxis );
95116 plot .setDataset ( 1 , divisionCountsSeries );
96117 plot .mapDatasetToRangeAxis ( 1 , 1 );
97118
98119 XYLineAndShapeRenderer spotCountRenderer = createRenderer (
99- SPOT_COUNT_COLOR , new Rectangle2D .Double ( -2 , -2 , 4 , 4 ) );
120+ spotCountColor , new Rectangle2D .Double ( -2 , -2 , 4 , 4 ) );
100121 plot .setRenderer ( 0 , spotCountRenderer );
101122 XYLineAndShapeRenderer divisionCountRenderer = createRenderer (
102- DIVISION_COUNT_COLOR , new Ellipse2D .Double ( -2 , -2 , 4 , 4 ) );
123+ divisionCountColor , new Ellipse2D .Double ( -2 , -2 , 4 , 4 ) );
103124 plot .setRenderer ( 1 , divisionCountRenderer );
104125
105126 // Set up the chart panel
106127 ChartPanel chartPanel = new ChartPanel ( chart );
107128 chartPanel .setPreferredSize ( new Dimension ( 800 , 600 ) );
108- setContentPane ( chartPanel );
109- setChartColors ( plot );
129+
130+ // Add color chooser and visibility controls
131+ JPanel controlPanel = createControlPanel ( timepoints , spotCounts , divisionCounts );
132+
133+ // Set up the frame layout
134+ setLayout ( new BorderLayout () );
135+ add ( chartPanel , BorderLayout .CENTER );
136+ add ( controlPanel , BorderLayout .SOUTH );
110137
111138 // Set up the frame
112139 setTitle ( TITLE );
113- setSize ( 800 , 600 );
140+ setSize ( 800 , 700 );
114141 setDefaultCloseOperation ( JFrame .DISPOSE_ON_CLOSE );
115142 setLocationRelativeTo ( null );
116143 }
117144
118145 /**
119- * Creates a reusable renderer for datasets.
120- *
121- * @param color The color for the raw data points and the line.
122- * @param shape The shape for the raw data points.
123- * @return A configured XYLineAndShapeRenderer.
146+ * Creates a control panel with color choosers and checkboxes for visibility controls.
124147 */
148+ private JPanel createControlPanel ( final double [] timepoints , final double [] spotCounts , final double [] divisionCounts )
149+ {
150+ JPanel controlPanel = new JPanel ( new MigLayout ( "wrap 5" ) );
151+
152+ // Spot-related controls
153+ JButton spotColorButton = new JButton ( "Choose Spot Color" );
154+ spotColorButton .addActionListener ( e -> {
155+ Color newColor = JColorChooser .showDialog ( null , "Choose Spot Color" , spotCountColor );
156+ if ( newColor != null )
157+ {
158+ spotCountColor = newColor ;
159+ updateChartColors ();
160+ }
161+ } );
162+
163+ JCheckBox showSpotCounts = new JCheckBox ( "Show Spot Counts" , true );
164+ JCheckBox showSpotAverage = new JCheckBox ( "Show Spot Sliding Average" , true );
165+
166+ showSpotCounts .addActionListener ( e -> {
167+ plot .getRenderer ( 0 ).setSeriesVisible ( 0 , showSpotCounts .isSelected () );
168+ updateAxisVisibility ();
169+ } );
170+
171+ showSpotAverage .addActionListener ( e -> {
172+ plot .getRenderer ( 0 ).setSeriesVisible ( 1 , showSpotAverage .isSelected () );
173+ updateAxisVisibility ();
174+ } );
175+
176+ JSpinner spotWindowSpinner = new JSpinner ( new SpinnerNumberModel ( spotWindowSize , 1 , Integer .MAX_VALUE , 1 ) );
177+ spotWindowSpinner .addChangeListener ( e -> {
178+ spotWindowSize = ( int ) spotWindowSpinner .getValue ();
179+ updateSlidingAverage ( timepoints , spotCounts , divisionCounts );
180+ } );
181+
182+ // Division-related controls
183+ JButton divisionColorButton = new JButton ( "Choose Division Color" );
184+ divisionColorButton .addActionListener ( e -> {
185+ Color newColor = JColorChooser .showDialog ( null , "Choose Division Color" , divisionCountColor );
186+ if ( newColor != null )
187+ {
188+ divisionCountColor = newColor ;
189+ updateChartColors ();
190+ }
191+ } );
192+
193+ JCheckBox showDivisionCounts = new JCheckBox ( "Show Division Counts" , true );
194+ JCheckBox showDivisionAverage = new JCheckBox ( "Show Division Sliding Average" , true );
195+
196+ showDivisionCounts .addActionListener ( e -> {
197+ plot .getRenderer ( 1 ).setSeriesVisible ( 0 , showDivisionCounts .isSelected () );
198+ updateAxisVisibility ();
199+ } );
200+
201+ showDivisionAverage .addActionListener ( e -> {
202+ plot .getRenderer ( 1 ).setSeriesVisible ( 1 , showDivisionAverage .isSelected () );
203+ updateAxisVisibility ();
204+ } );
205+
206+ JSpinner divisionWindowSpinner = new JSpinner ( new SpinnerNumberModel ( divisionWindowSize , 1 , Integer .MAX_VALUE , 1 ) );
207+ divisionWindowSpinner .addChangeListener ( e -> {
208+ divisionWindowSize = ( int ) divisionWindowSpinner .getValue ();
209+ updateSlidingAverage ( timepoints , spotCounts , divisionCounts );
210+ } );
211+
212+ // Add components to the control panel
213+ controlPanel .add ( spotColorButton , "growx" );
214+ controlPanel .add ( showSpotCounts , "growx" );
215+ controlPanel .add ( showSpotAverage , "growx" );
216+ controlPanel .add ( new JLabel ( "Window Size:" ), "align right" );
217+ controlPanel .add ( spotWindowSpinner , "growx" );
218+
219+ controlPanel .add ( divisionColorButton , "growx" );
220+ controlPanel .add ( showDivisionCounts , "growx" );
221+ controlPanel .add ( showDivisionAverage , "growx" );
222+ controlPanel .add ( new JLabel ( "Window Size:" ), "align right" );
223+ controlPanel .add ( divisionWindowSpinner , "growx" );
224+
225+ return controlPanel ;
226+ }
227+
228+ /**
229+ * Updates the chart colors based on the user's selection.
230+ */
231+ private void updateChartColors ()
232+ {
233+ // Update the renderer colors
234+ XYLineAndShapeRenderer spotCountRenderer = createRenderer (
235+ spotCountColor , new Rectangle2D .Double ( -2 , -2 , 4 , 4 ) );
236+ plot .setRenderer ( 0 , spotCountRenderer );
237+
238+ XYLineAndShapeRenderer divisionCountRenderer = createRenderer (
239+ divisionCountColor , new Ellipse2D .Double ( -2 , -2 , 4 , 4 ) );
240+ plot .setRenderer ( 1 , divisionCountRenderer );
241+
242+ // Update axis label colors
243+ customizeAxis ( leftAxis , SPOTS_COUNT_SERIES_NAME , spotCountColor );
244+ customizeAxis ( rightAxis , DIVISION_COUNT_SERIES_NAME , divisionCountColor );
245+
246+ // Repaint the chart
247+ repaint ();
248+ }
249+
250+ /**
251+ * Updates the visibility of the axis labels based on the state of the checkboxes.
252+ */
253+ private void updateAxisVisibility ()
254+ {
255+ XYItemRenderer spotCountRenderer = plot .getRenderer ( 0 );
256+ Boolean spotCountVisible = spotCountRenderer .getSeriesVisible ( 0 );
257+ Boolean spotAverageVisible = spotCountRenderer .getSeriesVisible ( 1 );
258+ boolean showSpotAxis = spotCountVisible != null && spotCountVisible || spotAverageVisible != null && spotAverageVisible ;
259+ leftAxis .setLabel ( showSpotAxis ? SPOTS_COUNT_SERIES_NAME : null );
260+ leftAxis .setVisible ( showSpotAxis );
261+
262+ XYItemRenderer divisionCountRenderer = plot .getRenderer ( 1 );
263+ Boolean divisionCountVisible = divisionCountRenderer .getSeriesVisible ( 0 );
264+ Boolean divisionAverageVisible = divisionCountRenderer .getSeriesVisible ( 1 );
265+ boolean showDivisionAxis =
266+ divisionCountVisible != null && divisionCountVisible || divisionAverageVisible != null && divisionAverageVisible ;
267+ rightAxis .setLabel ( showDivisionAxis ? DIVISION_COUNT_SERIES_NAME : null );
268+ rightAxis .setVisible ( showDivisionAxis );
269+
270+ repaint ();
271+ }
272+
125273 private XYLineAndShapeRenderer createRenderer ( final Color color , final Shape shape )
126274 {
127275 XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer ();
@@ -176,11 +324,18 @@ private static XYSeriesCollection createSeries( final double[] xValues, final do
176324 return dataset ;
177325 }
178326
179- private static void setChartColors ( final XYPlot plot )
327+ /**
328+ * Updates the sliding average series based on the new window sizes.
329+ */
330+ private void updateSlidingAverage ( double [] timepoints , double [] spotCounts , double [] divisionCounts )
180331 {
181- plot .setBackgroundPaint ( Color .WHITE );
182- plot .setDomainGridlinePaint ( Color .BLACK );
183- plot .setRangeGridlinePaint ( Color .BLACK );
332+ XYSeriesCollection spotCountsSeries = createSeries (
333+ timepoints , spotCounts , SPOTS_COUNT_SERIES_NAME , spotWindowSize );
334+ XYSeriesCollection divisionCountsSeries = createSeries (
335+ timepoints , divisionCounts , DIVISION_COUNT_SERIES_NAME , divisionWindowSize );
336+
337+ plot .setDataset ( 0 , spotCountsSeries );
338+ plot .setDataset ( 1 , divisionCountsSeries );
184339 }
185340
186341 private static double [] calculateSlidingAverage ( double [] values , int windowSize )
0 commit comments