【问题标题】:Adding a line in a JavaFX chart在 JavaFX 图表中添加一条线
【发布时间】:2014-08-05 15:25:56
【问题描述】:

我在 JavaFX 中的定义位置添加一行时遇到问题。该行必须是一条恒定线,如下所示:How to add a value marker to JavaFX chart?

我的问题是,我的布局定义有点复杂。看看:

重要的部分是顶部的部分。我想在 y=60 线上有这条线。 RadioBoxes 的左边部分是一个 VBox。带有 (Scatter-)Chart 的部分是 StackPane (因为我希望它填充其余的宽度)。这个 StackPane 里面是图表和一个组。 Group 的唯一子节点是 line。

我认为问题在于,StackPane 以图表上方的线条将 Group 居中。但我无法得到布局的组合 1.拉伸图表 2.在图表上方设置线 3. 不居中

我尝试了很多组合,但我无法按照我想要的方式得到它。有人有想法吗?!

【问题讨论】:

  • 嗯,我在散点图中“添加”一条线的方式实际上是将折线图和散点图添加到堆叠窗格中。可能不是你最好的方法......
  • 我也试过这种方式。但由于标题、图例、轴的标题等原因,我无法完成...

标签: java javafx-2 java-8 javafx-8


【解决方案1】:

为什么不将该线添加到图表中?我有显示 100、99、95 和 50 个百分位数的图表,我解决此问题的方法是在每个百分位数的正确 y 值处添加一条线。

为此,只需添加一条包含两个点的线,一个位于 y=60 x=70(最左边的 x 轴值),另一个位于 y=60 和 x=120(最右边x 轴值)。

这样做的好处是您不必自己手动对齐水平线,缺点是这条水平线也将成为图例的一部分。但是,鉴于您没有应该可以的图例。

如果您决定添加图例,请务必正确命名水平线,l

【讨论】:

  • 感谢您的回答。如果我做对了,那么您的操作方式就可以在 LineChart 中使用。我的图表是散点图。如果您的示例也适用于 ScatterChart,您能否提供一段代码?
  • 您应该能够向任何图表添加线条。我在面积图中使用这些百分位线,但是 AFAIK,您可以将系列数据添加到任何图表类型。
  • 我认为这在 AreaChart 中是可能的,因为 Series Data 的点是相连的。但是,如果我将系列数据添加到 ScatterChart,它们之间就没有联系。但也许我不明白你的答案......你能提供一些代码或图表的截图吗?
  • 我自己没有在散点图中试过这个,所以你的里程可能会有所不同
【解决方案2】:

不幸的是,XYCharts 不支持 ValueMarkers(这可能是层次结构中应该完成的位置)(错误)使用具有恒定值的数据是一种黑客行为,在某些情况下可能(或不)可以接受/可能.

更简洁的出路是支持此类标记的自定义图表。菲自定义散点图,如:

public class ScatterXChart<X, Y> extends ScatterChart<X, Y> {

    // data defining horizontal markers, xValues are ignored
    private ObservableList<Data<X, Y>> horizontalMarkers;

    public ScatterXChart(Axis<X> xAxis, Axis<Y> yAxis) {
        super(xAxis, yAxis);
        // a list that notifies on change of the yValue property
        horizontalMarkers = FXCollections.observableArrayList(d -> new Observable[] {d.YValueProperty()});
        // listen to list changes and re-plot
        horizontalMarkers.addListener((InvalidationListener)observable -> layoutPlotChildren());
    }

    /**
     * Add horizontal value marker. The marker's Y value is used to plot a
     * horizontal line across the plot area, its X value is ignored.
     * 
     * @param marker must not be null.
     */
    public void addHorizontalValueMarker(Data<X, Y> marker) {
        Objects.requireNonNull(marker, "the marker must not be null");
        if (horizontalMarkers.contains(marker)) return;
        Line line = new Line();
        marker.setNode(line );
        getPlotChildren().add(line);
        horizontalMarkers.add(marker);
    }

    /**
     * Remove horizontal value marker.
     * 
     * @param horizontalMarker must not be null
     */
    public void removeHorizontalValueMarker(Data<X, Y> marker) {
        Objects.requireNonNull(marker, "the marker must not be null");
        if (marker.getNode() != null) {
            getPlotChildren().remove(marker.getNode());
            marker.setNode(null);
        }
        horizontalMarkers.remove(marker);
    }

    /**
     * Overridden to layout the value markers.
     */
    @Override
    protected void layoutPlotChildren() {
        super.layoutPlotChildren();
        for (Data<X, Y> horizontalMarker : horizontalMarkers) {
            double lower = ((ValueAxis) getXAxis()).getLowerBound();
            X lowerX = getXAxis().toRealValue(lower);
            double upper = ((ValueAxis) getXAxis()).getUpperBound();
            X upperX = getXAxis().toRealValue(upper);
            Line line = (Line) horizontalMarker.getNode();
            line.setStartX(getXAxis().getDisplayPosition(lowerX));
            line.setEndX(getXAxis().getDisplayPosition(upperX));
            line.setStartY(getYAxis().getDisplayPosition(horizontalMarker.getYValue()));
            line.setEndY(line.getStartY());

        }
    }
}

一个 sn-p 测试图表(f.i. 插入 oracle 教程中的在线示例):

// instantiate chart
NumberAxis xAxis = new NumberAxis(0, 10, 1);
NumberAxis yAxis = new NumberAxis(-100, 500, 100);        
ScatterXChart<Number,Number> sc = new ScatterXChart<>(xAxis,yAxis);
// .. fill with some data
...
// ui to add/change/remove a value marker
Data<Number, Number> horizontalMarker = new Data<>(0, 110);
Button add = new Button("Add Marker");  
add.setOnAction(e -> sc.addHorizontalValueMarker(horizontalMarker));
Slider move = new Slider(yAxis.getLowerBound(), yAxis.getUpperBound(), 0);
move.setShowTickLabels(true);
move.valueProperty().bindBidirectional(horizontalMarker.YValueProperty());
Button remove = new Button("Remove Marker");
remove.setOnAction(e -> sc.removeHorizontalValueMarker(horizontalMarker));

附录:

虽然我不推荐 approach in the related question(将标记线添加到图表的父级并在外部管理其位置/长度),但 可以在可调整大小的容器中使用它。使其发挥作用的关键因素:

  • 聆听图表的大小/位置变化并适当更新线条
  • 将标记的托管属性设置为 false

在代码中(updateShift 是原版中计算 yShift/lineX 的部分):

Pane pane = new StackPane(chart);
chart.widthProperty().addListener(o -> updateShift(chart));
chart.heightProperty().addListener(o -> updateShift(chart));
valueMarker.setManaged(false);
pane.getChildren().add(valueMarker);

【讨论】:

  • 漂亮干净的面向对象解决方案。但是,我发现了一个很大的性能问题。每次更新标记时,都会重新绘制整个图表。因此,如果您有一个大图表并且只想更改标记位置(例如,光标在图表上运行),则标记更新可能需要很长时间(在我的情况下将近一秒钟)。这个特殊问题有没有更有效的解决方案?也许是 StackPane?
  • @StefanEndrullis 好点(不关心性能:-) - StackPane 提高了性能吗?不过想知道,为什么要频繁更新标记?
  • 我使用标记来指示视频中与图表值相关的当前位置。播放视频时,必须更新光标(标记)。但是,我通过使用 StackPane 并将图表的缓存属性设置为 true 来解决性能问题。见stackoverflow.com/questions/30482139/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-08-09
  • 1970-01-01
  • 1970-01-01
  • 2022-01-07
  • 1970-01-01
相关资源
最近更新 更多