【问题标题】:Quadrant Scatter Chart in JavaFX?JavaFX中的象限散点图?
【发布时间】:2015-04-30 14:09:50
【问题描述】:

感谢您阅读我的问题。

我目前正在使用 JavaFX-8、SceneBuilder 和 Eclipse。

我想做一个有四个象限的散点图,有两个固定的数字轴(数据位置不相关,我只需要在每个象限上显示点......只关心一个点在哪个象限) .每个象限都必须有一个特定颜色的背景。

我找到了this question,因此我尝试扩展 ScatterChart 以覆盖方法 layoutPlotChildren()。我尝试了一个最小实现,看看它是否会与我的 FXML 一起运行(我确实将新组件导入到 FXML)。这是我的最低实现:

public class ScatterQuadrantChart<X,Y> extends ScatterChart<X,Y> {
  public ScatterQuadrantChart(Axis<X> xAxis, Axis<Y> yAxis) {
      super(xAxis, yAxis);
} }

然后,我得到 NotSuchMethodError init 错误。我发现了一个类似的错误,但来自扩展 LineChart here 的人,但我不太确定我需要在自己的课堂上做什么。

我尝试添加一个无参数构造函数,但我需要调用 super 和 cant,因为我也无法调用“getXAxis()”方法。 我应该在这里做什么?

另外,剩下的另一个问题是,一旦我解决了这个问题,layoutPlotChildren() 方法应该做什么?

感谢阅读。

【问题讨论】:

    标签: java charts javafx-8 scatter


    【解决方案1】:

    您看到的问题是因为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);
        }
    
    }
    

    【讨论】:

    • 谢谢@James_D! FXML 工作正常,加载完美,没有错误。我现在遇到的问题是第二个问题:“如何实现“layoutPlotChildren”来绘制彩色背景?
    • 使用示例实现更新了答案。
    • 再次感谢@James_D!我尝试了代码,但象限不可见。我在设置样式类之前按原样尝试了代码,并删除了更改点形状的 CSS 以避免出现问题。然而,这些区域是不可见的。我尝试替换 HEX 代码的颜色,但它们仍然是透明的。
    • 确保设置了 xQuadrantDivideryQuadrantDivider 值。我更新了测试代码。
    • 它就像一个魅力!会调整它的风格,谢谢! :D
    最近更新 更多