单击鼠标左键然后移动鼠标,在此折线图上绘制一条线,并在轴上绘制。
我想在图表上绘制线条,使其不与x轴或y轴重叠。如何做到这一点?
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.geometry.Side;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.stage.Stage;
public class LinesEdit extends Application {
Path path;
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage stage) {
final CategoryAxis xAxis = new CategoryAxis();
final NumberAxis yAxis = new NumberAxis(1, 21, 0.1);
yAxis.setTickUnit(1);
yAxis.setPrefWidth(35);
yAxis.setMinorTickCount(10);
yAxis.setSide(Side.RIGHT);
yAxis.setTickLabelFormatter(new NumberAxis.DefaultFormatter(yAxis) {
@Override
public String toString(Number object) {
String label;
label = String.format("%7.2f", object.floatValue());
return label;
}
});
final LineChart<String, Number> lineChart = new LineChart<String, Number>(xAxis, yAxis);
lineChart.setCreateSymbols(false);
lineChart.setAlternativeRowFillVisible(false);
lineChart.setLegendVisible(false);
XYChart.Series series1 = new XYChart.Series();
series1.getData().add(new XYChart.Data("Jan", 1));
series1.getData().add(new XYChart.Data("Feb", 4));
series1.getData().add(new XYChart.Data("Mar", 2.5));
series1.getData().add(new XYChart.Data("Apr", 5));
series1.getData().add(new XYChart.Data("May", 6));
series1.getData().add(new XYChart.Data("Jun", 8));
series1.getData().add(new XYChart.Data("Jul", 12));
series1.getData().add(new XYChart.Data("Aug", 8));
series1.getData().add(new XYChart.Data("Sep", 11));
series1.getData().add(new XYChart.Data("Oct", 13));
series1.getData().add(new XYChart.Data("Nov", 10));
series1.getData().add(new XYChart.Data("Dec", 20));
BorderPane bp = new BorderPane();
bp.setCenter(lineChart);
Scene scene = new Scene(bp, 800, 600);
lineChart.setAnimated(false);
lineChart.getData().addAll(series1);
LinesEdit.MouseHandler mh = new LinesEdit.MouseHandler( bp );
bp.setOnMouseClicked( mh );
bp.setOnMouseMoved( mh );
stage.setScene(scene);
path = new Path();
path.setStrokeWidth(1);
path.setStroke(Color.BLACK);
scene.setOnMouseDragged(mh);
scene.setOnMousePressed(mh);
bp.getChildren().add(path);
stage.setScene(scene);
stage.show();
}
class MouseHandler implements EventHandler< MouseEvent > {
private boolean gotFirst = false;
private Line line;
private Pane pane;
private double x1, y1, x2, y2;
private LineHandler lineHandler;
public MouseHandler( Pane pane ) {
this.pane = pane;
lineHandler = new LineHandler(pane);
}
class LineHandler implements EventHandler< MouseEvent > {
double x, y;
Pane pane;
public LineHandler(Pane pane){
this.pane = pane;
}
@Override
public void handle( MouseEvent e ) {
Line l = (Line) e.getSource();
// remove line on right click
if( e.getEventType() == MouseEvent.MOUSE_PRESSED
&& e.isSecondaryButtonDown() ) {
pane.getChildren().remove( l );
} else if( e.getEventType() == MouseEvent.MOUSE_DRAGGED
&& e.isPrimaryButtonDown() ) {
double tx = e.getX();
double ty = e.getY();
double dx = tx - x;
double dy = ty - y;
l.setStartX( l.getStartX() + dx );
l.setStartY( l.getStartY() + dy );
l.setEndX( l.getEndX() + dx );
l.setEndY( l.getEndY() + dy );
x = tx;
y = ty;
} else if( e.getEventType() == MouseEvent.MOUSE_ENTERED ) {
// just to show that the line is selected
x = e.getX();
y = e.getY();
l.setStroke( Color.RED );
} else if( e.getEventType() == MouseEvent.MOUSE_EXITED ) {
l.setStroke( Color.BLACK );
}
// should not pass event to the parent
e.consume();
}
}
@Override
public void handle( MouseEvent event ) {
if( event.getEventType() == MouseEvent.MOUSE_CLICKED ) {
if( !gotFirst ) {
x1 = x2 = event.getX();
y1 = y2 = event.getY();
line = new Line( x1, y1, x2, y2 );
pane.getChildren().add( line );
gotFirst = true;
}
else {
line.setOnMouseEntered( lineHandler );
line.setOnMouseExited( lineHandler );
line.setOnMouseDragged( lineHandler );
line.setOnMousePressed( lineHandler );
// to consume the event
line.setOnMouseClicked( lineHandler );
line.setOnMouseReleased( lineHandler );
line = null;
gotFirst = false;
}
}
else {
if( line != null ) {
x2 = event.getX();
y2 = event.getY();
// update line
line.setEndX( x2 );
line.setEndY( y2 );
}
}
}
}
}
答案 0 :(得分:2)
首先要知道的是,许多JavaFX UI-Element图表都包含许多基础子元素。您想要绘制的部分实际上是Region
,它是Pane
的一部分LineChart
的一部分。我真的建议您使用ScenicView,因为它会准确显示您的场景图(包括内置UI组件)的外观。
回到你的问题:你的听众应该只适用于显示数据实际表示的Region
。 Region
正好在x和y轴的位置结束。以下代码将为您提供Region
并使其成为您的侦听器的目标:
//your previous code in start()...
Pane p = (Pane) lineChart.getChildrenUnmodifiable().get(1);
Region r = (Region) p.getChildren().get(0);
LinesEdit.MouseHandler mh = new LinesEdit.MouseHandler(r);
r.setOnMouseClicked(mh);
r.setOnMouseMoved(mh);
stage.setScene(scene);
path = new Path();
path.setStrokeWidth(1);
path.setStroke(Color.BLACK);
r.setOnMouseDragged(mh);
r.setOnMousePressed(mh);
bp.getChildren().add(path);
stage.setScene(scene);
//the following code.....
<德尔>
接下来的步骤是:
1.重写你的处理程序方法,以便它们接受一个`Region`对象作为参数
2.重写一下你的行设置代码。背景:您不能将对象添加到“Region”中,因为您无法访问子级的可写“List”。所以你必须将这些行放入保存`Region`的`Pane`对象中。因为每个JavaFX UI-Element都有自己的坐标系,所以你必须计算`Pane`和`Region`之间的偏移量,因为`Pane`有点大。如果不这样做,您的线将被略微高于鼠标指针。你可以通过调用`xyz.getBoundsInLocal()。getWidth()/ height()`来获得`Pane`和/或`Region`的宽度和高度。
德尔>
根据评论中的要求,我将展示解决此问题的一种方法。 在此图片中查看您的GUI。它显示了ScenicView中的所有图形元素。如您所见,Pane
大于内部Region
。对我们来说重要的是要知道JavaFX中所有坐标系的原点都从元素的左上角开始。在这种情况下,您必须向Pane
添加一行,但就Region
的边框而言。在我之前展示的代码片段中,我将所有侦听器添加到该区域,这意味着我们在区域的坐标系中获得里面的鼠标坐标。现在我们必须“转换”或更好地“转换”这些坐标(您希望设置线的起点或终点的点)到Pane
的坐标系(实际放置线的位置,读取)因为我们希望线条准确地从鼠标所在的位置开始。您可以调用一种方法来获取转换矩阵:r.getLocalToParentTransform()
。我们需要这个矩阵,因为我们必须得到应用于Region
的x和y平移的精确值(您可以看到Region
从{{1}移动了近似10个像素} x和y轴的原点)
我写了一个简单的方法来获取Panes
和Region
之间的x和y翻译:{{1}}。
Pane
方法的其余部分保持不变(但是我之前写的更改)。但必须修改getCoordDiff(Region r, Pane p)
和start()
的{{1}}方法。
handle()
您可以看到我将转换值添加到行起点和终点的部分。这是必需的,以便该行真正开始和结束鼠标所在的点。我移动了MouseHandler
时执行的代码,因为它阻止了用户放置行(因此它不跟随光标)。背景:您的光标现在总是(像素完美)在行的末尾,在您第一次放置它时没有“监听器”。丢失的侦听器会阻止LineHandler
- 点击转到public class LinesEdit extends Application
{
Path path;
public double[] getCoordDiff(Region r, Pane p)
{
//Acquires transformation matrix and returns x and y offset/translation from parent
double[] diffs =
{ r.getLocalToParentTransform().getTx(), r.getLocalToParentTransform().getTy() };
return diffs;
}
public static void main(String[] args)
{
launch(args);
}
@Override
public void start(Stage stage)
{
final CategoryAxis xAxis = new CategoryAxis();
final NumberAxis yAxis = new NumberAxis(1, 21, 0.1);
yAxis.setTickUnit(1);
yAxis.setPrefWidth(35);
yAxis.setMinorTickCount(10);
yAxis.setSide(Side.RIGHT);
yAxis.setTickLabelFormatter(new NumberAxis.DefaultFormatter(yAxis)
{
@Override
public String toString(Number object)
{
String label;
label = String.format("%7.2f", object.floatValue());
return label;
}
});
final LineChart<String, Number> lineChart = new LineChart<String, Number>(xAxis, yAxis);
lineChart.setCreateSymbols(false);
lineChart.setAlternativeRowFillVisible(false);
lineChart.setLegendVisible(false);
XYChart.Series series1 = new XYChart.Series();
series1.getData().add(new XYChart.Data("Jan", 1));
series1.getData().add(new XYChart.Data("Feb", 4));
series1.getData().add(new XYChart.Data("Mar", 2.5));
series1.getData().add(new XYChart.Data("Apr", 5));
series1.getData().add(new XYChart.Data("May", 6));
series1.getData().add(new XYChart.Data("Jun", 8));
series1.getData().add(new XYChart.Data("Jul", 12));
series1.getData().add(new XYChart.Data("Aug", 8));
series1.getData().add(new XYChart.Data("Sep", 11));
series1.getData().add(new XYChart.Data("Oct", 13));
series1.getData().add(new XYChart.Data("Nov", 10));
series1.getData().add(new XYChart.Data("Dec", 20));
BorderPane bp = new BorderPane();
bp.setCenter(lineChart);
Scene scene = new Scene(bp, 800, 600);
lineChart.setAnimated(false);
lineChart.getData().addAll(series1);
Pane p = (Pane) lineChart.getChildrenUnmodifiable().get(1);
Region r = (Region) p.getChildren().get(0);
LinesEdit.MouseHandler mh = new LinesEdit.MouseHandler(r);
r.setOnMouseClicked(mh);
r.setOnMouseMoved(mh);
stage.setScene(scene);
path = new Path();
path.setStrokeWidth(1);
path.setStroke(Color.BLACK);
r.setOnMouseDragged(mh);
r.setOnMousePressed(mh);
bp.getChildren().add(path);
stage.setScene(scene);
ScenicView.show(scene);
stage.show();
}
class MouseHandler implements EventHandler<MouseEvent>
{
private boolean gotFirst = false;
private Line line;
private Region reg;
private double x1, y1, x2, y2;
private LineHandler lineHandler;
public MouseHandler(Region reg)
{
this.reg = reg;
lineHandler = new LineHandler(reg);
}
class LineHandler implements EventHandler<MouseEvent>
{
double x, y;
Region reg;
public LineHandler(Region reg)
{
this.reg = reg;
}
@Override
public void handle(MouseEvent e)
{
Line l = (Line) e.getSource();
l.setStrokeWidth(3);
// remove line on right click
if (e.getEventType() == MouseEvent.MOUSE_PRESSED && e.isSecondaryButtonDown())
{
((Pane) reg.getParent()).getChildren().remove(l);
}
else if (e.getEventType() == MouseEvent.MOUSE_DRAGGED && e.isPrimaryButtonDown())
{
double tx = e.getX();
double ty = e.getY();
double dx = tx - x;
double dy = ty - y;
l.setStartX(l.getStartX() + dx);
l.setStartY(l.getStartY() + dy);
l.setEndX(l.getEndX() + dx);
l.setEndY(l.getEndY() + dy);
x = tx;
y = ty;
}
else if (e.getEventType() == MouseEvent.MOUSE_ENTERED)
{
// just to show that the line is selected
x = e.getX();
y = e.getY();
l.setStroke(Color.RED);
}
else if (e.getEventType() == MouseEvent.MOUSE_EXITED)
{
l.setStroke(Color.BLACK);
}
// should not pass event to the parent
e.consume();
}
}
@Override
public void handle(MouseEvent event)
{
if (event.getEventType() == MouseEvent.MOUSE_CLICKED)
{
double[] diff = getCoordDiff(reg, (Pane) reg.getParent());
if (!gotFirst)
{
//add translation to start/endcoordinates
x1 = x2 = event.getX() + diff[0];
y1 = y2 = event.getY() + diff[1];
line = new Line(x1, y1, x2, y2);
line.setStrokeWidth(3);
((Pane) reg.getParent()).getChildren().add(line);
gotFirst = true;
line.setOnMouseClicked(new EventHandler<Event>()
{
@Override
public void handle(Event arg0)
{
line.setOnMouseEntered(lineHandler);
line.setOnMouseExited(lineHandler);
line.setOnMouseDragged(lineHandler);
line.setOnMousePressed(lineHandler);
// to consume the event
line.setOnMouseClicked(lineHandler);
line.setOnMouseReleased(lineHandler);
line = null;
gotFirst = false;
}
});
}
}
else
{
if (line != null)
{
double[] diff = getCoordDiff(reg, (Pane) reg.getParent());
//add translation to end coordinates
x2 = event.getX() + diff[0];
y2 = event.getY() + diff[1];
// update line
line.setEndX(x2);
line.setEndY(y2);
}
}
}
}
}
。简而言之:“Click”事件现在总是在线本身完成,这就是为什么我们在最终放置线之前需要一个监听器。
剩余错误:无法在图表的黄线上放置一条线。那是因为点击事件没有在线上触发。我可能会在以后解决这个问题,或者你自己试试。