text - JavaFx 2.x : How to write text on a chart?

标签 text javafx-2

通过鼠标左键单击图表,我想通过创建文本区域矩形来编写文本,以便能够调整大小和移动。

任何帮助都非常感谢

编辑 : 您好,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);
}
}

最佳答案

1) 我首先将图表放在 StackPane 中。在图表顶部,我会放置一个 anchor Pane ,在鼠标单击时保存文本字段。

2)当用户单击图表时,我将使用图表的轴来确定单击是否在绘图区域内以及单击了哪个“值”(使用 NumberAxis#getValueForDisplay()

3) 然后我会在图表中添加监听器,以便收到任何更改(内容、宽度、高度...)的通知,并调整文本区域的位置以始终显示接近相同的值。

调整大小/直截了当,如果这给您带来任何麻烦,请告诉我们。

编辑:根据要求,这里是一些示例代码。下面的代码提供了一个简化的示例,允许您通过按住 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);
    }
}

到目前为止没有什么特别的,我只是用一些随机的模拟数据创建了一个面积图。

对于文本节点(或注释),我创建了一个简单的 POJO,其中包含带注释的 X/Y 值(不是显示位置)并采用要呈现的自定义节点:
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));
    }
}

棘手的部分是 View 和显示之间的坐标转换。要获取给定值的显示位置,可以调用 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);
    }
}

现在您已经有了覆盖代码 + 翻译坐标背后的想法,拖动节点也应该很简单。当注释的节点被拖动时,获取其显示位置,添加拖动增量,将其转换为值并将其应用于注释实例。

希望这能让事情更清楚一些。

关于text - JavaFx 2.x : How to write text on a chart?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13866069/

相关文章:

javafx-2 - JavaFX 2 繁忙指示器光标

position - JavaFX:如何设置正确的工具提示位置?

javascript - 如何自动滚动包含纯文本的 div(水平)?

perl - 如何在 Perl Linux 中为文本添加下划线?

html - 使用BeautifulSoup清除html文档和多个段落

java - 在 JavaFX 中的 PathTransition 方面需要帮助

java - 为什么在 javafx 的 listview.cellFacoty 方法中调用 super.updateItem(item, empty)?

java - 如何去除TextField的内部阴影?

c++ - 如何在另一个程序更新时流式显示文本文件?

java - 正则表达式删除与字母相邻的标点符号