2012-12-13 5 views
2

En cliquant avec le bouton gauche de la souris sur un graphique, je voudrais écrire du texte en créant un rectangle de zone de texte afin de pouvoir le redimensionner et le déplacer.JavaFx 2.x: Comment écrire du texte sur un graphique?

Toute aide vraiment apprécié

Modifier: Salut SARCAN merci beaucoup pour votre aimable réponse.

J'ai essayé votre code, il compile et il trace un graphique à aires avec annotation, très bon travail!

Je dois maintenant changer votre code de manière à pouvoir taper avec le clavier une fois que vous avez cliqué avec la souris au lieu d'imprimer des annotations dès maintenant.

est Ci-dessous votre code complet

import javafx.application.Application; 
import javafx.beans.InvalidationListener; 
import javafx.beans.Observable; 
import javafx.collections.FXCollections; 
import javafx.collections.ObservableList; 
import javafx.event.EventHandler; 
import javafx.scene.Node; 
import javafx.scene.Scene; 
import javafx.scene.chart.AreaChart; 
import javafx.scene.chart.Axis; 
import javafx.scene.chart.NumberAxis; 
import javafx.scene.chart.XYChart; 
import javafx.scene.chart.XYChart.Data; 
import javafx.scene.chart.XYChart.Series; 
import javafx.scene.control.Label; 
import javafx.scene.input.MouseButton; 
import javafx.scene.input.MouseEvent; 
import javafx.scene.layout.Pane; 
import javafx.scene.layout.StackPane; 
import javafx.scene.paint.Color; 
import javafx.scene.shape.Circle; 
import javafx.stage.Stage; 

/** 
* 
* @author sarcan 
*/ 
public class SampleApp extends Application { 

public class SampleChart extends AreaChart<Number, Number> { 
public SampleChart() { 
    super(new NumberAxis(), new NumberAxis()); 

    getXAxis().setLabel("X"); 
    getYAxis().setLabel("Y"); 

    final Series<Number, Number> data = new Series<Number, Number>(); 
    data.setName("Dummy data"); 
    data.getData().addAll(
      new Data<Number, Number>(0,4), 
      new Data<Number, Number>(1,5), 
      new Data<Number, Number>(2,6), 
      new Data<Number, Number>(3,5), 
      new Data<Number, Number>(4,5), 
      new Data<Number, Number>(5,7), 
      new Data<Number, Number>(6,8), 
      new Data<Number, Number>(7,9), 
      new Data<Number, Number>(8,7) 
    ); 

    getData().add(data); 
} 
} 

public class ChartAnnotationNode { 
private final Node _node; 
private double _x; 
private double _y; 

public ChartAnnotationNode(final Node node, final double x, final double y) { 
    _node = node; 
    _x = x; 
    _y = y; 
} 

public Node getNode() { 
    return _node; 
} 

public double getX() { 
    return _x; 
} 

public double getY() { 
    return _y; 
} 

public void setX(final double x) { 
    _x = x; 
} 

public void setY(final double y) { 
    _y = y; 
} 
} 

public class ChartAnnotationOverlay extends Pane { 
private ObservableList<ChartAnnotationNode> _annotationNodes; 
private XYChart<Number, Number> _chart; 

public ChartAnnotationOverlay(final XYChart<Number, Number> chart) { 
    _chart = chart; 

    /* Create a list to hold your annotations */ 
    _annotationNodes = FXCollections.observableArrayList(); 

    /* This will be our update listener, to be invoked whenever the chart changes or annotations are added */ 
    final InvalidationListener listener = new InvalidationListener() { 
     @Override 
     public void invalidated(final Observable observable) { 
      update(); 
     } 
    }; 
    _chart.needsLayoutProperty().addListener(listener); 
    _annotationNodes.addListener(listener); 

    /* Add new annotations by shift-clicking */ 
    setOnMouseClicked(new EventHandler<MouseEvent>() { 
     @Override 
     public void handle(final MouseEvent mouseEvent) { 
      if (mouseEvent.getButton() == MouseButton.PRIMARY && mouseEvent.isShiftDown()) 
       addAnnotation(mouseEvent.getX(), mouseEvent.getY()); 
     } 
    }); 
} 

/** 
* Invoked whenever the chart changes or annotations are added. This basically does a relayout of the annotation nodes. 
*/ 
private void update(){ 
    getChildren().clear(); 

    final Axis<Number> xAxis = _chart.getXAxis(); 
    final Axis<Number> yAxis = _chart.getYAxis(); 

    /* For each annotation, add a circle indicating the position and the custom node right next to it */ 
    for (ChartAnnotationNode annotation : _annotationNodes) { 
     final double x = xAxis.localToParent(xAxis.getDisplayPosition(annotation.getX()), 0).getX() + _chart.getPadding().getLeft(); 
     final double y = yAxis.localToParent(0,yAxis.getDisplayPosition(annotation.getY())).getY() + _chart.getPadding().getTop(); 

     final Circle indicator = new Circle(3); 
     indicator.setStroke(Color.BLUEVIOLET); 
     indicator.setCenterX(x); 
     indicator.setCenterY(y); 

     getChildren().add(indicator); 

     final Node node = annotation.getNode(); 
     getChildren().add(node); 
     node.relocate(x + 10, y - node.prefHeight(Integer.MAX_VALUE)/2); 
     node.autosize(); 
    } 
} 

/** 
* Add a new annotation for the given display coordinate. 
*/ 
private void addAnnotation(final double displayX, final double displayY){ 
    final Axis<Number> xAxis = _chart.getXAxis(); 
    final Axis<Number> yAxis = _chart.getYAxis(); 

    final double x = (xAxis.getValueForDisplay(xAxis.parentToLocal(displayX, 0).getX() - _chart.getPadding().getLeft())).doubleValue(); 
    final double y = (yAxis.getValueForDisplay(yAxis.parentToLocal(0, displayY).getY() - _chart.getPadding().getTop())).doubleValue(); 

    if (xAxis.isValueOnAxis(x) && yAxis.isValueOnAxis(y)) 
     _annotationNodes.add(new ChartAnnotationNode(new Label("Annotation "+System.currentTimeMillis()), x, y)); 
} 
} 


@Override 
public void start(final Stage stage) throws Exception { 
    final SampleChart chart = new SampleChart(); 

    final ChartAnnotationOverlay overlay = new ChartAnnotationOverlay(chart); 

    final StackPane stackPane = new StackPane(); 
    stackPane.getChildren().addAll(chart, overlay); 

    final Scene scene = new Scene(stackPane); 
    stage.setScene(scene); 
    stage.setWidth(800); 
    stage.setHeight(600); 
    stage.show(); 
} 

public static void main(String[] args) { 
    Application.launch(args); 
} 
} 

