您看到的问题是因为FXMLLoader 实例化类的默认机制是调用无参数构造函数。您的 ScatterQuadrantChart 没有无参数构造函数,因此是 NoSuchMethodError。
在 Java 8 之前,解决此问题的唯一方法是为您的类创建一个构建器类,如您链接的帖子中所示。 JavaFX 8 引入了(但未能记录)一种机制,使用 @NamedArg 注释为构造函数参数指定值,FXMLLoader 可以识别这些值。
因此,在 Java 8 中,您可以修改您的 ScatterQuadrantChart:
public class ScatterQuadrantChart<X,Y> extends ScatterChart<X,Y> {
public ScatterQuadrantChart(@NamedArg("xAxis")Axis<X> xAxis,
@NamedArg("yAxis)Axis<Y> yAxis) {
super(xAxis, yAxis);
}
}
然后你的 FXML 看起来像
<ScatterQuadrantChart>
<xAxis>
<NumberAxis ... />
</xAxis>
<yAxis>
<NumberAxis ... />
</yAxis>
</ScatterQuadrantChart>
我不知道 SceneBuilder 是否或如何与之交互,但 FXML 会起作用。
至于实现,您需要在图中添加一些节点来表示您的象限。我可能只会为这些使用普通区域。在构造函数中创建它们并调用getPlotChildren().add(...) 来添加它们。然后在layoutPlotChildren() 方法中,首先调用超类方法(将布置散点图节点),然后调整象限的大小和位置。您可以使用getXAxis().getDisplayPosition(...) 从实际的分频器值中找出位置。
在现实生活中,您应该将样式类添加到象限,以便您可以使用 css 等在外部设置样式,但一个非常基本的实现可能看起来像
import javafx.beans.NamedArg;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.scene.chart.Axis;
import javafx.scene.chart.ScatterChart;
import javafx.scene.layout.Region;
public class ScatterQuadrantChart<X,Y> extends ScatterChart<X,Y> {
private final Property<X> xQuadrantDivider = new SimpleObjectProperty<>();
private final Property<Y> yQuadrantDivider = new SimpleObjectProperty<>();
private final Region nwQuad ;
private final Region neQuad ;
private final Region swQuad ;
private final Region seQuad ;
public ScatterQuadrantChart(@NamedArg("xAxis") Axis<X> xAxis,
@NamedArg("yAxis") Axis<Y> yAxis) {
super(xAxis, yAxis);
nwQuad = new Region();
neQuad = new Region();
swQuad = new Region();
seQuad = new Region();
nwQuad.setStyle("-fx-background-color: lightsalmon ;");
neQuad.setStyle("-fx-background-color: antiquewhite ;");
swQuad.setStyle("-fx-background-color: aqua ;");
seQuad.setStyle("-fx-background-color: lightskyblue ;");
getPlotChildren().addAll(nwQuad, neQuad, swQuad, seQuad);
ChangeListener<Object> quadListener = (obs, oldValue, newValue) -> layoutPlotChildren();
xQuadrantDivider.addListener(quadListener);
yQuadrantDivider.addListener(quadListener);
}
@Override
public void layoutPlotChildren() {
super.layoutPlotChildren();
X x = xQuadrantDivider.getValue();
Y y = yQuadrantDivider.getValue();
if (x != null && y != null) {
Axis<X> xAxis = getXAxis();
Axis<Y> yAxis = getYAxis();
double xPixels = xAxis.getDisplayPosition(x);
double yPixels = yAxis.getDisplayPosition(y);
double totalWidth = xAxis.getWidth();
double totalHeight = yAxis.getHeight();
nwQuad.resizeRelocate(0, 0, xPixels, yPixels);
swQuad.resizeRelocate(0, yPixels, xPixels, totalHeight - yPixels);
neQuad.resizeRelocate(xPixels, 0, totalWidth - xPixels, yPixels);
seQuad.resizeRelocate(xPixels, yPixels, totalWidth - xPixels, totalHeight - yPixels);
}
}
public final Property<X> xQuadrantDividerProperty() {
return this.xQuadrantDivider;
}
public final X getXQuadrantDivider() {
return this.xQuadrantDividerProperty().getValue();
}
public final void setXQuadrantDivider(final X xQuadrantDivider) {
this.xQuadrantDividerProperty().setValue(xQuadrantDivider);
}
public final Property<Y> yQuadrantDividerProperty() {
return this.yQuadrantDivider;
}
public final Y getYQuadrantDivider() {
return this.yQuadrantDividerProperty().getValue();
}
public final void setYQuadrantDivider(final Y yQuadrantDivider) {
this.yQuadrantDividerProperty().setValue(yQuadrantDivider);
}
}
测试代码:
import java.util.Random;
import java.util.stream.Stream;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class ScatterQuadrantChartTest extends Application {
@Override
public void start(Stage primaryStage) {
final Random rng = new Random();
ScatterQuadrantChart<Number, Number> chart = new ScatterQuadrantChart<>(new NumberAxis(), new NumberAxis());
Series<Number, Number> series = new Series<>();
for (int i=0; i<20; i++) {
series.getData().add(new Data<>(rng.nextDouble() * 100, rng.nextDouble() * 100));
}
chart.getData().add(series);
chart.setXQuadrantDivider(50);
chart.setYQuadrantDivider(50);
BorderPane root = new BorderPane(chart);
Scene scene = new Scene(root, 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}