From a9efc73d7ddc402588e76b98ac79e9ce23311143 Mon Sep 17 00:00:00 2001 From: Alexander Krimm Date: Tue, 8 Sep 2020 10:55:42 +0200 Subject: [PATCH 1/2] Move indicator triangles to corresponding axis Moves the triangles from the edge of the chart on top of the corresponding axis, so the user can see which axis an indicator belongs to. --- .../src/main/java/de/gsi/chart/axes/Axis.java | 7 +- .../de/gsi/chart/axes/spi/AbstractAxis.java | 1 + .../plugins/AbstractSingleValueIndicator.java | 19 +++- .../de/gsi/chart/plugins/XValueIndicator.java | 16 +++- .../de/gsi/chart/plugins/YValueIndicator.java | 14 ++- .../axes/spi/AbstractAxisParameterTests.java | 6 ++ .../chart/samples/ChartIndicatorSample.java | 94 +++++++------------ 7 files changed, 86 insertions(+), 71 deletions(-) diff --git a/chartfx-chart/src/main/java/de/gsi/chart/axes/Axis.java b/chartfx-chart/src/main/java/de/gsi/chart/axes/Axis.java index 0be12aa26..00c07f621 100644 --- a/chartfx-chart/src/main/java/de/gsi/chart/axes/Axis.java +++ b/chartfx-chart/src/main/java/de/gsi/chart/axes/Axis.java @@ -7,6 +7,7 @@ import javafx.beans.property.ObjectProperty; import javafx.beans.property.StringProperty; import javafx.collections.ObservableList; +import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.paint.Paint; import javafx.scene.text.Font; @@ -17,7 +18,6 @@ import de.gsi.chart.axes.spi.TickMark; import de.gsi.chart.ui.geometry.Side; import de.gsi.dataset.AxisDescription; -import de.gsi.dataset.event.EventSource; import de.gsi.dataset.event.UpdateEvent; public interface Axis extends AxisDescription { @@ -346,4 +346,9 @@ default void invokeListener(final UpdateEvent updateEvent, final boolean execute * @return the primary unit label property */ DoubleProperty unitScalingProperty(); + + /** + * @return the canvas of the axis + */ + Canvas getCanvas(); } diff --git a/chartfx-chart/src/main/java/de/gsi/chart/axes/spi/AbstractAxis.java b/chartfx-chart/src/main/java/de/gsi/chart/axes/spi/AbstractAxis.java index f587880c9..37a13d7f4 100644 --- a/chartfx-chart/src/main/java/de/gsi/chart/axes/spi/AbstractAxis.java +++ b/chartfx-chart/src/main/java/de/gsi/chart/axes/spi/AbstractAxis.java @@ -278,6 +278,7 @@ public AxisLabelFormatter getAxisLabelFormatter() { return axisFormatter.get(); } + @Override public Canvas getCanvas() { return canvas; } diff --git a/chartfx-chart/src/main/java/de/gsi/chart/plugins/AbstractSingleValueIndicator.java b/chartfx-chart/src/main/java/de/gsi/chart/plugins/AbstractSingleValueIndicator.java index 32972e7b2..04559a0c9 100644 --- a/chartfx-chart/src/main/java/de/gsi/chart/plugins/AbstractSingleValueIndicator.java +++ b/chartfx-chart/src/main/java/de/gsi/chart/plugins/AbstractSingleValueIndicator.java @@ -97,10 +97,17 @@ protected AbstractSingleValueIndicator(Axis axis, final double value, final Stri editableIndicatorProperty().addListener((ch, o, n) -> updateMouseListener(n)); updateMouseListener(isEditable()); + // remove triangle from chart foregrond when the chart is removed + chartProperty().addListener((p, o, n) -> { + if (o != null) { + o.getPlotForeground().getChildren().remove(triangle); + } + }); + // Need to add them so that at initialization of the stage the CCS is // applied and we can calculate label's // width and height - getChartChildren().addAll(line, triangle, label); + getChartChildren().addAll(line, label); this.value.addListener( (ch, o, n) -> invokeListener(new UpdateEvent(this, "value changed to " + n + " for axis " + axis))); } @@ -162,6 +169,7 @@ private void initTriangle() { triangle.visibleProperty().bind(editableIndicatorProperty()); triangle.mouseTransparentProperty().bind(editableIndicatorProperty().not()); triangle.setPickOnBounds(true); + triangle.setOpacity(0.7); final double a = AbstractSingleValueIndicator.triangleHalfWidth; triangle.getPoints().setAll(-a, -a, -a, +a, +a, +a, +a, -a); triangle.setOnMousePressed(mouseEvent -> { @@ -216,7 +224,6 @@ protected void layoutLine(final double startX, final double startY, final double addChildNodeIfNotPresent(line); addChildNodeIfNotPresent(pickLine); - // pickLine.toBack(); } /** @@ -234,7 +241,13 @@ protected void layoutMarker(final double startX, final double startY, final doub triangle.setTranslateX(startX); triangle.setTranslateY(startY); - addChildNodeIfNotPresent(triangle); + triangle.toFront(); + // triangle has to be put onto the plot foreground to be able to put it on top of axes + // is removed when the chart is changed + //addChildNodeIfNotPresent(triangle); + if (!getChart().getPlotForeground().getChildren().contains(triangle)) { + getChart().getPlotForeground().getChildren().add(triangle); + } } /** diff --git a/chartfx-chart/src/main/java/de/gsi/chart/plugins/XValueIndicator.java b/chartfx-chart/src/main/java/de/gsi/chart/plugins/XValueIndicator.java index bacc85e0a..2ea077778 100644 --- a/chartfx-chart/src/main/java/de/gsi/chart/plugins/XValueIndicator.java +++ b/chartfx-chart/src/main/java/de/gsi/chart/plugins/XValueIndicator.java @@ -7,6 +7,7 @@ import javafx.scene.input.MouseEvent; import de.gsi.chart.axes.Axis; +import de.gsi.chart.ui.geometry.Side; import de.gsi.dataset.event.EventSource; /** @@ -44,7 +45,6 @@ public XValueIndicator(final Axis axis, final double value) { */ public XValueIndicator(final Axis axis, final double value, final String text) { super(axis, value, text); - triangle.getPoints().setAll(0.0, 0.0, -8.0, -8.0, 8.0, -8.0); setLabelPosition(0.04); pickLine.setOnMouseDragged(this::handleDragMouseEvent); @@ -74,13 +74,23 @@ public void layoutChildren() { final double maxX = plotAreaBounds.getMaxX(); final double minY = plotAreaBounds.getMinY(); final double maxY = plotAreaBounds.getMaxY(); - final double xPos = minX + getChart().getFirstAxis(Orientation.HORIZONTAL).getDisplayPosition(getValue()); + final double xPos = minX + getAxis().getDisplayPosition(getValue()); + final double axisPos; + if (getAxis().getSide().equals(Side.BOTTOM)) { + triangle.getPoints().setAll(0.0, -8.0, -8.0, 0.0, 8.0, 0.0); + axisPos = getChart().getPlotForeground().sceneToLocal(getAxis().getCanvas().localToScene(0, 0)).getY() + 6; + } else { + triangle.getPoints().setAll(0.0, 0.0, -8.0, -8.0, 8.0, -8.0); + axisPos = getChart().getPlotForeground().sceneToLocal(getAxis().getCanvas().localToScene(0, getAxis().getHeight())).getY() - 6; + } + final double xPosGlobal = getChart().getPlotForeground().sceneToLocal(getChart().getCanvas().localToScene(xPos, 0)).getX(); if (xPos < minX || xPos > maxX) { getChartChildren().clear(); } else { layoutLine(xPos, minY, xPos, maxY); - layoutMarker(xPos, minY + 1.5 * AbstractSingleValueIndicator.triangleHalfWidth, xPos, maxY); + layoutMarker(xPosGlobal, axisPos + 4, xPos, maxY); + layoutLabel(new BoundingBox(xPos, minY, 0, maxY - minY), AbstractSingleValueIndicator.MIDDLE_POSITION, getLabelPosition()); } diff --git a/chartfx-chart/src/main/java/de/gsi/chart/plugins/YValueIndicator.java b/chartfx-chart/src/main/java/de/gsi/chart/plugins/YValueIndicator.java index 0331bf915..64095adcb 100644 --- a/chartfx-chart/src/main/java/de/gsi/chart/plugins/YValueIndicator.java +++ b/chartfx-chart/src/main/java/de/gsi/chart/plugins/YValueIndicator.java @@ -11,6 +11,7 @@ import javafx.scene.input.MouseEvent; import de.gsi.chart.axes.Axis; +import de.gsi.chart.ui.geometry.Side; import de.gsi.dataset.event.EventSource; /** @@ -49,7 +50,6 @@ public YValueIndicator(final Axis axis, final double value) { */ public YValueIndicator(final Axis axis, final double value, final String text) { super(axis, value, text); - triangle.getPoints().setAll(0.0, 0.0, 8.0, 8.0, 8.0, -8.0); setLabelHorizontalAnchor(HPos.RIGHT); setLabelPosition(0.975); @@ -81,13 +81,21 @@ public void layoutChildren() { final double maxY = plotAreaBounds.getMaxY(); final double yPos = minY + getAxis().getDisplayPosition(getValue()); + final double axisPos; + if (getAxis().getSide().equals(Side.RIGHT)) { + axisPos = getChart().getPlotForeground().sceneToLocal(getAxis().getCanvas().localToScene(0, 0)).getX() + 2; + triangle.getPoints().setAll(0.0, 0.0, 8.0, 8.0, 8.0, -8.0); + } else { + axisPos = getChart().getPlotForeground().sceneToLocal(getAxis().getCanvas().localToScene(getAxis().getWidth(), 0)).getX() - 2; + triangle.getPoints().setAll(0.0, 0.0, -8.0, 8.0, -8.0, -8.0); + } + final double yPosGlobal = getChart().getPlotForeground().sceneToLocal(getChart().getCanvas().localToScene(0, yPos)).getY(); if (yPos < minY || yPos > maxY) { getChartChildren().clear(); } else { layoutLine(minX, yPos, maxX, yPos); - layoutMarker(maxX - 1.5 * AbstractSingleValueIndicator.triangleHalfWidth, yPos, minX, yPos); // + - // 1.5*TRIANGLE_HALF_WIDTH + layoutMarker(axisPos, yPosGlobal, minX, yPos); layoutLabel(new BoundingBox(minX, yPos, maxX - minX, 0), getLabelPosition(), AbstractSingleValueIndicator.MIDDLE_POSITION); } diff --git a/chartfx-chart/src/test/java/de/gsi/chart/axes/spi/AbstractAxisParameterTests.java b/chartfx-chart/src/test/java/de/gsi/chart/axes/spi/AbstractAxisParameterTests.java index 156daac4f..a599eb730 100644 --- a/chartfx-chart/src/test/java/de/gsi/chart/axes/spi/AbstractAxisParameterTests.java +++ b/chartfx-chart/src/test/java/de/gsi/chart/axes/spi/AbstractAxisParameterTests.java @@ -6,6 +6,7 @@ import java.util.List; +import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.paint.Color; import javafx.scene.text.Font; @@ -374,5 +375,10 @@ public boolean isValueOnAxis(double value) { public void requestAxisLayout() { // deliberately not implemented } + + @Override + public Canvas getCanvas() { + return null; + } } } diff --git a/chartfx-samples/src/main/java/de/gsi/chart/samples/ChartIndicatorSample.java b/chartfx-samples/src/main/java/de/gsi/chart/samples/ChartIndicatorSample.java index a86923fc8..7ccc9af01 100644 --- a/chartfx-samples/src/main/java/de/gsi/chart/samples/ChartIndicatorSample.java +++ b/chartfx-samples/src/main/java/de/gsi/chart/samples/ChartIndicatorSample.java @@ -11,13 +11,10 @@ import javafx.application.Platform; import javafx.scene.Scene; import javafx.scene.control.Button; -import javafx.scene.control.Label; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.Region; -import javafx.scene.layout.VBox; -import javafx.scene.text.Font; import javafx.stage.Stage; import org.slf4j.Logger; @@ -38,25 +35,20 @@ import de.gsi.chart.renderer.ErrorStyle; import de.gsi.chart.renderer.datareduction.DefaultDataReducer; import de.gsi.chart.renderer.spi.ErrorDataSetRenderer; +import de.gsi.chart.ui.ProfilerInfoBox; import de.gsi.chart.ui.geometry.Side; -import de.gsi.chart.utils.SimplePerformanceMeter; import de.gsi.dataset.spi.FifoDoubleErrorDataSet; import de.gsi.dataset.testdata.spi.RandomDataGenerator; import de.gsi.dataset.utils.ProcessingProfiler; public class ChartIndicatorSample extends Application { private static final Logger LOGGER = LoggerFactory.getLogger(ChartIndicatorSample.class); - private static final int DEBUG_UPDATE_RATE = 1000; - private static final int MIN_PIXEL_DISTANCE = 0; // 0: just drop points that - // are drawn on the same - // pixel + private static final int MIN_PIXEL_DISTANCE = 0; // 0: just drop points that are drawn on the same pixel private static final int N_SAMPLES = 3000; // default: 1000000 private static final int UPDATE_DELAY = 1000; // [ms] private static final int UPDATE_PERIOD = 40; // [ms] - private static final int BUFFER_CAPACITY = 750; // 750 samples @ 25 Hz <-> - // 30 s - private static final double MAX_DISTANCE = ChartIndicatorSample.BUFFER_CAPACITY * ChartIndicatorSample.UPDATE_PERIOD - * 1e-3 * 0.90; + private static final int BUFFER_CAPACITY = 750; // 750 samples @ 25 Hz <-> 30 s + private static final double MAX_DISTANCE = ChartIndicatorSample.BUFFER_CAPACITY * ChartIndicatorSample.UPDATE_PERIOD * 1e-3 * 0.90; public final FifoDoubleErrorDataSet rollingBufferDipoleCurrent = new FifoDoubleErrorDataSet("dipole current [A]", ChartIndicatorSample.BUFFER_CAPACITY, ChartIndicatorSample.MAX_DISTANCE); @@ -72,10 +64,7 @@ public class ChartIndicatorSample extends Application { private void generateData() { startTime = ProcessingProfiler.getTimeStamp(); - final double now = System.currentTimeMillis() / 1000.0 + 1; // N.B. '+1' - // to check - // for - // resolution + final double now = System.currentTimeMillis() / 1000.0 + 1; // N.B. '+1' to check for resolution if (rollingBufferDipoleCurrent.getDataCount() == 0) { rollingBufferBeamIntensity.autoNotification().set(false); @@ -97,21 +86,20 @@ private void generateData() { rollingSine.autoNotification().set(true); } else { rollingBufferDipoleCurrent.autoNotification().set(false); - final double t = now; - final double y = 25 * ChartIndicatorSample.rampFunctionDipoleCurrent(t); - final double y2 = 100 * ChartIndicatorSample.rampFunctionBeamIntensity(t); + final double y = 25 * ChartIndicatorSample.rampFunctionDipoleCurrent(now); + final double y2 = 100 * ChartIndicatorSample.rampFunctionBeamIntensity(now); final double ey = 1; - rollingBufferDipoleCurrent.add(t, y, ey, ey); - rollingBufferBeamIntensity.add(t, y2, ey, ey); - final double val = 1500 + 1000.0 * Math.sin(Math.PI * 2 * 0.1 * t); - rollingSine.add(t + 1, val, ey, ey); + rollingBufferDipoleCurrent.add(now, y, ey, ey); + rollingBufferBeamIntensity.add(now, y2, ey, ey); + final double val = 1500 + 1000.0 * Math.sin(Math.PI * 2 * 0.1 * now); + rollingSine.add(now + 1, val, ey, ey); rollingBufferDipoleCurrent.autoNotification().set(true); } ProcessingProfiler.getTimeDiff(startTime, "adding data into DataSet"); } - private HBox getHeaderBar(final Scene scene, final TimerTask task) { + private HBox getHeaderBar(final TimerTask task) { final Button newDataSet = new Button("new DataSet"); newDataSet.setOnAction(evt -> Platform.runLater(task)); @@ -134,32 +122,13 @@ private HBox getHeaderBar(final Scene scene, final TimerTask task) { HBox.setHgrow(spacer, Priority.ALWAYS); // JavaFX and Chart Performance metrics - final SimplePerformanceMeter meter = new SimplePerformanceMeter(scene, DEBUG_UPDATE_RATE); - - final Label fxFPS = new Label(); - fxFPS.setFont(Font.font("Monospaced", 12)); - final Label chartFPS = new Label(); - chartFPS.setFont(Font.font("Monospaced", 12)); - final Label cpuLoadProcess = new Label(); - cpuLoadProcess.setFont(Font.font("Monospaced", 12)); - final Label cpuLoadSystem = new Label(); - cpuLoadSystem.setFont(Font.font("Monospaced", 12)); - meter.fxFrameRateProperty().addListener((ch, o, n) -> { - final String fxRate = String.format("%4.1f", meter.getFxFrameRate()); - final String actualRate = String.format("%4.1f", meter.getActualFrameRate()); - final String cpuProcess = String.format("%5.1f", meter.getProcessCpuLoad()); - final String cpuSystem = String.format("%5.1f", meter.getSystemCpuLoad()); - fxFPS.setText(String.format("%-6s: %4s %s", "JavaFX", fxRate, "FPS, ")); - chartFPS.setText(String.format("%-6s: %4s %s", "Actual", actualRate, "FPS, ")); - cpuLoadProcess.setText(String.format("%-11s: %4s %s", "Process-CPU", cpuProcess, "%")); - cpuLoadSystem.setText(String.format("%-11s: %4s %s", "System -CPU", cpuSystem, "%")); - }); + final ProfilerInfoBox profiler = new ProfilerInfoBox(); + profiler.setDebugLevel(ProfilerInfoBox.DebugLevel.VERSION); - return new HBox(newDataSet, startTimer, spacer, new VBox(fxFPS, chartFPS), - new VBox(cpuLoadProcess, cpuLoadSystem)); + return new HBox(newDataSet, startTimer, spacer, profiler); } - public BorderPane initComponents(final Scene scene) { + public BorderPane initComponents() { final BorderPane root = new BorderPane(); generateData(); initErrorDataSetRenderer(beamIntensityRenderer); @@ -167,16 +136,19 @@ public BorderPane initComponents(final Scene scene) { final DefaultNumericAxis xAxis1 = new DefaultNumericAxis(); final DefaultNumericAxis xAxis2 = new DefaultNumericAxis(); + xAxis2.setAnimated(false); final DefaultNumericAxis yAxis1 = new DefaultNumericAxis("beam intensity", "ppp"); final DefaultNumericAxis yAxis2 = new DefaultNumericAxis("dipole current", "A"); - xAxis2.setAnimated(false); yAxis2.setSide(Side.RIGHT); yAxis2.setAutoUnitScaling(true); yAxis2.setAutoRanging(true); yAxis2.setAnimated(false); - // N.B. it's important to set secondary axis on the 2nd renderer before - // adding the renderer to the chart - dipoleCurrentRenderer.getAxes().add(yAxis2); + final DefaultNumericAxis yAxis3 = new DefaultNumericAxis("test", 0, 1, 0.1); + yAxis3.setSide(Side.RIGHT); + final DefaultNumericAxis xAxis3 = new DefaultNumericAxis("test", 0, 1, 0.1); + xAxis3.setSide(Side.TOP); + // N.B. it's important to set secondary axis on the 2nd renderer before adding the renderer to the chart + dipoleCurrentRenderer.getAxes().addAll(yAxis2); final XYChart chart = new XYChart(xAxis1, yAxis1); chart.legendVisibleProperty().set(true); @@ -222,14 +194,12 @@ public BorderPane initComponents(final Scene scene) { final XValueIndicator xValueIndicator = new XValueIndicator(xAxis1, minX + 0.5 * rangeX, "mid-range label -X"); chart.getPlugins().add(xValueIndicator); - // xValueIndicator.valueProperty().bind(xAxis1.lowerBoundProperty().add(5)); + // xValueIndicator.valueProperty().bind(xAxis1.lowerBoundProperty().add(5)); - final YValueIndicator yValueIndicator1 = new YValueIndicator(yAxis1, minY1 + 0.5 * rangeY1, - "mid-range label -Y1"); + final YValueIndicator yValueIndicator1 = new YValueIndicator(yAxis1, minY1 + 0.5 * rangeY1, "mid-range label -Y1"); chart.getPlugins().add(yValueIndicator1); - final YValueIndicator yValueIndicator2 = new YValueIndicator(yAxis2, minY2 + 0.2 * rangeY2, - "mid-range label -Y2"); + final YValueIndicator yValueIndicator2 = new YValueIndicator(yAxis2, minY2 + 0.2 * rangeY2, "mid-range label -Y2"); chart.getPlugins().add(yValueIndicator2); beamIntensityRenderer.getDatasets().add(rollingBufferBeamIntensity); @@ -258,6 +228,10 @@ public BorderPane initComponents(final Scene scene) { yAxis1.setAutoRangeRounding(true); yAxis2.setAutoRangeRounding(true); + chart.getAxes().addAll(yAxis3, xAxis3); + chart.getPlugins().add(new YValueIndicator(yAxis3, 0.4)); + chart.getPlugins().add(new XValueIndicator(xAxis3, 0.3)); + final TimerTask task = new TimerTask() { int updateCount = 0; @@ -285,7 +259,7 @@ public void run() { } }; - root.setTop(getHeaderBar(scene, task)); + root.setTop(getHeaderBar(task)); startTime = ProcessingProfiler.getTimeStamp(); ProcessingProfiler.getTimeDiff(startTime, "adding data to chart"); @@ -300,9 +274,7 @@ public void run() { protected void initErrorDataSetRenderer(final ErrorDataSetRenderer eRenderer) { eRenderer.setErrorType(ErrorStyle.ERRORSURFACE); - eRenderer.setDashSize(ChartIndicatorSample.MIN_PIXEL_DISTANCE); // plot - // pixel-to-pixel - // distance + eRenderer.setDashSize(ChartIndicatorSample.MIN_PIXEL_DISTANCE); // plot pixel-to-pixel distance eRenderer.setDrawMarker(false); final DefaultDataReducer reductionAlgorithm = (DefaultDataReducer) eRenderer.getRendererDataReducer(); reductionAlgorithm.setMinPointPixelDistance(ChartIndicatorSample.MIN_PIXEL_DISTANCE); @@ -314,7 +286,7 @@ public void start(final Stage primaryStage) { final BorderPane root = new BorderPane(); final Scene scene = new Scene(root, 1800, 400); - root.setCenter(initComponents(scene)); + root.setCenter(initComponents()); startTime = ProcessingProfiler.getTimeStamp(); primaryStage.setTitle(this.getClass().getSimpleName()); From 19879f907eabedd88a75cc59582ce7add256be03 Mon Sep 17 00:00:00 2001 From: Alexander Krimm Date: Tue, 8 Sep 2020 16:12:51 +0200 Subject: [PATCH 2/2] Make indicator label editable and deletable Right clicking on the indicator label allows editing the indicator title by showing a TextEdit. Pressing ENTER will commit the change, pressing ESC will reset the change and pressing CTRL + DEL will remove the indicator from the chart. --- .../plugins/AbstractRangeValueIndicator.java | 9 +-- .../plugins/AbstractSingleValueIndicator.java | 22 ++++---- .../chart/plugins/AbstractValueIndicator.java | 56 +++++++++++++++---- .../utils/ValueIndicatorSelector.java | 4 +- 4 files changed, 61 insertions(+), 30 deletions(-) diff --git a/chartfx-chart/src/main/java/de/gsi/chart/plugins/AbstractRangeValueIndicator.java b/chartfx-chart/src/main/java/de/gsi/chart/plugins/AbstractRangeValueIndicator.java index 6bc54bf3d..f3c6f646a 100644 --- a/chartfx-chart/src/main/java/de/gsi/chart/plugins/AbstractRangeValueIndicator.java +++ b/chartfx-chart/src/main/java/de/gsi/chart/plugins/AbstractRangeValueIndicator.java @@ -1,11 +1,12 @@ package de.gsi.chart.plugins; -import de.gsi.chart.axes.Axis; import javafx.beans.property.DoubleProperty; import javafx.beans.property.SimpleDoubleProperty; import javafx.geometry.Bounds; import javafx.scene.shape.Rectangle; +import de.gsi.chart.axes.Axis; + /** * Plugin indicating a value range as a rectangle drawn on the plot area, with an optional {@link #textProperty() text * label} describing the range. @@ -22,7 +23,6 @@ public abstract class AbstractRangeValueIndicator extends AbstractValueIndicator protected final Rectangle rectangle = new Rectangle(0, 0, 0, 0); private final DoubleProperty lowerBound = new SimpleDoubleProperty(this, "lowerBound") { - @Override protected void invalidated() { layoutChildren(); @@ -30,7 +30,6 @@ protected void invalidated() { }; private final DoubleProperty upperBound = new SimpleDoubleProperty(this, "upperBound") { - @Override protected void invalidated() { layoutChildren(); @@ -39,7 +38,6 @@ protected void invalidated() { private final DoubleProperty labelHorizontalPosition = new SimpleDoubleProperty(this, "labelHorizontalPosition", 0.5) { - @Override protected void invalidated() { if (get() < 0 || get() > 1) { @@ -50,7 +48,6 @@ protected void invalidated() { }; private final DoubleProperty labelVerticalPosition = new SimpleDoubleProperty(this, "labelVerticalPosition", 0.5) { - @Override protected void invalidated() { if (get() < 0 || get() > 1) { @@ -141,13 +138,13 @@ public final DoubleProperty labelVerticalPositionProperty() { */ protected void layout(final Bounds bounds) { if (bounds.intersects(getChart().getCanvas().getBoundsInLocal())) { + layoutLabel(bounds, getLabelHorizontalPosition(), getLabelVerticalPosition()); rectangle.setX(bounds.getMinX()); rectangle.setY(bounds.getMinY()); rectangle.setWidth(bounds.getWidth()); rectangle.setHeight(bounds.getHeight()); addChildNodeIfNotPresent(rectangle); - layoutLabel(bounds, getLabelHorizontalPosition(), getLabelVerticalPosition()); } else { getChartChildren().clear(); } diff --git a/chartfx-chart/src/main/java/de/gsi/chart/plugins/AbstractSingleValueIndicator.java b/chartfx-chart/src/main/java/de/gsi/chart/plugins/AbstractSingleValueIndicator.java index 04559a0c9..ff089c6c5 100644 --- a/chartfx-chart/src/main/java/de/gsi/chart/plugins/AbstractSingleValueIndicator.java +++ b/chartfx-chart/src/main/java/de/gsi/chart/plugins/AbstractSingleValueIndicator.java @@ -153,15 +153,17 @@ private void initLine() { pickLine.setStrokeWidth(getPickingDistance()); pickLine.mouseTransparentProperty().bind(editableIndicatorProperty().not()); pickLine.setOnMousePressed(mouseEvent -> { - /* - * Record a delta distance for the drag and drop operation. Because layoutLine() sets the start/end points - * we have to use these here. It is enough to use the start point. For X indicators, start x and end x are - * identical and for Y indicators start y and end y are identical. - */ - dragDelta.x = pickLine.getStartX() - mouseEvent.getX(); - dragDelta.y = pickLine.getStartY() - mouseEvent.getY(); - pickLine.setCursor(Cursor.MOVE); - mouseEvent.consume(); + if (mouseEvent.isPrimaryButtonDown()) { + /* + * Record a delta distance for the drag and drop operation. Because layoutLine() sets the start/end points + * we have to use these here. It is enough to use the start point. For X indicators, start x and end x are + * identical and for Y indicators start y and end y are identical. + */ + dragDelta.x = pickLine.getStartX() - mouseEvent.getX(); + dragDelta.y = pickLine.getStartY() - mouseEvent.getY(); + pickLine.setCursor(Cursor.MOVE); + mouseEvent.consume(); + } }); } @@ -222,8 +224,8 @@ protected void layoutLine(final double startX, final double startY, final double pickLine.setEndX(endX); pickLine.setEndY(endY); - addChildNodeIfNotPresent(line); addChildNodeIfNotPresent(pickLine); + addChildNodeIfNotPresent(line); } /** diff --git a/chartfx-chart/src/main/java/de/gsi/chart/plugins/AbstractValueIndicator.java b/chartfx-chart/src/main/java/de/gsi/chart/plugins/AbstractValueIndicator.java index b4af83f1f..5f0950e67 100644 --- a/chartfx-chart/src/main/java/de/gsi/chart/plugins/AbstractValueIndicator.java +++ b/chartfx-chart/src/main/java/de/gsi/chart/plugins/AbstractValueIndicator.java @@ -19,6 +19,9 @@ import javafx.scene.Cursor; import javafx.scene.Node; import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.scene.input.KeyCode; +import javafx.scene.input.MouseButton; import de.gsi.chart.Chart; import de.gsi.chart.axes.Axis; @@ -39,6 +42,7 @@ public abstract class AbstractValueIndicator extends ChartPlugin { private double yOffset; protected final Label label = new Label(); + protected final TextField labelEdit = new TextField(); /* Difference between the mouse press position and the indicators center */ protected final Delta dragDelta = new Delta(); @@ -76,19 +80,49 @@ protected AbstractValueIndicator(Axis axis, final String text) { this.axis = axis; setText(text); - label.mouseTransparentProperty().bind(editableIndicatorProperty().not()); label.pickOnBoundsProperty().set(true); + label.toFront(); label.setOnMousePressed(mouseEvent -> { - /* - * Record a delta distance for the drag and drop operation. PROBLEM: At this point, we need to know the - * relative position of the label with respect to the indicator value. - */ - Point2D c = label.sceneToLocal(mouseEvent.getSceneX(), mouseEvent.getSceneY()); - dragDelta.x = -(c.getX() + xOffset); - dragDelta.y = c.getY() + yOffset; - label.setCursor(Cursor.MOVE); - mouseEvent.consume(); + if (mouseEvent.isPrimaryButtonDown() && isEditable()) { + /* + * Record a delta distance for the drag and drop operation. PROBLEM: At this point, we need to know the + * relative position of the label with respect to the indicator value. + */ + Point2D c = label.sceneToLocal(mouseEvent.getSceneX(), mouseEvent.getSceneY()); + dragDelta.x = -(c.getX() + xOffset); + dragDelta.y = c.getY() + yOffset; + label.setCursor(Cursor.MOVE); + mouseEvent.consume(); + } + }); + // mouse handler to edit the indicator on right click + label.setOnMouseClicked(evt -> { + if (evt.getButton().equals(MouseButton.SECONDARY)) { + label.setVisible(false); + getChartChildren().add(labelEdit); + labelEdit.requestFocus(); + labelEdit.setLayoutX(label.getLayoutX()); + labelEdit.setLayoutY(label.getLayoutY()); + labelEdit.resizeRelocate(label.getLayoutX() - 20, label.getLayoutY() - 5, label.getWidth() + 40, label.getHeight() + 10); + // change the label on exit + labelEdit.setOnAction(actionEvt -> { + label.setText(labelEdit.getText()); + getChartChildren().remove(labelEdit); + label.setVisible(true); + }); + labelEdit.setOnKeyPressed(keyEvt -> { + // remove the indicator when pressing ctl + delete + if (keyEvt.getCode().equals(KeyCode.DELETE) && keyEvt.isControlDown()) { + getChart().getPlugins().remove(this); + } + // restore the old label when pressing esc + if (keyEvt.getCode().equals(KeyCode.ESCAPE)) { + getChartChildren().remove(labelEdit); + label.setVisible(true); + } + }); + } }); editableIndicatorProperty().addListener((ch, o, n) -> updateMouseListener(n)); @@ -116,7 +150,7 @@ private void addAxisListener() { protected void addChildNodeIfNotPresent(final Node node) { if (!getChartChildren().contains(node)) { - getChartChildren().add(node); + getChartChildren().add(0, node); // add elements always at the bottom so they cannot steal focus } } diff --git a/chartfx-chart/src/main/java/de/gsi/chart/plugins/measurements/utils/ValueIndicatorSelector.java b/chartfx-chart/src/main/java/de/gsi/chart/plugins/measurements/utils/ValueIndicatorSelector.java index 88dd8f4ec..bd05f9e94 100644 --- a/chartfx-chart/src/main/java/de/gsi/chart/plugins/measurements/utils/ValueIndicatorSelector.java +++ b/chartfx-chart/src/main/java/de/gsi/chart/plugins/measurements/utils/ValueIndicatorSelector.java @@ -73,14 +73,12 @@ public ValueIndicatorSelector(final ParameterMeasurements plugin, final AxisMode if (plugin.getChart() != null) { plugin.getChart().getPlugins().addListener(pluginsChanged); plugin.getChart().getPlugins().forEach(this::addNewIndicators); - reuseIndicators.setSelected(plugin.getChart().getAxes().size() <= 2); } final Label label = new Label("re-use inidcators: "); GridPane.setConstraints(label, 0, 0); GridPane.setConstraints(reuseIndicators, 1, 0); - reuseIndicators.selectedProperty().addListener((ch, o, n) -> indicatorListView.setDisable(!n)); - + indicatorListView.disableProperty().bind(reuseIndicators.selectedProperty().not()); indicatorListView.setOrientation(Orientation.VERTICAL); indicatorListView.setPrefSize(-1, DEFAULT_SELECTOR_HEIGHT); indicatorListView.setCellFactory(list -> new DataSelectorLabel());