Come aggiungere forms su JavaFX LineChart

Ho intenzione di aggiungere alcune forms su LineChart . Metto LineChart e AnchorPane nello StackPane . Ho aggiunto forms a AnchorPane ottenendo le coordinate x e y dalle serie di grafici. Ecco un esempio.

LineChartApp.java

 package shapes; import javafx.application.Application; import javafx.scene.Scene; import javafx.stage.Stage; public class LineChartApp extends Application { @Override public void start(Stage primaryStage) throws Exception { primaryStage.setScene(new Scene(new ChartContent())); primaryStage.setMaximized(true); primaryStage.show(); } public static void main(String[] args) { launch(args); } } 

ChartContent.java

 package shapes; import java.util.ArrayList; import java.util.List; import javafx.animation.KeyFrame; import javafx.animation.Timeline; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.geometry.Side; import javafx.scene.Node; import javafx.scene.chart.LineChart; import javafx.scene.chart.NumberAxis; import javafx.scene.chart.XYChart.Data; import javafx.scene.chart.XYChart.Series; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.StackPane; import javafx.scene.paint.Color; import javafx.scene.shape.Circle; import javafx.scene.shape.Shape; import javafx.util.Duration; public class ChartContent extends StackPane { private AnchorPane objectsLayer; private LineChart chart; private NumberAxis xAxis; private NumberAxis yAxis; private Series series = new Series(); private int level = 0; private int datas[][] = { { 15, 8, 12, 11, 16, 21, 13 }, { 10, 24, 20, 16, 31, 25, 44 }, { 88, 60, 105, 75, 151, 121, 137 }, { 1000, 1341, 1211, 1562, 1400, 1600, 1550 } }; private List shapes = new ArrayList(); public ChartContent() { xAxis = new NumberAxis(); yAxis = new NumberAxis(); yAxis.setSide(Side.RIGHT); yAxis.setForceZeroInRange(false); xAxis.setForceZeroInRange(false); chart = new LineChart(xAxis, yAxis); chart.setCreateSymbols(false); chart.setLegendVisible(false); chart.setAnimated(false); chart.setVerticalZeroLineVisible(false); Timeline timer = new Timeline(new KeyFrame(Duration.seconds(5), new EventHandler() { @Override public void handle(ActionEvent event) { chartRefresh(); } })); timer.setCycleCount(datas.length - 1); timer.play(); objectsLayer = new AnchorPane(); objectsLayer.prefHeightProperty().bind(heightProperty()); objectsLayer.prefWidthProperty().bind(widthProperty()); getChildren().addAll(chart, objectsLayer); chartRefresh(); } private void chartRefresh() { series.getData().clear(); if (level < datas.length) { for (int i = 0; i < datas[level].length; i++) { series.getData().add( new Data(i, datas[level][i])); } } level++; chart.getData().clear(); chart.getData().add(series); series.getNode().setStyle("-fx-stroke:blue;-fx-stroke-width:1"); reDrawShapes(series); } private void reDrawShapes(Series series) { Node chartPlotBackground = chart.lookup(".chart-plot-background"); chartPlotBackground.setStyle("-fx-background-color:white"); Circle circle; objectsLayer.getChildren().removeAll(shapes); shapes.clear(); double top = chart.getPadding().getTop(), left = chart.getPadding() .getLeft(); double minX = chartPlotBackground.getBoundsInParent().getMinX(); double minY = chartPlotBackground.getBoundsInParent().getMinY(); for (Data data : series.getData()) { circle = new Circle(minX + chart.getXAxis().getDisplayPosition(data.getXValue()) + left, minY + chart.getYAxis().getDisplayPosition(data.getYValue()) + top, 3, Color.RED); shapes.add(circle); } objectsLayer.getChildren().addAll(shapes); } } 

Sto aggiornando le serie di grafici ogni cinque secondi e ridisegno le sue forms. Ma dopo le forms aggiunte ad AnchorPane , non sono lì dove mi aspetto che siano.


Risultato atteso

Risultato atteso: grafico a linee con cerchi rossi nei punti dati


Risultato attuale

Risultato effettivo: grafico a linee con cerchi rossi in luoghi apparentemente casuali

Innanzitutto, tieni presente che per l’esatta funzionalità che stai cercando di raggiungere, ciò può essere fatto semplicemente impostando un nodo sui dati.

