【问题标题】:JavaFX8 : add label on linechart and update them according to a zoomJavaFX:在折线图上添加标签并根据缩放更新它们
【发布时间】:2016-01-31 01:30:34
【问题描述】:

我是 JavaFX 的新用户,我的实习项目遇到了一些问题。我必须制作一个 GUI 来绘制一个光谱并在每个峰的顶部添加标签(x 值)。用户可以放大图表,因此必须更新标签。

我使用第一种方法添加基于此帖子的标签:write text on a chart 但我遇到了一些麻烦,只有一半的标签可见(见图)并且缩放后标签位置的更新不好。我检查了每个标签的值和位置,它们都很好,如果我在图表上应用一个动作,标签的后半部分就会出现,所以我不知道这种误解的原因。我的更新(标签位置)功能是缩放上限 yaxis 的侦听器,但是为了获得良好的位置,我需要手动使用更新按钮,如果有人有想法,我找不到解决方案?

我目前使用anchorpane,使用stackpane更好吗?

我放了一部分代码:

public class PaneController implements Initializable{
	@FXML
	private LineChart<Number, Number> lineChart;
	private ObservableList<Annotation> annotationNodes = FXCollections.observableArrayList();

	@Override
	public void initialize(URL location, ResourceBundle resources) {
		// TODO Auto-generated method stub
		lineChart.setLegendVisible(false);
		lineChart.setCreateSymbols(false);
		lineChart.setData(createData());
		//create labels
		createLabels(lineChart.getData());
		//frame listener
		((AnchorPane)lineChart.getParent()).heightProperty().addListener((num)-> update());
    	//axis listener
		((NumberAxis)lineChart.getYAxis()).upperBoundProperty().addListener((num)-> update());
	    //update listener on middle button
	    lineChart.getParent().addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
	        @Override
	        public void handle(MouseEvent event) {
	        	  if( event.getButton() == MouseButton.MIDDLE){
	        		  update();
	            }
	        }
	    });
     MyJFXChartUtil.setupZooming( lineChart );
	}
	
	/** create data for the line chart */
	public ObservableList<Series<Number,Number>> createData(){
		 ObservableList<Series<Number,Number>> data = FXCollections.observableArrayList();
		 for(int i=1;i<=10;i++){
			 XYChart.Series<Number, Number> peakSelect = new XYChart.Series<>();
			 peakSelect.getData().add(new XYChart.Data<Number, Number>(i*2,0));
			 peakSelect.getData().add(new XYChart.Data<Number, Number>(i*2,i));
			 data.add(peakSelect);
		 }
		 return data;
	}
	
	  /** place a x-value label on each peak */
	  private void createLabels(ObservableList<Series<Number,Number>> data){
	    	Pane pane = (Pane) lineChart.getParent();
	    	//clear old annotations
		    pane.getChildren().clear();
		    pane.getChildren().add(lineChart);
			for(int i=0;i<lineChart.getData().size();i++){
				for(XYChart.Data<Number, Number> value:lineChart.getData().get(i).getData()){
					if(value.getYValue().intValue()>0){
			    		//massAnnot
			    		Annotation massAnnot = new Annotation(new Label(""+value.getXValue()), value.getXValue().doubleValue(), value.getYValue().doubleValue());
				        annotationNodes.add(massAnnot);
				        //labelAnnot
			    		Annotation labelAnnot = new Annotation(new Label(""+(i+1)), value.getXValue().doubleValue(), 11);
				        annotationNodes.add(labelAnnot);
        	    	}
				}
			}
	    	//add node to parent
	    	for (Annotation annot : annotationNodes) { 
	    		 pane.getChildren().add(annot.getLabel());
	    	}
	  }
	  
	  /** update position of labels */
	  private void update(){
		  //clear nodes and add the linechart as children
		  NumberAxis xAxis = (NumberAxis) lineChart.getXAxis();
		  NumberAxis yAxis = (NumberAxis) lineChart.getYAxis();
		  System.out.println(xAxis.getUpperBound()+" update "+yAxis.getUpperBound());
		  Pane pane = (Pane) lineChart.getParent();
		  
		  pane.getChildren().clear();
		  pane.getChildren().add(lineChart);
		  int i = 0;
		  for (Annotation annot : annotationNodes) {
			  Node node = annot.getLabel();
			  if(i%2==0){//massAnnot
				  double x = lineChart.getXAxis().localToParent(xAxis.getDisplayPosition(annot.getX()), 0).getX() + lineChart.getPadding().getLeft();
				  double y = yAxis.localToParent(0,yAxis.getDisplayPosition(annot.getY())).getY() + lineChart.getPadding().getTop();
				  node.setLayoutX(x);
				  node.setLayoutY(y - node.prefHeight(Integer.MAX_VALUE));
			  }
			  else{//labelAnnot
				  double x = lineChart.getXAxis().localToParent(xAxis.getDisplayPosition(annot.getX()), 0).getX() + lineChart.getPadding().getLeft();
				  double y = yAxis.localToParent(0,yAxis.getDisplayPosition(annot.getY())).getY() + lineChart.getPadding().getTop();
				  node.setLayoutX(x);
				  node.setLayoutY(y - node.prefHeight(Integer.MAX_VALUE));
			  }
			  node.autosize();
			  pane.getChildren().add(node);
		  }
	  }
	  
  class Annotation{
	  private Label label;
	  private double x;
	  private double y;

	  public Annotation(Label label, double x, double y) {
		  this.label = label;
		  this.x = x;
		  this.y = y;
	  }
	  public Label getLabel(){
		  return this.label;
	  }
	  
	  public double getX(){
		  return this.x;
	  }
	  
	  public double getY(){
		  return this.y;
	  }
  } 
}
public class Main extends Application {
	@Override
	public void start(Stage primaryStage) {
		try {
			FXMLLoader loader = new FXMLLoader();
			loader.setLocation(Main.class.getResource("Pane.fxml"));
			AnchorPane rootPane = (AnchorPane) loader.load();
			Scene scene = new Scene(rootPane);
			primaryStage.setScene(scene);
			primaryStage.setTitle("Test");

			PaneController controller = loader.getController();
			
			primaryStage.show();
            
		} catch(Exception e) {
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args) {
		launch(args);
	}
}
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.chart.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>

<AnchorPane xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.PaneController">
   <children>
      <LineChart fx:id="lineChart" prefHeight="400.0" prefWidth="500.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
        <xAxis>
          <NumberAxis side="BOTTOM" />
        </xAxis>
        <yAxis>
          <NumberAxis side="LEFT" />
        </yAxis>
      </LineChart>
   </children>
</AnchorPane>

【问题讨论】:

