Issue
I have a problem again with the JavaFX Chart : D
Context :
I had Popup/Label on my chart to display the value on hover : JavaFX LineChart Hover Values (Jewelsea answer)
Problem :
But when the point are near the edges of chart, the popup is hidden by them.
The chart with problem, I highlighted the edges of chart.
This is a problem, because my popup is bigger and display more informations (x value, y value and the data serie)
Possible solutions :
- May I can check where the edges are, and if the popup is hide. In this case, I should shift the popup. But when I look doc, I didn't found the right method :
- May I can put the popup above the chart. Like z-index in CSS.
The code :
import javafx.application.Application;
import javafx.collections.*;
import javafx.event.EventHandler;
import javafx.scene.*;
import javafx.scene.chart.*;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
/**
* Displays a LineChart which displays the value of a plotted Node when you hover over the Node.
* @author original, jewelsea https://gist.github.com/jewelsea
*/
public class LineChartWithHover extends Application {
@SuppressWarnings("unchecked")
@Override public void start(Stage stage) {
final LineChart lineChart = new LineChart(
new NumberAxis(), new NumberAxis(),
FXCollections.observableArrayList(
new XYChart.Series(
"My portfolio",
FXCollections.observableArrayList(
plot(0, 14, 15, 24, 34, 36, 22, 55, 43, 17, 29, 25)
)
)
)
);
lineChart.setCursor(Cursor.CROSSHAIR);
lineChart.setTitle("Stock Monitoring, 2013");
stage.setScene(new Scene(lineChart, 500, 400));
stage.show();
System.out.println("test 1 = "+lineChart.getProperties());
}
/** @return plotted y values for monotonically increasing integer x values, starting from x=1 */
public ObservableList<XYChart.Data<Integer, Integer>> plot(int... y) {
final ObservableList<XYChart.Data<Integer, Integer>> dataset = FXCollections.observableArrayList();
int i = 0;
while (i < y.length) {
final XYChart.Data<Integer, Integer> data = new XYChart.Data<>(i + 1, y[i]);
data.setNode(
new HoveredThresholdNode(
(i == 0) ? 0 : y[i-1],
y[i]
)
);
dataset.add(data);
i++;
}
return dataset;
}
/** a node which displays a value on hover, but is otherwise empty */
class HoveredThresholdNode extends StackPane {
HoveredThresholdNode(int priorValue, int value) {
setPrefSize(15, 15);
final Label label = createDataThresholdLabel(priorValue, value);
setOnMouseEntered(new EventHandler<MouseEvent>() {
@Override public void handle(MouseEvent mouseEvent) {
getChildren().setAll(label);
setCursor(Cursor.NONE);
toFront();
}
});
setOnMouseExited(new EventHandler<MouseEvent>() {
@Override public void handle(MouseEvent mouseEvent) {
getChildren().clear();
setCursor(Cursor.CROSSHAIR);
}
});
}
private Label createDataThresholdLabel(int priorValue, int value) {
final Label label = new Label(value + "");
label.getStyleClass().addAll("default-color0", "chart-line-symbol", "chart-series-line");
label.setStyle("-fx-font-size: 20; -fx-font-weight: bold;");
if (priorValue == 0) {
label.setTextFill(Color.DARKGRAY);
} else if (value > priorValue) {
label.setTextFill(Color.FORESTGREEN);
} else {
label.setTextFill(Color.FIREBRICK);
}
label.setMinSize(Label.USE_PREF_SIZE, Label.USE_PREF_SIZE);
return label;
}
}
public static void main(String[] args) { launch(args); }
}
Thank you in advance ! And my apologies about my english, still learning !
Solution
I have been looking on the JavaFX CSS reference guide and I could'nt find anything to simply solve your problem.
A possible solution is to translate your symbol depending on how near it is to the max or min value.
I wrote something like this, based on your code :
/**
* Displays a LineChart which displays the value of a plotted Node when you hover over the Node.
* @author original, jewelsea https://gist.github.com/jewelsea
*/
public class LineChartWithHover extends Application {
@SuppressWarnings("unchecked")
@Override public void start(Stage stage) {
final LineChart lineChart = new LineChart(
new NumberAxis(), new NumberAxis(),
FXCollections.observableArrayList(
new XYChart.Series(
"My portfolio",
FXCollections.observableArrayList(
plot(0, 14, 15, 24, 34, 36, 22, 55, 43, 17, 29, 25)
)
)
)
);
lineChart.setCursor(Cursor.CROSSHAIR);
lineChart.setTitle("Stock Monitoring, 2013");
stage.setScene(new Scene(lineChart, 500, 400));
stage.show();
System.out.println("test 1 = "+lineChart.getProperties());
}
/** @return plotted y values for monotonically increasing integer x values, starting from x=1 */
public ObservableList<XYChart.Data<Integer, Integer>> plot(Integer... y) {
final ObservableList<XYChart.Data<Integer, Integer>> dataset = FXCollections.observableArrayList();
int i = 0;
List<Integer> list = Arrays.asList(y);
int min = Collections.min(list);
int max = Collections.max(list);
int minThreshold = 5;
int maxThreshold = 5;
while (i < y.length) {
final XYChart.Data<Integer, Integer> data = new XYChart.Data<>(i + 1, y[i]);
int topMargin = 0;
if(y[i] <= min + minThreshold) {
topMargin = -50;
} else if (y[i] >= max - maxThreshold) {
topMargin = 50;
}
StackPane stackPane = new HoveredThresholdNode(
(i == 0) ? 0 : y[i-1],
y[i],
topMargin
);
data.setNode(stackPane);
dataset.add(data);
i++;
}
return dataset;
}
/** a node which displays a value on hover, but is otherwise empty */
class HoveredThresholdNode extends StackPane {
HoveredThresholdNode(int priorValue, int value, int topMargin) {
setPrefSize(15, 15);
final Label label = createDataThresholdLabel(priorValue, value);
setOnMouseEntered(new EventHandler<MouseEvent>() {
@Override public void handle(MouseEvent mouseEvent) {
getChildren().setAll(label);
setCursor(Cursor.NONE);
toFront();
setMargin(label, new Insets(topMargin,0,0,0));
}
});
setOnMouseExited(new EventHandler<MouseEvent>() {
@Override public void handle(MouseEvent mouseEvent) {
getChildren().clear();
setCursor(Cursor.CROSSHAIR);
}
});
}
private Label createDataThresholdLabel(int priorValue, int value) {
final Label label = new Label(value + "");
label.getStyleClass().addAll("default-color0", "chart-line-symbol", "chart-series-line");
label.setStyle("-fx-font-size: 20; -fx-font-weight: bold;");
if (priorValue == 0) {
label.setTextFill(Color.DARKGRAY);
} else if (value > priorValue) {
label.setTextFill(Color.FORESTGREEN);
} else {
label.setTextFill(Color.FIREBRICK);
}
label.setMinSize(Label.USE_PREF_SIZE, Label.USE_PREF_SIZE);
return label;
}
}
public static void main(String[] args) { launch(args); }
}
Basically, I am just saying that all values <= min+5 and >= max-5 must be translated.
The +/- 5 is arbitrary and should be calculated from the ticks gap and plot scale to have a perfect repositioning. Anyway, without performing any maths, it is still quite satisfying.
Answered By - Kwoinkwoin