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);
}
}
}
我在离线环境下工作,所以我无法复制整个代码库,我会尽量描述。
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);
}
}
}