  • 能否请您发布一个简短的示例? sscce.org
  • 我通过 MVCE 编辑代码,我无法重现只看到一半的标签,但您可以测试它们的位置,根据上限 y 轴在开始时自动进行更新,但是这是错的。如果你按下鼠标中键,你会调用更新函数并看到标签移动。但是,如果您调整框架的大小,则会出现同样的问题。抱歉我的英语不好,感谢您的帮助。
  • 我很困惑,看起来您的代码没有任何问题。问题可能出在缩放代码上,你能把它也包括进去吗?
  • jfxutils library提供的zoom代码,示例实现难度较大,需要在构建路径中添加库。
  • 我不想使用按钮来更新标签的位置,我希望在缩放或调整框架大小时自动更新。

标签: javafx javafx-8 linechart


【解决方案1】:

似乎更新被称为快速并且轴没有更新,所以当你更新标签位置时,没有任何布局值被更新。您可以将代码更改为:

private void update() {
    runIn(50, () -> {
        /*your current code*/
    }
}

private void runIn(int ms, Runnable callback) {
    new Thread(() -> {
        try {
            Thread.sleep(ms);
        } catch (InterruptedException e) { e.printStackTrace(); }
        Platform.runLater(callback);
    }).start();
}

这是可行的,因为我猜想最长的Pulse(一个屏幕布局和渲染)需要多长时间。您可以通过将 -Djavafx.pulseLogger=true 标志添加到您的 JVM 参数来查看脉冲需要多长时间。每个脉冲都会显示在控制台上。你正在寻找这个:

...
PULSE: 49 [15ms:29ms]
...

29ms 是布局和渲染场景所用的时间,因为50ms 更长一些,当update() 运行时它得到了新值。但是,假设我们有一台速度较慢的机器,可能需要60ms 一个脉冲,长时间等待update() 才能获得正确的值。然后您需要将50ms 更改为70ms。最好挂上pulse 并查看它何时结束,然后运行update()。不幸的是,我不知道这样的钩子是否存在。

【讨论】:

  • 谢谢,明天我会测试,我告诉你;)
  • 谢谢,一切正常;)!但是,我不明白您的最后一个“注释”。
  • 抱歉给您带来不便,我找到了解决办法,我只是删除了我之前的评论
  • @Thomas 没关系,我很高兴能帮上忙。
猜你喜欢
  • 2019-01-12
  • 2021-12-25
  • 2018-02-14
  • 2017-03-22
  • 2015-11-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-27
相关资源
最近更新 更多