FXML自定义图表 [英] FXML Custom Chart

查看:111
本文介绍了FXML自定义图表的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在使用集成测试,效果很好.

I've been using custom fitting chart from Ensemble Test, which works great.

问题是我不能在FXML中使用它. 其他人似乎成功做到这一点.

Problem is I cannot use it in FXML. Someone else seemed to do that successfully.

错误: FXML Loader无法创建curvefittedareachartappfxml.CurvedFittedAreaChart的实例.

FXMLDocument.fxml

<?import javafx.scene.chart.*?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import curvefittedareachartappfxml.CurveFittedAreaChart?>

<AnchorPane id="AnchorPane" prefHeight="200" prefWidth="320" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.40" fx:controller="curvefittedareachartappfxml.FXMLDocumentController">
    <children>
        <Button fx:id="button" layoutX="128.0" layoutY="25.0" text="Click Me!" />
      <AreaChart layoutX="56.0" layoutY="131.0" prefHeight="200.0" prefWidth="444.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="60.0">
        <xAxis>
          <CategoryAxis side="BOTTOM" />
        </xAxis>
        <yAxis>
          <NumberAxis side="LEFT" />
        </yAxis>
      </AreaChart>
      <CurveFittedAreaChart layoutX="10.0" layoutY="160.0" prefHeight="200.0" prefWidth="444.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="250.0">
         <xAxis>
            <CategoryAxis side="BOTTOM" />
         </xAxis>
         <yAxis>
            <NumberAxis side="LEFT" />
         </yAxis>
      </CurveFittedAreaChart>
    </children>
</AnchorPane>

CurveFittedAreaChart.java 来自Oracle Ensemble的原始文件

