This program generates a chart and displays the coordinate values when the mouse enters the plotted dots by replacing the dot for a label.
But the problem is that the label does not appear completely if the dot is in the border of the chart.
I couldn't solve this issue using the toFront()
and toBack()
functions.
My code was adapted from the answer to this question JavaFX LineChart Hover Values, which has the same bug.
Chart.java
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.stage.Stage;
public class Chart extends Application {
@Override
public void start(Stage stage) {
// Random chart
// Defining the Axis
final NumberAxis xAxis = new NumberAxis();
final NumberAxis yAxis = new NumberAxis();
// Creating the chart
LineChart<Number, Number> lineChart = new LineChart(xAxis, yAxis);
// This didn't solve the bug
xAxis.toBack();
yAxis.toBack();
// Preparing the series
XYChart.Series series = new XYChart.Series();
series.setName("Chart");
for (double x = 0; x <= 10; x++) {
double y = Math.random() * 100;
XYChart.Data chartData;
chartData = new XYChart.Data(x, y);
chartData.setNode(new ShowCoordinatesNode(x, y));
series.getData().add(chartData);
}
// Adding series to chart
lineChart.getData().add(series);
Scene scene = new Scene(lineChart, 800, 600);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
ShowCoordinatesNode.java
import java.text.DecimalFormat;
import javafx.event.EventHandler;
import javafx.scene.Cursor;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
public class ShowCoordinatesNode extends StackPane {
public ShowCoordinatesNode(double x, double y) {
final Label label = createDataThresholdLabel(x, y);
setOnMouseEntered(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent mouseEvent) {
setScaleX(1);
setScaleY(1);
getChildren().setAll(label);
setCursor(Cursor.NONE);
toFront(); // This didn't solve the bug
}
});
setOnMouseExited(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent mouseEvent) {
getChildren().clear();
setCursor(Cursor.CROSSHAIR);
}
});
}
private Label createDataThresholdLabel(double x, double y) {
DecimalFormat df = new DecimalFormat("0.##");
final Label label = new Label("(" + df.format(x) + "; " + df.format(y) + ")");
label.getStyleClass().addAll("default-color0", "chart-line-symbol", "chart-series-line");
label.setStyle("-fx-font-size: 10; -fx-font-weight: bold;");
label.setMinSize(Label.USE_PREF_SIZE, Label.USE_PREF_SIZE);
label.setId("show-coord-label");
return label;
}
}
I have done a some searching trying to make this work and the reason toFront doesn't work as the Javadoc states
Moves this Node to the front of its sibling nodes in terms of z-order. This is accomplished by moving this Node to the last position in its parent's content ObservableList. This function has no effect if this Node is not part of a group.
So I could not get that to work in any sense. This is the only solution I could come up with it involves figuring out the width/height of the label/2 and shifting the label if it on the X or Y axis so you could see it
I made no changes in the Main other than hardcoding a testing value and removing the toFront Calls
public class Main extends Application {
@Override
public void start(Stage stage) throws Exception{
// Random chart
// Defining the Axis
final NumberAxis xAxis = new NumberAxis();
final NumberAxis yAxis = new NumberAxis();
// Creating the chart
LineChart<Number, Number> lineChart = new LineChart<>(xAxis, yAxis);
// Preparing the series
XYChart.Series series = new XYChart.Series();
series.setName("Chart");
boolean firstRun = true;//First point at 0,0 to test
for (double x = 0; x <= 10; x++) {
double y;
if(firstRun) {
y = 0.0;
firstRun = false;
}else
y = Math.random() * 100;
XYChart.Data chartData;
chartData = new XYChart.Data<>(x, y);
chartData.setNode(new ShowCoordinatesNode(x, y));
series.getData().add(chartData);
}
// Adding series to chart
lineChart.getData().add(series);
Scene scene = new Scene(lineChart, 800, 600);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) { launch(args); }
}
Here is where I added 2 If statements to translate the label if on one of the axises
public class ShowCoordinatesNode extends StackPane {
public ShowCoordinatesNode(double x, double y) {
final Label label = createDataThresholdLabel(x, y);
setOnMouseEntered(mouseEvent -> {
setScaleX(1);
setScaleY(1);
getChildren().setAll(label);
if(x == 0.0) {
applyCss();
layout();
label.setTranslateX(label.getWidth()/2);
}
if(y == 0.0){
applyCss();
layout();
label.setTranslateY(-label.getHeight()/2);
}
setCursor(Cursor.NONE);
});
setOnMouseExited(mouseEvent -> {
getChildren().clear();
setCursor(Cursor.CROSSHAIR);
});
}
private Label createDataThresholdLabel(double x, double y) {
DecimalFormat df = new DecimalFormat("0.##");
final Label label = new Label("(" + df.format(x) + "; " + df.format(y) + ")");
label.getStyleClass().addAll("default-color0", "chart-line-symbol", "chart-series-line");
label.setStyle("-fx-font-size: 10; -fx-font-weight: bold;");
label.setMinSize(Label.USE_PREF_SIZE, Label.USE_PREF_SIZE);
label.setId("show-coord-label");
return label;
}
}
I use the applyCSS() to calculate the size of the label before it is added to the window The JavaDoc states:
If required, apply styles to this Node and its children, if any. This method does not normally need to be invoked directly but may be used in conjunction with Parent.layout() to size a Node before the next pulse, or if the Scene is not in a Stage.