2012-12-13 28 views
2

通過在圖表上單擊鼠標左鍵我想通過創建文本區域矩形來寫文本,以便能夠調整大小和移動。JavaFx 2.x:如何在圖表上書寫文字?

任何幫助非常感激

編輯:嗨sarcan非常感謝你的善意回應。

我試過你的代碼,它編譯並繪製了帶有註釋的面積圖,非常棒!

我現在需要以一種方式更改您的代碼,以便能夠在點擊鼠標左鍵時鍵入鍵盤鍵,而不是像現在一樣打印註釋。

下面是完整的代碼

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); 
} 
} 

回答

6

1)我想通過把圖的StackPane內開始。在圖表的頂部,我會在鼠標點擊時放置一個保持文本字段的錨點窗格。

2)當用戶點擊該圖表,我會使用圖表的軸,以確定點擊是否是繪圖區和「值」被點擊內(使用NumberAxis#getValueForDisplay()

3)I將隨後將監聽器添加到圖表中以便通知任何更改(內容,寬度,高度...)並使文本區域的位置始終顯示接近相同的值。

調整大小/簡單,請讓我們知道,如果這給你任何麻煩。

編輯:根據要求,這裏是一些示例代碼。下面的代碼提供了一個簡化示例,允許您通過按住shift單擊來添加文本節點(我將它們稱爲註釋)。拖動或編輯註釋很簡單,但我想保持示例簡潔。

讓我們通過定義一個示例圖表開始:

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); 
    } 
} 

沒什麼特別的,到目前爲止,我只是創建了一些隨機模擬數據的區域圖。

對於文本節點(或註釋),我已經創建包含註釋的X/Y值(不顯示位置)和將用戶自定義節點的簡單的POJO被渲染:

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; 
    } 
} 

有趣這些東西發生在我將稱之爲覆蓋層的內部:一個透明的面板,將放置在圖表上方。請注意,我沒有像最初建議的那樣選擇AnchorPane,儘管這樣做也可以。此外,這個實現並不是最有效的方法,但我想保持簡單的例子。

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)); 
    } 
} 

棘手的部分是視圖和顯示之間的座標轉換。要獲得給定值的顯示位置,您可以調用Axis#getDisplayPosition(...),但返回的座標將位於軸的座標空間中。撥打Axis#localToParent將其轉換爲圖表的座標空間。通常情況下,您只能使用這些座標,但圖表有5個像素的默認填充,由於某些原因將無法正確翻譯。

這裏有一個小的測試應用程序把他們放在一起:

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); 
    } 
} 

現在你已經覆蓋代碼+翻譯背後座標,拖動節點應該是簡單以及想法。當拖動註解的節點時,獲取其顯示位置,添加拖動增量,將其轉換爲值並將其應用於註釋實例。

希望這可以讓事情變得更加清晰。

+0

嗨sarcan,請你給我提供一些代碼作爲例子嗎?非常感謝你 –

+0

我添加了一些示例代碼 - 不是你想要的一切,但希望足以讓你開始。 – sarcan