JavaFx 2.x:如何在图表上编写文本? [英] JavaFx 2.x: How to write text on a chart?

查看:54
本文介绍了JavaFx 2.x:如何在图表上编写文本?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

By left mouse click on a chart I would like to write text by creating a text area rectangle so to be able to resize and move.

任何帮助都非常感激

编辑:您好,sarcan非常感谢您的答复.

Edit: Hi sarcan thank you very much for your kind reply.

我尝试了您的代码,它会编译并绘制带有注释的面积图,非常棒!

I tried your code, it compiles and it plots an area chart with annotation, very great work!

我现在需要更改您的代码,以便能够在单击鼠标左键后立即用键盘输入,而不是现在打印注释.

I now need to change your code in a way to be able to type with key keyboard once left mouse clicked instead of printing annotations as of now.

下面是您的完整代码

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中.在图表的顶部,我将放置一个锚定窗格,单击鼠标即可保留文本字段.

1) I'd start by putting the chart within a StackPane. On top of the chart I'd place an anchor pane holding the text field upon mouse click.

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

2) When the user clicks the chart, I'd use the chart's axes to determine whether the click was inside the plot area and which 'value' was clicked (using NumberAxis#getValueForDisplay().

3)然后,我将向图表添加侦听器,以便在发生任何更改(内容,宽度,高度...)时得到通知,并使文本区域的位置始终显示在相同的值附近.

3) I would then add listeners to the chart in order to be notified of any changes (content, width, height...) and adapt the text area's position to always display near the same value.

调整大小/简单明了,请告诉我们是否给您带来麻烦.

Resizing / straight-forward, please let us know if that gives you any trouble.

根据要求,下面是一些示例代码.下面的代码提供了一个简化的示例,允许您通过按住Shift键并单击将文本节点(我称其为注释)添加到图表中.拖动或编辑批注很简单,但是我想保持示例简洁.

As requested, here is some sample code. The code below provides a simplified example, allowing you to add text nodes (I'll call them annotations) to the chart by shift-clicking. Dragging or editing the annotations is straight-forward, but I wanted to keep the example concise.

让我们先定义一个样本图:

Lets start by defining a sample chart:

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

到目前为止,我还没有幻想,我只是用一些随机的模拟数据创建了一个面积图.

Nothing fancy so far, I just create an area chart with some random mock data.

对于文本节点(或注解),我创建了一个简单的POJO,其中包含带注释的X/Y值(非显示位置)并采用要渲染的自定义节点:

For the text nodes (or annotations), I've created a simple POJO containing the annotated X/Y value (not display position) and taking a custom node to be rendered:

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,尽管那样也可以.此外,这种实现方法并不是最有效的方法,但我想使示例保持简单.

The interesting stuff happens within what I'll refer to as the overlay: a transparent panel which will be placed above the chart. Note that I did not, as originally advised, choose AnchorPane, though that would have worked as well. Furthermore, this implementation is not exactly the most efficient approach, but I wanted to keep the example 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));
    }
}

棘手的部分是视图和显示之间的坐标转换.要获取给定值的显示位置,可以调用Axis#getDisplayPosition(...),但是返回的坐标将在轴的坐标空间中.调用Axis#localToParent会将其转换为图表的坐标空间.通常,您希望能够只使用这些坐标,但是图表的默认填充为5像素,由于某些原因,该填充将无法正确转换.

The tricky part is the coordinate translation between view and display. To get the display position for a given value, you can invoke Axis#getDisplayPosition(...), yet the coordinate returned will be in the axis' coordinate space. The call to Axis#localToParent translates this into the chart's coordinate space. Normally, you'd expect to be able to just use those coordinates, but the chart has a default padding of 5 pixels that for some reason will not be translated correctly.

这是一个将所有内容组合在一起的小型测试应用程序:

Here's a small test app putting it all together:

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

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

Now that you have the overlay code + the idea behind translating coordinates, dragging the nodes should be simple as well. When an annotation's node is dragged, get its display position, add the drag delta, convert it to value and apply it to the annotation instance.

希望这会使事情变得更清楚.

Hope this makes things a bit more clear.

这篇关于JavaFx 2.x:如何在图表上编写文本?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