(A parte: si potrebbe argomentare, e direi, che rendere un nodo una proprietà dei dati visualizzati nel grafico viola praticamente ogni buona pratica sulla separazione della vista dai dati nello sviluppo dell’interfaccia utente. L’API Chart ha un numero di difettosi difetti di progettazione, imho, e questo è uno di questi, probabilmente ci dovrebbe essere qualcosa di simile a una proprietà Function, Node> nodeFactory del Chart stesso per questo, ma è ciò che è.)

 private void chartRefresh() { series.getData().clear(); if (level < datas.length) { for (int i = 0; i < datas[level].length; i++) { Data data = new Data(i, datas[level][i]); data.setNode(new Circle(3, Color.RED)); series.getData().add(data); } } level++; chart.getData().clear(); chart.getData().add(series); series.getNode().setStyle("-fx-stroke:blue;-fx-stroke-width:1"); // reDrawShapes(series); } 

Funziona se il tuo nodo è abbastanza semplice da centrarlo sul punto è ciò di cui hai bisogno.

Se si desidera qualcosa di più complesso, per il quale ciò non funziona, il meccanismo supportato consiste nel sottoclass la class del grafico e sovrascrive il metodo layoutPlotChildren() . Ecco la class completa che utilizza questo approccio:

 import java.util.ArrayList; import java.util.List; import javafx.animation.KeyFrame; import javafx.animation.Timeline; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.geometry.Side; import javafx.scene.chart.LineChart; import javafx.scene.chart.NumberAxis; import javafx.scene.chart.XYChart.Data; import javafx.scene.chart.XYChart.Series; import javafx.scene.layout.StackPane; import javafx.scene.paint.Color; import javafx.scene.shape.Circle; import javafx.scene.shape.Shape; import javafx.util.Duration; public class ChartContent extends StackPane { private LineChart chart; private NumberAxis xAxis; private NumberAxis yAxis; private Series series = new Series(); private int level = 0; private int datas[][] = { { 15, 8, 12, 11, 16, 21, 13 }, { 10, 24, 20, 16, 31, 25, 44 }, { 88, 60, 105, 75, 151, 121, 137 }, { 1000, 1341, 1211, 1562, 1400, 1600, 1550 } }; public ChartContent() { xAxis = new NumberAxis(); yAxis = new NumberAxis(); yAxis.setSide(Side.RIGHT); yAxis.setForceZeroInRange(false); xAxis.setForceZeroInRange(false); chart = new LineChart(xAxis, yAxis) { private List shapes = new ArrayList<>(); @Override public void layoutPlotChildren() { super.layoutPlotChildren(); getPlotChildren().removeAll(shapes); shapes.clear(); for (Data d : series.getData()) { double x = xAxis.getDisplayPosition(d.getXValue()); double y = yAxis.getDisplayPosition(d.getYValue()); shapes.add(new Circle(x, y, 3, Color.RED)); } getPlotChildren().addAll(shapes); } }; chart.setCreateSymbols(false); chart.setLegendVisible(false); chart.setAnimated(false); chart.setVerticalZeroLineVisible(false); Timeline timer = new Timeline(new KeyFrame(Duration.seconds(5), new EventHandler() { @Override public void handle(ActionEvent event) { chartRefresh(); } })); timer.setCycleCount(datas.length - 1); timer.play(); getChildren().addAll(chart); chartRefresh(); } private void chartRefresh() { series.getData().clear(); if (level < datas.length) { for (int i = 0; i < datas[level].length; i++) { Data data = new Data(i, datas[level][i]); data.setNode(new Circle(3, Color.RED)); series.getData().add(data); } } level++; chart.getData().clear(); chart.getData().add(series); series.getNode().setStyle("-fx-stroke:blue;-fx-stroke-width:1"); } } 

Questo risulta in

Grafico a linee con cerchi rossi nei punti dati


È ansible utilizzare questa tecnica per, ad esempio, aggiungere linee di adattamento migliori per distribuire grafici o linee di tendenza su grafici a linee, ecc.

Non riesco a capire esattamente perché il codice che hai usato non funzioni, ma fa diverse ipotesi su come viene gestito il layout (cioè la posizione del chart-plot-background in relazione al grafico generale stesso) e anche su quando misurare sono prese per fare cose come calcolare la scala negli assi per la mapping da “coordinate del grafico” a “coordinate del pixel”. Non è difficile immaginare che questi diventino non validi quando i dati cambiano e vengono ricalcolati solo all’inizio del processo di layout, ad esempio. La registrazione dei “valori dei dati” ( data.getXValue() e data.getYValue() ) insieme ai valori Axis.getDisplayValue(...) da Axis.getDisplayValue(...) per quei valori suggerisce che qualcosa di simile a quest’ultima spiegazione potrebbe essere il caso, come quelli sicuramente non sembrano produrre le trasformazioni corrette.

L’ layoutPlotChildren() metodo layoutPlotChildren() è più affidabile.