88 * it under the terms of the GNU General Public License as
99 * published by the Free Software Foundation, either version 3 of the
1010 * License, or (at your option) any later version.
11- *
11+ *
1212 * This program is distributed in the hope that it will be useful,
1313 * but WITHOUT ANY WARRANTY; without even the implied warranty of
1414 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1515 * GNU General Public License for more details.
16- *
16+ *
1717 * You should have received a copy of the GNU General Public
1818 * License along with this program. If not, see
1919 * <http://www.gnu.org/licenses/gpl-3.0.html>.
2323
2424import java .util .ArrayList ;
2525import java .util .List ;
26+
2627import javafx .application .Platform ;
2728import javafx .beans .property .ObjectProperty ;
2829import javafx .beans .property .SimpleObjectProperty ;
3839import javafx .scene .chart .XYChart .Data ;
3940import javafx .scene .chart .XYChart .Series ;
4041import javafx .scene .control .TextField ;
42+ import javafx .scene .control .ToggleButton ;
4143import javafx .scene .image .ImageView ;
4244import javafx .scene .image .WritableImage ;
45+ import javafx .scene .input .MouseEvent ;
4346import javafx .scene .layout .AnchorPane ;
47+ import javafx .scene .shape .Circle ;
48+ import javafx .scene .shape .Line ;
49+
4450import org .controlsfx .control .HiddenSidesPane ;
4551import org .controlsfx .control .SegmentedButton ;
52+
53+ import net .imglib2 .type .numeric .real .FloatType ;
54+
4655import flimlib .flimj .FitParams ;
4756import flimlib .flimj .FitResults ;
4857import flimlib .flimj .ui .Utils ;
4958import flimlib .flimj .ui .VariableScaleAxis ;
5059import flimlib .flimj .ui .controls .NumericSpinner ;
51- import net .imglib2 .type .numeric .real .FloatType ;
5260
5361/**
5462 * The controller of the "Plot" tab.
@@ -59,15 +67,21 @@ public class PlotCtrl extends AbstractCtrl {
5967 private static final int FIT_IDX = 1 ;
6068 private static final int RES_IDX = 2 ;
6169 private static final int IRF_IDX = 3 ;
62-
6370 private static final int BEG_IDX = 0 ;
6471 private static final int END_IDX = 1 ;
65-
6672 private static final int N_PLOTS = 4 ;
6773
6874 /** cursors */
6975 @ FXML
70- private Group lCsr , rCsr ;
76+ private Group lCsr , rCsr , lCsr_res , rCsr_res ;
77+
78+ /** cursor circle elements*/
79+ @ FXML
80+ private Circle lCsrCircle , rCsrCircle ;
81+
82+ /** cursor line elements*/
83+ @ FXML
84+ private Line lCsrBar , rCsrBar , lCsrBar_res , rCsrBar_res ;
7185
7286 /** cursor position spinners */
7387 @ FXML
@@ -96,6 +110,9 @@ public class PlotCtrl extends AbstractCtrl {
96110 @ FXML
97111 private ImageView frostImageView ;
98112
113+ @ FXML
114+ private ToggleButton linTB , logTB ;
115+
99116 /** cursor positions */
100117 private ObjectProperty <Double > lCsrPos , rCsrPos ;
101118
@@ -128,22 +145,41 @@ public class PlotCtrl extends AbstractCtrl {
128145 @ SuppressWarnings ("unchecked" )
129146 public void initialize () {
130147 // initialize properties with invalid values (corrected by refresh())
131- lCsrPos = new SimpleObjectProperty <>();
132- lCsrPos .set (-1.0 );
133- rCsrPos = new SimpleObjectProperty <>();
134- rCsrPos .set (-1.0 );
135- fitStart = new SimpleObjectProperty <>();
136- fitStart .set (-1 );
137- fitEnd = new SimpleObjectProperty <>();
138- fitEnd .set (-1 );
148+ lCsrPos = new SimpleObjectProperty <>(-1.0 );
149+ rCsrPos = new SimpleObjectProperty <>(-1.0 );
150+ fitStart = new SimpleObjectProperty <>(-1 );
151+ fitEnd = new SimpleObjectProperty <>(-1 );
139152 lCsrSpinner .setMin (0.0 );
140153 rCsrSpinner .setMin (0.0 );
141154 lCsrSpinner .setMax (0.0 );
142155 rCsrSpinner .setMax (0.0 );
143156
157+ // set cursor initial positions
158+ lCsr .setTranslateX (0 );
159+ rCsr .setTranslateX (fitPlotAreaPane .getWidth ());
160+
161+ // bind the X pos of the residual cursors so they move together
162+ lCsr_res .translateXProperty ().bind (lCsr .translateXProperty ());
163+ rCsr_res .translateXProperty ().bind (rCsr .translateXProperty ());
164+
165+ // fit the height of the bar with the plot height size
166+ lCsrBar .endYProperty ().bind (fitPlotAreaPane .heightProperty ().subtract (1 ));
167+ rCsrBar .endYProperty ().bind (fitPlotAreaPane .heightProperty ().subtract (1 ));
168+ lCsrBar_res .endYProperty ().bind (fitPlotAreaPane .heightProperty ().subtract (1 ));
169+ lCsrBar_res .endYProperty ().bind (fitPlotAreaPane .heightProperty ().subtract (1 ));
170+
171+ // link the two toggle buttons to the segmented button
172+ fitYScaleSB .getButtons ().addAll (linTB , logTB );
173+ linTB .setSelected (true );
174+
175+ // initialize cursor listeners
144176 initListeners (rCsr , rCsrPos , rCsrSpinner , fitEnd );
145177 initListeners (lCsr , lCsrPos , lCsrSpinner , fitStart );
146178
179+ // initialize cursor mouse event handlers
180+ initCursorEventHandlers (lCsr );
181+ initCursorEventHandlers (rCsr );
182+
147183 fitPlotAreaPane .widthProperty ().addListener ((obs , oldVal , newVal ) -> {
148184 // == 0 at init
149185 if (oldVal .floatValue () != 0 ) {
@@ -220,6 +256,17 @@ else if (oldVal != null)
220256 });
221257 }
222258
259+ /**
260+ * Clamps a value between min and max.
261+ * @param value The value to clamp.
262+ * @param min The minimum allowed value.
263+ * @param max The maximum allowed value.
264+ * @return The clamped value.
265+ */
266+ private double clamp (double value , double min , double max ) {
267+ return Math .max (min , Math .min (value , max ));
268+ }
269+
223270 @ Override
224271 protected void refresh (FitParams <FloatType > params , FitResults results ) {
225272 nIntervals = params .trans .length - 1 ;
@@ -248,6 +295,75 @@ protected void refresh(FitParams<FloatType> params, FitResults results) {
248295
249296 }
250297
298+ /**
299+ * Sets up mouse event handlers for a cursor group.
300+ *
301+ * @param cursor The cursor group to initialize.
302+ */
303+ private void initCursorEventHandlers (Group cursor ) {
304+ // find the circle within the cursor group
305+ var csrCircle = (Circle ) cursor .lookup ("#" + cursor .getId () + "Circle" );
306+
307+ // create onMouseEntered event handler
308+ cursor .setOnMouseEntered (event -> {
309+ double newCenterY = clamp (event .getY (),
310+ csrCircle .getRadius () * 2.5 ,
311+ fitPlotAreaPane .getHeight () - csrCircle .getRadius () * 2.5 );
312+ csrCircle .setCenterY (newCenterY );
313+ csrCircle .setScaleX (2 );
314+ csrCircle .setScaleY (2 );
315+ });
316+
317+ // create onMouseExited event handler
318+ cursor .setOnMouseExited (event -> {
319+ csrCircle .setScaleX (1 );
320+ csrCircle .setScaleY (1 );
321+ });
322+
323+ // create onMouseDragged event handler
324+ cursor .setOnMouseDragged (event -> {
325+ double newTranslateX = cursor .getTranslateX () + event .getX ();
326+ double min = 0.0 ;
327+ double max = fitPlotAreaPane .getWidth ();
328+ if (cursor == rCsr ) {
329+ min = lCsr .getTranslateX ();
330+ } else if (cursor == lCsr ) {
331+ max = rCsr .getTranslateX ();
332+ }
333+ cursor .setTranslateX (clamp (newTranslateX , min , max ));
334+ });
335+ }
336+
337+ /**
338+ * Handle "mouse entered" events.
339+ *
340+ * @param event A mouse movement event.
341+ */
342+ @ FXML
343+ private void handleMouseEntered (MouseEvent event ) {
344+ ((Group ) event .getSource ()).getOnMouseEntered ().handle (event );
345+ }
346+
347+ /**
348+ * Handle "mouse exited" events.
349+ *
350+ * @param event A mouse movement event.
351+ */
352+ @ FXML
353+ private void handleMouseExited (MouseEvent event ) {
354+ ((Group ) event .getSource ()).getOnMouseExited ().handle (event );
355+ }
356+
357+ /**
358+ * Handle "mouse dragged" events.
359+ *
360+ * @param event A mouse movement event.
361+ */
362+ @ FXML
363+ private void handleMouseDragged (MouseEvent event ) {
364+ ((Group ) event .getSource ()).getOnMouseDragged ().handle (event );
365+ }
366+
251367 /**
252368 * Adds change listeners to critical values so that they work together.
253369 *
0 commit comments