JavaFX 双圆环图

JavaFX Double Donut Chart

我在离线环境下工作,所以我无法复制整个代码库,我会尽量描述。

JavaFX 不提供圆环图,因此我通过简单扩展 PieChart 然后在中心添加一个圆来创建自定义圆环图。伪代码如下所示:

public class DonutChart extends PieChart{
    private final Circle innerCircle;

    //Initialize PieChart
    //Override "layoutChartChildren()" method to override chart label with value instead of name
    //Calculate chart bounds by looping through each node to find the max/min X/Y bounds.
    //Create a smaller circle and relocate it to the center of the PieChart
}

这给了我一个我想要的圆环图,但现在我希望有一个双圆环图,这意味着另一个层进一步分解了图表。我又做了同样的事情,但这次我不仅添加了 'innerCircle',还添加了另一个饼图。现在看起来像这样:

public class DonutChart extends PieChart{
    private final Circle innerCircle;
    private PieChart innerChart;

   /*
    * Initialize PieChart
    * Override "layoutChartChildren()" method to override chart label with value instead of name
    * Calculate chart bounds by looping through each node to find the max/min X/Y bounds.
    * Create and add innerChart and innerCircle
    */
    private void addChartAndCircle(){
        if (getData().size > 0){
            Node pie = getData().get(0).getNode();
            if (pie.getParent() instanceof Pane){
                Pane parent = pie.getParent();
                if (!parent.getChildren().contains(innerChart)){
                    parent.getChildren().add(innerChart);
                }
                if (!parent.getChildren().contains(innerCircle)){
                    parent.getChildren().add(innerCircle);
                }
            }
        }
    }
    //Relocate to center
}

只显示了外层图表"DonutChart"和内层圆圈一起显示,但看不到内层图表。我也观察到inner chart的width属性为0,我试过绑定innerChart的属性,设置max/min/pref width/height但是没有用。

有人可以创建显示 2 层的自定义圆环图吗?

这个问题看起来很有趣,我试了一下你的要求。事实上,我也遇到了渲染内部图表的确切问题。经过进一步调查,问题似乎出在区域的布局子项上。

由于现在我们介绍一个陌生人(aka inner chart),outer chart的layoutChildren()不知道如何渲染它。你可能会问圆是怎么渲染出来的。我认为使用 Shapes 会有所不同。不确定 ;-)

所以要让它工作,你需要在外层图表的layoutChartChildren()方法中提供内层图表的尺寸。

跳过您提供的一些内容,下面是我尝试让它工作的快速演示。希望对您有所帮助。

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.BoundingBox;
import javafx.geometry.Bounds;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.chart.PieChart;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;

import java.util.Set;
import java.util.stream.Collectors;

public class PieChartSample extends Application {

    @Override
    public void start(Stage stage) {
        Scene scene = new Scene(new Group());
        stage.setTitle("Imported Fruits");
        stage.setWidth(500);
        stage.setHeight(500);

        ObservableList<PieChart.Data> pieChartData =
                FXCollections.observableArrayList(
                        new PieChart.Data("Grapefruit", 45),
                        new PieChart.Data("Oranges", 25),
                        new PieChart.Data("Plums", 30));

        ObservableList<PieChart.Data> innerChartData =
                FXCollections.observableArrayList(
                        new PieChart.Data("G1", 20),
                        new PieChart.Data("G2", 25),
                        new PieChart.Data("O1", 10),
                        new PieChart.Data("O2", 15),
                        new PieChart.Data("P1", 15),
                        new PieChart.Data("P2", 15));
        final DonutChart chart = new DonutChart(pieChartData);
        chart.setTitle("Imported Fruits");
        chart.setInnerChartData(innerChartData);

        ((Group) scene.getRoot()).getChildren().add(chart);
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }

    class DonutChart extends PieChart {
        private final double SPACE = 30; /* Gap between outer and inner pie*/
        private PieChart innerChart;
        private Circle circle;
        private Bounds outerPieBounds;

        public DonutChart(ObservableList<PieChart.Data> data) {
            super(data);
            innerChart = new PieChart() {
                @Override
                protected void layoutChartChildren(double top, double left, double contentWidth, double contentHeight) {
                    super.layoutChartChildren(top, left, contentWidth, contentHeight);
                    final Pane chartContent = (Pane) lookup(".chart-content");
                    chartContent.setPadding(new Insets(0));
                }
            };
            innerChart.setPadding(new Insets(0));
            innerChart.setLabelsVisible(false);
            innerChart.setLegendVisible(false);

            circle = new Circle(40);
            circle.setOpacity(1);
            circle.setStyle("-fx-fill:white;");
        }

        @Override
        protected void layoutChartChildren(double top, double left, double contentWidth, double contentHeight) {
            super.layoutChartChildren(top, left, contentWidth, contentHeight);
            final Pane chartContent = (Pane) lookup(".chart-content");
            if (!chartContent.getChildren().contains(innerChart)) {
                chartContent.getChildren().add(innerChart);
            }
            if (!chartContent.getChildren().contains(circle)) {
                chartContent.getChildren().add(circle);
            }
            updateOuterPieBounds();
            double cX = outerPieBounds.getMinX() + (outerPieBounds.getWidth() / 2);
            double cY = outerPieBounds.getMinY() + (outerPieBounds.getHeight() / 2);
            circle.setCenterX(cX);
            circle.setCenterY(cY);
            double innerSize = outerPieBounds.getWidth() - (2 * SPACE);
            innerChart.resize(innerSize, innerSize); // THIS WHERE YOUR ISSUE LIES. YOU NEED TO PROVIDE THE SIZE TO INNER CHART
            innerChart.setTranslateX(cX - innerChart.getWidth() / 2);
            innerChart.setTranslateY(cY - innerChart.getHeight() / 2);
        }

        public void setInnerChartData(ObservableList<PieChart.Data> data) {
            innerChart.setData(data);
        }

        /**
         * Determining the outer pie visual bounds.
         */
        private void updateOuterPieBounds() {
            Pane chartContent = (Pane) lookup(".chart-content");
            Set<Node> pieNodes = chartContent.getChildren().stream().filter(node -> node.getStyleClass().contains("chart-pie")).collect(Collectors.toSet());
            double minX = getWidth();
            double minY = getHeight();
            double maxX = 0, maxY = 0;
            for (Node pie : pieNodes) {
                Bounds pieBounds = pie.getBoundsInParent();
                minX = Math.min(minX, pieBounds.getMinX());
                minY = Math.min(minY, pieBounds.getMinY());
                maxX = Math.max(maxX, pieBounds.getMaxX());
                maxY = Math.max(maxY, pieBounds.getMaxY());
            }
            outerPieBounds = new BoundingBox(minX, minY, maxX - minX, maxY - minY);
        }
    }
}