CurveFittedAreaChart.java Original from Oracle Ensemble

 /*
 * Copyright (c) 2008, 2014, Oracle and/or its affiliates.
 * All rights reserved. Use is subject to license terms.
 *
 * This file is available and licensed under the following license:
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *  - Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  - Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the distribution.
 *  - Neither the name of Oracle Corporation nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package curvefittedareachartappfxml;

import javafx.collections.ObservableList;
import javafx.geometry.Point2D;
import javafx.scene.Group;
import javafx.scene.chart.AreaChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.shape.ClosePath;
import javafx.scene.shape.CubicCurveTo;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.scene.shape.PathElement;
import javafx.util.Pair;

public class CurveFittedAreaChart extends AreaChart<Number, Number> {

    public CurveFittedAreaChart(NumberAxis xAxis, NumberAxis yAxis) {
        super(xAxis, yAxis);
    }   
    @Override protected void layoutPlotChildren() {
        super.layoutPlotChildren();
        for (int seriesIndex = 0; seriesIndex < getDataSize(); seriesIndex++) {
            final XYChart.Series<Number, Number> series = getData().get(seriesIndex);
            final Path seriesLine = (Path) ((Group) series.getNode()).getChildren().get(1);
            final Path fillPath = (Path) ((Group) series.getNode()).getChildren().get(0);
            smooth(seriesLine.getElements(), fillPath.getElements());
        }
    }

    private int getDataSize() {
        final ObservableList<XYChart.Series<Number, Number>> data = getData();
        return (data != null) ? data.size() : 0;
    }

    private static void smooth(ObservableList<PathElement> strokeElements, ObservableList<PathElement> fillElements) {
        // as we do not have direct access to the data, first recreate the list of all the data points we have
        final Point2D[] dataPoints = new Point2D[strokeElements.size()];
        for (int i = 0; i < strokeElements.size(); i++) {
            final PathElement element = strokeElements.get(i);
            if (element instanceof MoveTo) {
                final MoveTo move = (MoveTo) element;
                dataPoints[i] = new Point2D(move.getX(), move.getY());
            } else if (element instanceof LineTo) {
                final LineTo line = (LineTo) element;
                final double x = line.getX(), y = line.getY();
                dataPoints[i] = new Point2D(x, y);
            }
        }
        // next we need to know the zero Y value
        final double zeroY = ((MoveTo) fillElements.get(0)).getY();
        // now clear and rebuild elements
        strokeElements.clear();
        fillElements.clear();
        Pair<Point2D[], Point2D[]> result = calcCurveControlPoints(dataPoints);
        Point2D[] firstControlPoints = result.getKey();
        Point2D[] secondControlPoints = result.getValue();
        // start both paths
        strokeElements.add(new MoveTo(dataPoints[0].getX(), dataPoints[0].getY()));
        fillElements.add(new MoveTo(dataPoints[0].getX(), zeroY));
        fillElements.add(new LineTo(dataPoints[0].getX(), dataPoints[0].getY()));
        // add curves
        for (int i = 1; i < dataPoints.length; i++) {
            final int ci = i - 1;
            strokeElements.add(new CubicCurveTo(
                    firstControlPoints[ci].getX(), firstControlPoints[ci].getY(),
                    secondControlPoints[ci].getX(), secondControlPoints[ci].getY(),
                    dataPoints[i].getX(), dataPoints[i].getY()));
            fillElements.add(new CubicCurveTo(
                    firstControlPoints[ci].getX(), firstControlPoints[ci].getY(),
                    secondControlPoints[ci].getX(), secondControlPoints[ci].getY(),
                    dataPoints[i].getX(), dataPoints[i].getY()));
        }
        // end the paths
        fillElements.add(new LineTo(dataPoints[dataPoints.length - 1].getX(), zeroY));
        fillElements.add(new ClosePath());
    }

    /**
     * Calculate open-ended Bezier Spline Control Points.
     *
     * @param dataPoints Input data Bezier spline points.
     * @return The spline points
     */
    public static Pair<Point2D[], Point2D[]> calcCurveControlPoints(Point2D[] dataPoints) {
        Point2D[] firstControlPoints;
        Point2D[] secondControlPoints;
        int n = dataPoints.length - 1;
        if (n == 1) { // Special case: Bezier curve should be a straight line.
            firstControlPoints = new Point2D[1];
            // 3P1 = 2P0 + P3
            firstControlPoints[0] = new Point2D(
                    (2 * dataPoints[0].getX() + dataPoints[1].getX()) / 3,
                    (2 * dataPoints[0].getY() + dataPoints[1].getY()) / 3);

            secondControlPoints = new Point2D[1];
            // P2 = 2P1 – P0
            secondControlPoints[0] = new Point2D(
                    2 * firstControlPoints[0].getX() - dataPoints[0].getX(),
                    2 * firstControlPoints[0].getY() - dataPoints[0].getY());
            return new Pair<Point2D[], Point2D[]>(firstControlPoints, secondControlPoints);
        }

        // Calculate first Bezier control points
        // Right hand side vector
        double[] rhs = new double[n];

        // Set right hand side X values
        for (int i = 1; i < n - 1; ++i) {
            rhs[i] = 4 * dataPoints[i].getX() + 2 * dataPoints[i + 1].getX();
        }
        rhs[0] = dataPoints[0].getX() + 2 * dataPoints[1].getX();
        rhs[n - 1] = (8 * dataPoints[n - 1].getX() + dataPoints[n].getX()) / 2.0;
        // Get first control points X-values
        double[] x = GetFirstControlPoints(rhs);

        // Set right hand side Y values
        for (int i = 1; i < n - 1; ++i) {
            rhs[i] = 4 * dataPoints[i].getY() + 2 * dataPoints[i + 1].getY();
        }
        rhs[0] = dataPoints[0].getY() + 2 * dataPoints[1].getY();
        rhs[n - 1] = (8 * dataPoints[n - 1].getY() + dataPoints[n].getY()) / 2.0;
        // Get first control points Y-values
        double[] y = GetFirstControlPoints(rhs);

        // Fill output arrays.
        firstControlPoints = new Point2D[n];
        secondControlPoints = new Point2D[n];
        for (int i = 0; i < n; ++i) {
            // First control point
            firstControlPoints[i] = new Point2D(x[i], y[i]);
            // Second control point
            if (i < n - 1) {
                secondControlPoints[i] = new Point2D(2 * dataPoints[i + 1].getX() - x[i + 1], 2
                        * dataPoints[i + 1].getY() - y[i + 1]);
            } else {
                secondControlPoints[i] = new Point2D((dataPoints[n].getX() + x[n - 1]) / 2,
                        (dataPoints[n].getY() + y[n - 1]) / 2);
            }
        }
        return new Pair<Point2D[], Point2D[]>(firstControlPoints, secondControlPoints);
    }

    /**
     * Solves a tridiagonal system for one of coordinates (x or y) of first
     * Bezier control points.
     *
     * @param rhs Right hand side vector.
     * @return Solution vector.
     */
    private static double[] GetFirstControlPoints(double[] rhs) {
        int n = rhs.length;
        double[] x = new double[n]; // Solution vector.
        double[] tmp = new double[n]; // Temp workspace.
        double b = 2.0;
        x[0] = rhs[0] / b;
        for (int i = 1; i < n; i++) {// Decomposition and forward substitution.
            tmp[i] = 1 / b;
            b = (i < n - 1 ? 4.0 : 3.5) - tmp[i];
            x[i] = (rhs[i] - x[i - 1]) / b;
        }
        for (int i = 1; i < n; i++) {
            x[n - i - 1] -= tmp[n - i] * x[n - i]; // Backsubstitution.
        }
        return x;
    }
}

Controller App 文件是生成的默认NetBeans.

Controller and App files are default NetBeans generated.

推荐答案

出了什么问题

您的代码中存在多个问题.

There are multiple issues in your code.

  1. 如果要通过在FXML中引用实例来实例化类的实例,则该类需要具有默认的(无参数)构造函数.

  1. If you want to instantiate a instance of a class by referencing it in FXML, the class needs to a have default (no-arg) constructor.

不幸的是,这不是如果您希望通过FXML设置实例化类的属性,则该类需要具有适当的getter和setter.请参考 JavaFX属性和绑定教程.遵循约定 https://google.github.io/styleguide/javaguide.html#s5.3-camel-case"rel =" nofollow noreferrer>小驼峰大小写方法命名或FXML不会采用您的方法可设置的属性.