Répondre

6

1) Je commencerai par mettre la carte dans un StackPane. En haut du tableau, je placerais un volet d'ancrage contenant le champ de texte lors d'un clic de souris.

2) Lorsque l'utilisateur clique sur le graphique, j'utiliser les axes du graphique pour déterminer si le clic se trouvait dans la zone de traçage et qui a été cliqué sur « valeur » (en utilisant NumberAxis#getValueForDisplay().

3) Je puis ajouter des écouteurs au tableau pour être averti de tout changement (contenu, largeur, hauteur ...) et adapter la position de la zone de texte pour toujours afficher près de la même valeur.

Redimensionnement/direct, s'il vous plaît laissez-nous savoir si cela vous pose des problèmes.

Modifier: Comme demandé, voici un exemple de code. Le code ci-dessous fournit un exemple simplifié, vous permettant d'ajouter des nœuds de texte (je les appellerai annotations) au graphique en cliquant sur Maj. Faire glisser ou éditer les annotations est simple, mais je voulais garder l'exemple concis.

permet de démarrer en définissant un diagramme échantillon:

public class SampleChart extends AreaChart<Number, Number> { 
    public SampleChart() { 
     super(new NumberAxis(), new NumberAxis()); 

     getXAxis().setLabel("X"); 
     getYAxis().setLabel("Y"); 

     final Series<Number, Number> data = new Series<Number, Number>(); 
     data.setName("Dummy data"); 
     data.getData().addAll(
       new Data<Number, Number>(0,4), 
       new Data<Number, Number>(1,5), 
       new Data<Number, Number>(2,6), 
       new Data<Number, Number>(3,5), 
       new Data<Number, Number>(4,5), 
       new Data<Number, Number>(5,7), 
       new Data<Number, Number>(6,8), 
       new Data<Number, Number>(7,9), 
       new Data<Number, Number>(8,7) 
     ); 

     getData().add(data); 
    } 
} 

Rien d'extraordinaire à ce jour, je viens de créer un graphique de la zone avec des données fictives au hasard.

Pour les nœuds de texte (ou annotations), j'ai créé un POJO simple contenant la valeur X/Y annoté (pas afficher la position) et prendre un noeud personnalisé à rendre:

public class ChartAnnotationNode { 
    private final Node _node; 
    private double _x; 
    private double _y; 

    public ChartAnnotationNode(final Node node, final double x, final double y) { 
     _node = node; 
     _x = x; 
     _y = y; 
    } 

    public Node getNode() { 
     return _node; 
    } 

    public double getX() { 
     return _x; 
    } 

    public double getY() { 
     return _y; 
    } 

    public void setX(final double x) { 
     _x = x; 
    } 

    public void setY(final double y) { 
     _y = y; 
    } 
} 

L'intéressant Ce qui se passe dans ce que je vais appeler la superposition: un panneau transparent qui sera placé au-dessus de la carte. Notez que je n'ai pas, comme conseillé à l'origine, choisi AnchorPane, bien que cela aurait également fonctionné. De plus, cette implémentation n'est pas exactement l'approche la plus efficace, mais je voulais garder l'exemple simple.

public class ChartAnnotationOverlay extends Pane { 
    private ObservableList<ChartAnnotationNode> _annotationNodes; 
    private XYChart<Number, Number> _chart; 

