在 JavaFX 图表中添加一条线 [英] Adding a line in a JavaFX chart
问题描述
我在 JavaFX 中的定义位置添加行时遇到问题.该线必须是一条恒定线,如下所示:
最重要的部分是顶部.我想在 y=60 线上有这条线.RadioBoxes 的左侧部分是 VBox.带有 (Scatter-)Chart 的部分是 StackPane(因为我希望它填充宽度的其余部分).在这个 StackPane 里面是图表和一个组.Group 的唯一子元素是 line.
我认为问题在于,StackPane 将 Group 与图表上方的线居中.但我无法获得布局的组合1. 拉伸图表2. 在图表上方设置 Line3. 不居中
我尝试了很多组合,但我就是无法按照我想要的方式实现.有人有想法吗?!
不幸的是,XYCharts 中不支持 ValueMarkers(这可能是层次结构中应该完成的地方)(错误)使用具有常量值的数据是在某些情况下可能(或不)可接受/可能的 hack.
更简洁的方法是使用支持此类标记的自定义图表.F.i.自定义 ScatterChart,如:
公共类 ScatterXChart扩展 ScatterChart<X, Y>{//定义水平标记的数据,xValues 被忽略private ObservableList>水平标记;public ScatterXChart(Axis xAxis, Axis yAxis) {超级(xAxis,yAxis);//通知 yValue 属性更改的列表水平标记 = FXCollections.observableArrayList(d -> new Observable[] {d.YValueProperty()});//监听列表变化并重新绘制horizontalMarkers.addListener((InvalidationListener)observable -> layoutPlotChildren());}/*** 添加水平值标记.标记的 Y 值用于绘制* 穿过绘图区域的水平线,其 X 值被忽略.** @param 标记不能为空.*/public void addHorizontalValueMarker(数据标记){Objects.requireNonNull(marker, "标记不能为空");如果 (horizontalMarkers.contains(marker)) 返回;Line line = new Line();标记.setNode(行);getPlotChildren().add(line);水平标记.添加(标记);}/*** 删除水平值标记.** @param horizontalMarker 不能为空*/public void removeHorizontalValueMarker(数据标记){Objects.requireNonNull(marker, "标记不能为空");如果(marker.getNode()!= null){getPlotChildren().remove(marker.getNode());标记.setNode(null);}水平标记.删除(标记);}/*** 重写以布局值标记.*/@覆盖受保护的无效 layoutPlotChildren() {super.layoutPlotChildren();for (DatahorizontalMarker: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());}}}
测试图表的片段(例如插入到 oracle 教程中的在线示例中):
//实例化图表NumberAxis xAxis = new NumberAxis(0, 10, 1);NumberAxis yAxis = new NumberAxis(-100, 500, 100);ScatterXChartsc = 新的 ScatterXChart<>(xAxis,yAxis);//.. 填充一些数据...//ui 添加/更改/删除值标记数据<号码,号码>水平标记 = 新数据 (0, 110);Button add = new Button("添加标记");add.setOnAction(e -> sc.addHorizontalValueMarker(horizontalMarker));滑块移动 = 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));
附录:
虽然我不推荐相关问题中的方法(将标记线添加到图表的父级并管理它在外部的位置/长度) 可以在可调整大小的容器中使用它.使其发挥作用的关键事项:
- 聆听图表的大小/位置变化并适当更新线条
- 将标记的托管属性设置为 false
在代码中(updateShift 是计算 yShift/lineX 的原始部分):
Pane pane = new StackPane(chart);chart.widthProperty().addListener(o -> updateShift(chart));chart.heightProperty().addListener(o -> updateShift(chart));valueMarker.setManaged(false);窗格.getChildren().add(valueMarker);
I have problems with adding a line at a defined position in JavaFX. The line has to be line a constant line as shown here: How to add a value marker to JavaFX chart?
My problem is, that my layout definition is a bit more complicated. Have a look:
The important part is the one on the top. I want to have the line on the y=60 line. The left part with the RadioBoxes is a VBox. The part with the (Scatter-)Chart is a StackPane (because I want it to fill the rest of the width). Inside this StackPane is the chart and a Group. The only child of the Group is the line.
I think the problem is, that the StackPane centers the Group with the line above the chart. But I can't get the combination of layouts which 1. stretches the chart 2. sets the Line above the chart 3. doesn't center the line
I tried lots of combinations but I just can't get it the way I want. Does anybody have an idea?!
Unfortunately, ValueMarkers are not supported in XYCharts (that's probably the place in the hierarchy were it should be done) (Mis-)using data with constant value is a hack which might (or not) be acceptable/possible in some contexts.
A cleaner way out is a custom Chart that supports such markers. F.i. a custom ScatterChart like:
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());
}
}
}
A snippet testing the chart (f.i. insert into the online example in oracle's tutorial):
// 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));
Addendum:
While I wouldn't recommend the approach in the related question (adding the marker line to the chart's parent and managing its position/length externally) it is possible to use it in resizable containers. The crucial thingies to make it work:
- listen to chart's size/location changes and update the line appropriately
- set the marker's managed property to false
in code (updateShift is the part in the original that calculates the 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);
这篇关于在 JavaFX 图表中添加一条线的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!