If you wish to set properties of an instantiated class via FXML, that class needs to have appropriate getters and setters. Refer to the property naming conventions used in the JavaFX properties and binding tutorial. Adhere to the conventions for lower camel case method naming or FXML won't pick up your methods as settable properties.

您试图将CategoryAxis分配给自定义图表的XAxis,但是您提供的自定义图表类型要求XAxis是NumberAxis.

You are trying to assign a CategoryAxis to the XAxis of your custom chart, but the custom chart type you supplied requires that the XAxis be a NumberAxis.

前两个规则的例外是是否提供了构建器类(下面提供了一个示例构建器类).

An exception to the first two rules is if you supply a builder class (a sample builder class is provided below).

示例构建器类

这是AreaChart类的构建器代码,该代码使您可以按使用方式在FXML中使用它.如果希望以相同的方式使用自定义图表,则需要创建自己的构建器.

Here is the builder code for the AreaChart class, which allows it to be used in the FXML in the way in which you have used it. If you wish to use your custom chart in the same way, you need to create your own builder.

package javafx.scene.chart;

/**
Builder class for javafx.scene.chart.AreaChart
@see javafx.scene.chart.AreaChart
@deprecated This class is deprecated and will be removed in the next version
* @since JavaFX 2.0
*/
@javax.annotation.Generated("Generated by javafx.builder.processor.BuilderProcessor")
@Deprecated
public class AreaChartBuilder<X, Y, B extends javafx.scene.chart.AreaChartBuilder<X, Y, B>> extends javafx.scene.chart.XYChartBuilder<X, Y, B> {
    protected AreaChartBuilder() {
    }

    /** Creates a new instance of AreaChartBuilder. */
    @SuppressWarnings({"deprecation", "rawtypes", "unchecked"})
    public static <X, Y> javafx.scene.chart.AreaChartBuilder<X, Y, ?> create() {
        return new javafx.scene.chart.AreaChartBuilder();
    }

    private javafx.scene.chart.Axis<X> XAxis;
    /**
    Set the value of the {@link javafx.scene.chart.AreaChart#getXAxis() XAxis} property for the instance constructed by this builder.
    */
    @SuppressWarnings("unchecked")
    public B XAxis(javafx.scene.chart.Axis<X> x) {
        this.XAxis = x;
        return (B) this;
    }

    private javafx.scene.chart.Axis<Y> YAxis;
    /**
    Set the value of the {@link javafx.scene.chart.AreaChart#getYAxis() YAxis} property for the instance constructed by this builder.
    */
    @SuppressWarnings("unchecked")
    public B YAxis(javafx.scene.chart.Axis<Y> x) {
        this.YAxis = x;
        return (B) this;
    }

    /**
    Make an instance of {@link javafx.scene.chart.AreaChart} based on the properties set on this builder.
    */
    public javafx.scene.chart.AreaChart<X, Y> build() {
        javafx.scene.chart.AreaChart<X, Y> x = new javafx.scene.chart.AreaChart<X, Y>(this.XAxis, this.YAxis);
        applyTo(x);
        return x;
    }
}

示例代码修改

我对您的FXML和Oracle自定义图表代码做了较小的重构,以允许您在FXML中引用它.这段代码只是入门的示例,您将需要进行更多的修改以按照您希望的方式绘制有用的图表.

I did a minor refactoring of your FXML and the Oracle custom chart code to allow you to reference it in FXML. This code is just a sample to get you started, you will need to make more modifications to plot a useful chart in the manner you wish.

该示例未采用自定义构建器类方法,而是使用默认构造函数并将轴公开为属性.

The sample does not take the custom builder class approach, instead it uses a default constructor and exposes the axes as properties.

在您的CurveFittedAreaChart类中,替换:

In your CurveFittedAreaChart class, replace:

public CurveFittedAreaChart(NumberAxis xAxis, NumberAxis yAxis) {
    super(xAxis, yAxis);
}   

具有:

public CurveFittedAreaChart() {
    super(new NumberAxis(), new NumberAxis());
}

如果需要修改轴(例如,更改其自动量程功能),则可以使用@FXML将图表注入到控制器中,在控制器的初始化方法中从中检索轴,并在代码中修改轴在那里.

If you need to modify the axes (e.g., change their auto ranging capabilities), then you can inject the chart in your controller using @FXML, retrieve the axes from it in the controller initialize method, and modify the axes in code there.

在您的FXML中删除您的轴规范,例如,该图表仅被引用如下:

In your FXML remove your axes specification, e.g., the chart is just referenced as below:

<CurveFittedAreaChart layoutX="10.0" layoutY="160.0" prefHeight="200.0" prefWidth="444.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="250.0">
</CurveFittedAreaChart>

SceneBuilder中使用情况的相关信息

如果您还想在SceneBuilder中使用自定义组件(请问这不是您的问题),那么您还应该执行其他任务:

If you also want to use your custom component in SceneBuilder (which admittedly is not what your question asks), there are additional tasks you should perform:

登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