如何使用JavaFX LineChart添加两条垂直线
我的桌面应用程序有一个启动和停止测试的计时器。在图表上,我想创建两条垂直线来指示开始和停止时间。“使用JavaFX向StackPane添加垂直线”不适用于我的情况,因为我不希望这些线保持在相同的位置,这些线应该在绘图中绘制,而不是在布局中绘制。当用户在图表上缩放时,这些垂直线应与用户缩放的位置相对应。谢谢你给我小费 以下是我创建图表的代码:如何使用JavaFX LineChart添加两条垂直线,java,javafx,Java,Javafx,我的桌面应用程序有一个启动和停止测试的计时器。在图表上,我想创建两条垂直线来指示开始和停止时间。“使用JavaFX向StackPane添加垂直线”不适用于我的情况,因为我不希望这些线保持在相同的位置,这些线应该在绘图中绘制,而不是在布局中绘制。当用户在图表上缩放时,这些垂直线应与用户缩放的位置相对应。谢谢你给我小费 以下是我创建图表的代码: LineChart<Number, Number> chart = new LineChart<Number, Number>(xA
LineChart<Number, Number> chart = new LineChart<Number, Number>(xAxis, yAxis, dataset);
xAxis.setLabel("time(s)");
yAxis.setLabel("deg/s");
LineChart chart=新的线形图(xAxis、yAxis、dataset);
xAxis.setLabel(“时间”);
yAxis.设置标签(“度/秒”);
我不确定您指的是哪个问题。基本上,您可以使用一些绑定魔法来完成这一切:诀窍是使用xAxis.getDisplayPosition(…)
将线的x
值映射到相对于xAxis
的坐标。然后,您需要将该坐标转换为相对于容纳图表和线条的容器的坐标:最简单的方法是首先使用xAxis.localToScene(…)
转换为场景
坐标,然后使用container.sceneToLocal(…)
转换为容器的坐标
然后,您只需要让绑定观察它需要观察的所有更改:这些将是轴的(数字)边界,图表的(图形)边界,如果直线要移动,则是表示其x值的属性
这是一个SSCCE。在本例中,我使用滑块
移动直线。我还使线仅在范围内可见,并绑定y坐标,使其跨越yAxis
import java.util.Random;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.value.ObservableDoubleValue;
import javafx.geometry.Insets;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
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.Slider;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
public class LineChartWithVerticalLine extends Application {
@Override
public void start(Stage primaryStage) {
NumberAxis xAxis = new NumberAxis();
NumberAxis yAxis = new NumberAxis();
LineChart<Number, Number> chart = new LineChart<>(xAxis, yAxis);
chart.getData().add(createSeries());
Pane chartHolder = new Pane();
chartHolder.getChildren().add(chart);
DoubleProperty lineX = new SimpleDoubleProperty();
Slider slider = new Slider();
slider.minProperty().bind(xAxis.lowerBoundProperty());
slider.maxProperty().bind(xAxis.upperBoundProperty());
slider.setPadding(new Insets(20));
lineX.bind(slider.valueProperty());
chartHolder.getChildren().add(createVerticalLine(chart, xAxis, yAxis, chartHolder, lineX));
BorderPane root = new BorderPane(chartHolder, null, null, slider, null);
Scene scene = new Scene(root, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
private Line createVerticalLine(XYChart<Number, Number> chart, NumberAxis xAxis, NumberAxis yAxis, Pane container, ObservableDoubleValue x) {
Line line = new Line();
line.startXProperty().bind(Bindings.createDoubleBinding(() -> {
double xInAxis = xAxis.getDisplayPosition(x.get());
Point2D pointInScene = xAxis.localToScene(xInAxis, 0);
double xInContainer = container.sceneToLocal(pointInScene).getX();
return xInContainer ;
},
x,
chart.boundsInParentProperty(),
xAxis.lowerBoundProperty(),
xAxis.upperBoundProperty()));
line.endXProperty().bind(line.startXProperty());
line.startYProperty().bind(Bindings.createDoubleBinding(() -> {
double lowerY = yAxis.getDisplayPosition(yAxis.getLowerBound());
Point2D pointInScene = yAxis.localToScene(0, lowerY);
double yInContainer = container.sceneToLocal(pointInScene).getY();
return yInContainer ;
},
chart.boundsInParentProperty(),
yAxis.lowerBoundProperty()));
line.endYProperty().bind(Bindings.createDoubleBinding(() -> {
double upperY = yAxis.getDisplayPosition(yAxis.getUpperBound());
Point2D pointInScene = yAxis.localToScene(0, upperY);
double yInContainer = container.sceneToLocal(pointInScene).getY();
return yInContainer ;
},
chart.boundsInParentProperty(),
yAxis.lowerBoundProperty()));
line.visibleProperty().bind(
Bindings.lessThan(x, xAxis.lowerBoundProperty())
.and(Bindings.greaterThan(x, xAxis.upperBoundProperty())).not());
return line ;
}
private Series<Number, Number> createSeries() {
Series<Number, Number> series = new Series<>();
series.setName("Data");
Random rng = new Random();
for (int i=0; i<=20; i++) {
series.getData().add(new Data<>(i, rng.nextInt(101)));
}
return series ;
}
public static void main(String[] args) {
launch(args);
}
}
import java.util.Random;
导入javafx.application.application;
导入javafx.beans.binding.Bindings;
导入javafx.beans.property.DoubleProperty;
导入javafx.beans.property.SimpleDoubleProperty;
导入javafx.beans.value.observedOutleValue;
导入javafx.geometry.Insets;
导入javafx.geometry.Point2D;
导入javafx.scene.scene;
导入javafx.scene.chart.LineChart;
导入javafx.scene.chart.NumberAxis;
导入javafx.scene.chart.XYChart;
导入javafx.scene.chart.XYChart.Data;
导入javafx.scene.chart.XYChart.Series;
导入javafx.scene.control.Slider;
导入javafx.scene.layout.BorderPane;
导入javafx.scene.layout.Pane;
导入javafx.scene.shape.Line;
导入javafx.stage.stage;
使用Verticalline扩展应用程序的公共类LineChart{
@凌驾
公共无效开始(阶段primaryStage){
NumberAxis xAxis=新NumberAxis();
NumberAxis yAxis=新的NumberAxis();
线形图=新线形图(xAxis,yAxis);
chart.getData().add(createSeries());
窗格图表持有者=新窗格();
chartHolder.getChildren().add(图表);
DoubleProperty lineX=新的SimpleDoubleProperty();
滑块=新滑块();
slider.minProperty().bind(xAxis.lowerBoundProperty());
slider.maxProperty().bind(xAxis.upperBoundProperty());
滑块。设置填充(新插图(20));
bind(slider.valueProperty());
getChildren().add(createVerticalLine(chart,xAxis,yAxis,chartHolder,lineX));
BorderPane根=新的BorderPane(图表持有者,null,null,滑块,null);
场景=新场景(根,800600);
初级阶段。场景(场景);
primaryStage.show();
}
专用线createVerticalLine(XYChart图表、NumberAxis-xAxis、NumberAxis-yAxis、窗格容器、观察到的超左值x){
行=新行();
line.startXProperty().bind(Bindings.createDoubleBinding(()->{
double xInAxis=xAxis.getDisplayPosition(x.get());
Point2D pointInScene=xAxis.localToScene(xInAxis,0);
double xInContainer=container.sceneToLocal(pointInScene.getX();
返回集装箱;
},
x,,
chart.boundsInParentProperty(),
xAxis.lowerBoundProperty(),
xAxis.upperBoundProperty());
line.endXProperty().bind(line.startXProperty());
line.startYProperty().bind(Bindings.createDoubleBinding(()->{
double-lowerY=yAxis.getDisplayPosition(yAxis.getLowerBound());
Point2D pointInScene=yAxis.localToScene(0,lowerY);
double-yincainer=container.sceneToLocal(pointInScene.getY();
返回集装箱;
},
chart.boundsInParentProperty(),
yAxis.lowerBoundProperty());
line.endYProperty().bind(Bindings.createDoubleBinding(()->{
double-upperY=yAxis.getDisplayPosition(yAxis.getUpperBound());
Point2D pointInScene=yAxis.localToScene(0,大写);
double-yincainer=container.sceneToLocal(pointInScene.getY();
返回集装箱;
},
chart.boundsInParentProperty(),
yAxis.lowerBoundProperty());
line.visibleProperty().bind(
Bindings.lessThan(x,xAxis.lowerBoundProperty())
.和(Bindings.greaterThan(x,xAxis.upperBoundProperty()).not());
回流线;
}
私有系列createSeries(){
系列=新系列();
series.setName(“数据”);
随机rng=新随机();
对于(int i=0;i您需要扩展LineChart类并重写layoutPlotChildren方法以显示标记
Kleopatra做了一个测试。下面的代码是折线图的修改版本,具有垂直和水平标记:
public class LineChartSample extends Application {
@Override public void start(Stage stage) {
final NumberAxis xAxis = new NumberAxis();
final NumberAxis yAxis = new NumberAxis();
xAxis.setLabel("Number of Month");
final LineChartWithMarkers<Number,Number> lineChart = new LineChartWithMarkers<Number,Number>(xAxis,yAxis);
XYChart.Series series = new XYChart.Series();
series.setName("My portfolio");
series.getData().add(new XYChart.Data(1, 23));
series.getData().add(new XYChart.Data(2, 14));
series.getData().add(new XYChart.Data(3, 15));
series.getData().add(new XYChart.Data(4, 24));
series.getData().add(new XYChart.Data(5, 34));
series.getData().add(new XYChart.Data(6, 36));
series.getData().add(new XYChart.Data(7, 22));
series.getData().add(new XYChart.Data(8, 45));
series.getData().add(new XYChart.Data(9, 43));
series.getData().add(new XYChart.Data(10, 17));
series.getData().add(new XYChart.Data(11, 29));
series.getData().add(new XYChart.Data(12, 25));
lineChart.getData().add(series);
Data<Number, Number> horizontalMarker = new Data<>(0, 25);
lineChart.addHorizontalValueMarker(horizontalMarker);
Data<Number, Number> verticalMarker = new Data<>(10, 0);
lineChart.addVerticalValueMarker(verticalMarker);
Slider horizontalMarkerSlider = new Slider(yAxis.getLowerBound(), yAxis.getUpperBound(), 0);
horizontalMarkerSlider.setOrientation(Orientation.VERTICAL);
horizontalMarkerSlider.setShowTickLabels(true);
horizontalMarkerSlider.valueProperty().bindBidirectional(horizontalMarker.YValueProperty());
horizontalMarkerSlider.minProperty().bind(yAxis.lowerBoundProperty());
horizontalMarkerSlider.maxProperty().bind(yAxis.upperBoundProperty());
Slider verticalMarkerSlider = new Slider(xAxis.getLowerBound(), xAxis.getUpperBound(), 0);
verticalMarkerSlider.setOrientation(Orientation.HORIZONTAL);
verticalMarkerSlider.setShowTickLabels(true);
verticalMarkerSlider.valueProperty().bindBidirectional(verticalMarker.XValueProperty());
verticalMarkerSlider.minProperty().bind(xAxis.lowerBoundProperty());
verticalMarkerSlider.maxProperty().bind(xAxis.upperBoundProperty());
BorderPane borderPane = new BorderPane();
borderPane.setCenter( lineChart);
borderPane.setTop(verticalMarkerSlider);
borderPane.setRight(horizontalMarkerSlider);
Scene scene = new Scene(borderPane,800,600);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
private class LineChartWithMarkers<X,Y> extends LineChart {
private ObservableList<Data<X, Y>> horizontalMarkers;
private ObservableList<Data<X, Y>> verticalMarkers;
public LineChartWithMarkers(Axis<X> xAxis, Axis<Y> yAxis) {
super(xAxis, yAxis);
horizontalMarkers = FXCollections.observableArrayList(data -> new Observable[] {data.YValueProperty()});
horizontalMarkers.addListener((InvalidationListener)observable -> layoutPlotChildren());
verticalMarkers = FXCollections.observableArrayList(data -> new Observable[] {data.XValueProperty()});
verticalMarkers.addListener((InvalidationListener)observable -> layoutPlotChildren());
}
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);
}
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);
}
public void addVerticalValueMarker(Data<X, Y> marker) {
Objects.requireNonNull(marker, "the marker must not be null");
if (verticalMarkers.contains(marker)) return;
Line line = new Line();
marker.setNode(line );
getPlotChildren().add(line);
verticalMarkers.add(marker);
}
public void removeVerticalValueMarker(Data<X, Y> marker) {
Objects.requireNonNull(marker, "the marker must not be null");
if (marker.getNode() != null) {
getPlotChildren().remove(marker.getNode());
marker.setNode(null);
}
verticalMarkers.remove(marker);
}
@Override
protected void layoutPlotChildren() {
super.layoutPlotChildren();
for (Data<X, Y> horizontalMarker : horizontalMarkers) {
Line line = (Line) horizontalMarker.getNode();
line.setStartX(0);
line.setEndX(getBoundsInLocal().getWidth());
line.setStartY(getYAxis().getDisplayPosition(horizontalMarker.getYValue()) + 0.5); // 0.5 for crispness
line.setEndY(line.getStartY());
line.toFront();
}
for (Data<X, Y> verticalMarker : verticalMarkers) {
Line line = (Line) verticalMarker.getNode();
line.setStartX(getXAxis().getDisplayPosition(verticalMarker.getXValue()) + 0.5); // 0.5 for crispness
line.setEndX(line.getStartX());
line.setStartY(0d);
line.setEndY(getBoundsInLocal().getHeight());
line.toFront();
}
}
}
}
公共类LineChartSample扩展应用程序{
@覆盖公共无效开始(阶段){
最终数字axis xAxis=新数字axis();
最终数字axis yAxis=新数字axis();
xAxis.setLabel(“月数”);
带标记的最终线形图
Data<Number, Number> verticalMarker = new Data<>(10, 0);
lineChart.addVerticalValueMarker(verticalMarker);
private ObservableList<Data<X, X>> verticalRangeMarkers;
public LineChartWithMarkers(Axis<X> xAxis, Axis<Y> yAxis) {
...
verticalRangeMarkers = FXCollections.observableArrayList(data -> new Observable[] {data.XValueProperty()});
verticalRangeMarkers = FXCollections.observableArrayList(data -> new Observable[] {data.YValueProperty()}); // 2nd type of the range is X type as well
verticalRangeMarkers.addListener((InvalidationListener)observable -> layoutPlotChildren());
}
public void addVerticalRangeMarker(Data<X, X> marker) {
Objects.requireNonNull(marker, "the marker must not be null");
if (verticalRangeMarkers.contains(marker)) return;
Rectangle rectangle = new Rectangle(0,0,0,0);
rectangle.setStroke(Color.TRANSPARENT);
rectangle.setFill(Color.BLUE.deriveColor(1, 1, 1, 0.2));
marker.setNode( rectangle);
getPlotChildren().add(rectangle);
verticalRangeMarkers.add(marker);
}
public void removeVerticalRangeMarker(Data<X, X> marker) {
Objects.requireNonNull(marker, "the marker must not be null");
if (marker.getNode() != null) {
getPlotChildren().remove(marker.getNode());
marker.setNode(null);
}
verticalRangeMarkers.remove(marker);
}
protected void layoutPlotChildren() {
...
for (Data<X, X> verticalRangeMarker : verticalRangeMarkers) {
Rectangle rectangle = (Rectangle) verticalRangeMarker.getNode();
rectangle.setX( getXAxis().getDisplayPosition(verticalRangeMarker.getXValue()) + 0.5); // 0.5 for crispness
rectangle.setWidth( getXAxis().getDisplayPosition(verticalRangeMarker.getYValue()) - getXAxis().getDisplayPosition(verticalRangeMarker.getXValue()));
rectangle.setY(0d);
rectangle.setHeight(getBoundsInLocal().getHeight());
rectangle.toBack();
}
}
Data<Number, Number> verticalRangeMarker = new Data<>(4, 10);
lineChart.addVerticalRangeMarker(verticalRangeMarker);
/**
* The ChartView.
*/
public class ChartController {
private ChartViewModel chartViewModel;
private CustomLineChart<Number, Number> lineChart;
private NumberAxis xAxis;
private NumberAxis yAxis;
private XYChart.Series<Number, Number> series;
private List<Integer> data;
private boolean mouseDragged;
private double initialNumberStart;
private double initialNumberEnd;
@FXML
private VBox mainContainer;
@FXML
private HBox chartContainer;
/**
* The constructor.
*/
public ChartController() {
chartViewModel = new ChartViewModel();
mouseDragged = false;
}
/**
* The initialize method.
*/
public void initialize() {
createChart();
handleEvents();
}
/**
* Handles the events.
*/
private void handleEvents() {
lineChart.setOnMousePressed(pressed -> {
int minSize = 1;
// Get coordinate from the scene and transform to coordinates from the chart axis
Point2D firstSceneCoordinate = new Point2D(pressed.getSceneX(), pressed.getSceneY());
double firstX = xAxis.sceneToLocal(firstSceneCoordinate).getX();
lineChart.setOnMouseDragged(dragged -> {
mouseDragged = true;
Point2D draggedSceneCoordinate = new Point2D(dragged.getSceneX(), dragged.getSceneY());
double draggedX = xAxis.sceneToLocal(draggedSceneCoordinate).getX();
List<Double> numbers = filterSeries(firstX, draggedX);
int size = numbers.size();
double numberStart = size > minSize ? numbers.get(0) : initialNumberStart;
double numberEnd = numbers.size() > minSize ? numbers.get(size - 1) : initialNumberEnd;
if (size > minSize) {
lineChart.addVerticalRangeLines(new Data<>(numberStart, numberEnd));
}
lineChart.setOnMouseReleased(released -> {
if (mouseDragged) {
initialNumberStart = numberStart;
initialNumberEnd = numberEnd;
mouseDragged = false;
redrawChart();
}
});
});
});
}
/**
* Creates the charts.
*/
private void createChart() {
xAxis = new NumberAxis();
yAxis = new NumberAxis();
lineChart = new CustomLineChart<>(xAxis, yAxis);
data = chartViewModel.getData();
createSeries(data);
lineChart.getData().add(series);
initialNumberStart = 1;
initialNumberEnd = data.size() - 1;
chartContainer.getChildren().add(lineChart);
HBox.setHgrow(lineChart, Priority.ALWAYS);
}
/**
* Creates the series for the line chart.
*
* @param numbers The list of numbers for the series
*/
private void createSeries(List<Integer> numbers) {
int size = numbers.size();
series = new XYChart.Series<>();
series.setName("Example");
for (int i = 0; i < size; i++) {
series.getData().add(new XYChart.Data<Number, Number>(i, numbers.get(i)));
}
}
/**
* Filters the nodes and returns the node x positions within the firstX and lastX positions.
*
* @param firstX The first x position
* @param lastX The last x position
* @return The x positions for the nodes within the firstX and lastX
*/
private List<Double> filterSeries(double firstX, double lastX) {
List<Double> nodeXPositions = new ArrayList<>();
lineChart.getData().get(0).getData().forEach(node -> {
double nodeXPosition = lineChart.getXAxis().getDisplayPosition(node.getXValue());
if (nodeXPosition >= firstX && nodeXPosition <= lastX) {
nodeXPositions.add(Double.parseDouble(node.getXValue().toString()));
}
});
return nodeXPositions;
}
/**
* Updates the series for the chart.
*/
private void updateSeries() {
lineChart.getData().remove(0);
lineChart.getData().add(series);
}
/**
* Redraws the chart.
*/
private void redrawChart() {
List<Integer> filteredSeries = new ArrayList<>();
data.forEach(number -> {
if (number >= initialNumberStart && number <= initialNumberEnd) {
filteredSeries.add(number);
}
});
if (!filteredSeries.isEmpty()) {
createSeries(filteredSeries);
updateSeries();
lineChart.removeVerticalRangeLines();
}
}
/**
* Resets the series for the chart.
*
* @param event The event
*/
@FXML
void resetChart(ActionEvent event) {
createSeries(data);
updateSeries();
}
}