    public ChartAnnotationOverlay(final XYChart<Number, Number> chart) { 
     _chart = chart; 

     /* Create a list to hold your annotations */ 
     _annotationNodes = FXCollections.observableArrayList(); 

     /* This will be our update listener, to be invoked whenever the chart changes or annotations are added */ 
     final InvalidationListener listener = new InvalidationListener() { 
      @Override 
      public void invalidated(final Observable observable) { 
       update(); 
      } 
     }; 
     _chart.needsLayoutProperty().addListener(listener); 
     _annotationNodes.addListener(listener); 

     /* Add new annotations by shift-clicking */ 
     setOnMouseClicked(new EventHandler<MouseEvent>() { 
      @Override 
      public void handle(final MouseEvent mouseEvent) { 
       if (mouseEvent.getButton() == MouseButton.PRIMARY && mouseEvent.isShiftDown()) 
        addAnnotation(mouseEvent.getX(), mouseEvent.getY()); 
      } 
     }); 
    } 

    /** 
    * Invoked whenever the chart changes or annotations are added. This basically does a relayout of the annotation nodes. 
    */ 
    private void update(){ 
     getChildren().clear(); 

     final Axis<Number> xAxis = _chart.getXAxis(); 
     final Axis<Number> yAxis = _chart.getYAxis(); 

     /* For each annotation, add a circle indicating the position and the custom node right next to it */ 
     for (ChartAnnotationNode annotation : _annotationNodes) { 
      final double x = xAxis.localToParent(xAxis.getDisplayPosition(annotation.getX()), 0).getX() + _chart.getPadding().getLeft(); 
      final double y = yAxis.localToParent(0,yAxis.getDisplayPosition(annotation.getY())).getY() + _chart.getPadding().getTop(); 

      final Circle indicator = new Circle(3); 
      indicator.setStroke(Color.BLUEVIOLET); 
      indicator.setCenterX(x); 
      indicator.setCenterY(y); 

      getChildren().add(indicator); 

      final Node node = annotation.getNode(); 
      getChildren().add(node); 
      node.relocate(x + 10, y - node.prefHeight(Integer.MAX_VALUE)/2); 
      node.autosize(); 
     } 
    } 

    /** 
    * Add a new annotation for the given display coordinate. 
    */ 
    private void addAnnotation(final double displayX, final double displayY){ 
     final Axis<Number> xAxis = _chart.getXAxis(); 
     final Axis<Number> yAxis = _chart.getYAxis(); 

     final double x = (xAxis.getValueForDisplay(xAxis.parentToLocal(displayX, 0).getX() - _chart.getPadding().getLeft())).doubleValue(); 
     final double y = (yAxis.getValueForDisplay(yAxis.parentToLocal(0, displayY).getY() - _chart.getPadding().getTop())).doubleValue(); 

     if (xAxis.isValueOnAxis(x) && yAxis.isValueOnAxis(y)) 
      _annotationNodes.add(new ChartAnnotationNode(new Label("Annotation "+System.currentTimeMillis()), x, y)); 
    } 
} 

La partie délicate est la translation des coordonnées entre la vue et l'affichage. Pour obtenir la position d'affichage pour une valeur donnée, vous pouvez appeler Axis#getDisplayPosition(...), mais la coordonnée renvoyée sera dans l'espace de coordonnées de l'axe. L'appel à Axis#localToParent traduit cela dans l'espace de coordonnées du graphique. Normalement, vous vous attendez à pouvoir utiliser ces coordonnées, mais le tableau a un remplissage par défaut de 5 pixels qui, pour une raison quelconque, ne sera pas traduit correctement.

Voici une petite application de test mettant tous ensemble:

public class SampleApp extends Application { 
    @Override 
    public void start(final Stage stage) throws Exception { 
     final SampleChart chart = new SampleChart(); 

     final ChartAnnotationOverlay overlay = new ChartAnnotationOverlay(chart); 

     final StackPane stackPane = new StackPane(); 
     stackPane.getChildren().addAll(chart, overlay); 

     final Scene scene = new Scene(stackPane); 
     stage.setScene(scene); 
     stage.setWidth(800); 
     stage.setHeight(600); 
     stage.show(); 
    } 

    public static void main(String[] args) { 
     Application.launch(args); 
    } 
} 

Maintenant que vous avez le code de recouvrement + l'idée derrière la traduction coordonnées, faisant glisser les nœuds doivent être simples aussi bien. Lorsqu'un nœud d'annotation est déplacé, obtenez sa position d'affichage, ajoutez le delta de déplacement, convertissez-le en valeur et appliquez-le à l'instance d'annotation.

Espérons que cela rend les choses un peu plus claires.

+0

Salut sarcan, pouvez-vous s'il vous plaît me fournir un code comme exemple? Merci beaucoup –

+0

J'ai ajouté quelques exemples de code - pas tout ce que vous vouliez, mais j'espère assez pour vous aider à démarrer. – sarcan

Questions connexes