Search code examples
javajavafxgraphgraphicspie-chart

How to display values in JavaFX Pie Chart?


I am displaying pie chart representing under and over performing students for each module of a degree. The code that I have displays this: output

However I would like the values for each slice to appear within it, I don't want any fancy mouselistener thing just to show the numbers in the slices.

My code is as follows:

public class PieChartSample extends Application { //TODO make constructor that takes hashmaps as arguments
static Map<String, Integer> studsabove = new HashMap<String, Integer>();
static Map<String, Integer> studsbelow = new HashMap<String, Integer>();

@Override
public void start(Stage primaryStage) throws Exception {
    primaryStage.setTitle("My First JavaFX App");

    ArrayList<PieChart> pielist = new ArrayList<PieChart>();
    for(Entry<String, Integer> mod: studsabove.entrySet()){
        for(Entry<String, Integer> mod2: studsbelow.entrySet()){
            PieChart pieChart = new PieChart();
            PieChart.Data above = new PieChart.Data(mod.getKey(), mod.getValue());
            PieChart.Data below = new PieChart.Data(mod2.getKey(), mod2.getValue());

            pieChart.getData().add(above);
            pieChart.getData().add(below);

            pielist.add(pieChart);
        }
    }

    VBox vbox = new VBox();
    for (PieChart pie: pielist){
        pie.setLabelLineLength(10); //FIXME
        pie.setLegendSide(Side.LEFT);
        vbox.getChildren().add(pie);
    }
    Scene scene = new Scene(vbox, 400, 200); //TODO: Make pie charts appear horizontally rather than vertically

    primaryStage.setScene(scene);
    primaryStage.setHeight(900);
    primaryStage.setWidth(400);

    primaryStage.show();
}


public static void main(String[] args) { //TODO: need to figure out how to display the values in the slices of the pie charts

    PieChartSample.studsabove.put("CE201", 23);
    PieChartSample.studsbelow.put("CE201", 67);

    PieChartSample.studsabove.put("CE222", 20);
    PieChartSample.studsbelow.put("CE222", 80);


    PieChartSample.studsabove.put("CE233", 6);
    PieChartSample.studsbelow.put("CE233", 94);

    PieChartSample.studsabove.put("CE244", 56);
    PieChartSample.studsbelow.put("CE244", 44);
    Application.launch(args);
}

}


Solution

  • You can extend PieChart like this:

    import java.util.HashMap;
    import java.util.Map;
    import javafx.collections.ListChangeListener;
    import javafx.scene.chart.PieChart;
    import javafx.scene.layout.Region;
    import javafx.scene.shape.Arc;
    import javafx.scene.text.Text;
    
    public class LabeledPieChart extends PieChart {
    
        private final Map<Data, Text> _labels = new HashMap<>();
    
        public LabeledPieChart() {
            super();
            this.getData().addListener((ListChangeListener.Change<? extends Data> c) -> {
                addLabels();
            });
    
        }
    
        @Override
        protected void layoutChartChildren(double top, double left, double contentWidth, double contentHeight) {
            super.layoutChartChildren(top, left, contentWidth, contentHeight);
    
            double centerX = contentWidth / 2 + left;
            double centerY = contentHeight / 2 + top;
    
            layoutLabels(centerX, centerY);
        }
    
        private void addLabels() {
    
            for (Text label : _labels.values()) {
                this.getChartChildren().remove(label);
            }
            _labels.clear();
            for (Data vData : getData()) {
                final Text dataText;
                final double yValue = vData.getPieValue();
                dataText = new Text(Double.toString(yValue));
                _labels.put(vData, dataText);
                this.getChartChildren().add(dataText);
            }
        }
    
        private void layoutLabels(double centerX, double centerY) {
    
            double total = 0.0;
            for (Data d : this.getData()) {
                total += d.getPieValue();
            }
            double scale = (total != 0) ? 360 / total : 0;
    
            for (Map.Entry<Data, Text> entry : _labels.entrySet()) {
                Data vData = entry.getKey();
                Text vText = entry.getValue();
    
                Region vNode = (Region) vData.getNode();
                Arc arc = (Arc) vNode.getShape();
    
                double start = arc.getStartAngle();
    
                double size = (isClockwise()) ? (-scale * Math.abs(vData.getPieValue())) : (scale * Math.abs(vData.getPieValue()));
                final double angle = normalizeAngle(start + (size / 2));
                final double sproutX = calcX(angle, arc.getRadiusX() / 2, centerX);
                final double sproutY = calcY(angle, arc.getRadiusY() / 2, centerY);
    
                vText.relocate(
                        sproutX - vText.getBoundsInLocal().getWidth(),
                        sproutY - vText.getBoundsInLocal().getHeight());
            }
    
        }
    
        private static double normalizeAngle(double angle) {
            double a = angle % 360;
            if (a <= -180) {
                a += 360;
            }
            if (a > 180) {
                a -= 360;
            }
            return a;
        }
    
        private static double calcX(double angle, double radius, double centerX) {
            return (double) (centerX + radius * Math.cos(Math.toRadians(-angle)));
        }
    
        private static double calcY(double angle, double radius, double centerY) {
            return (double) (centerY + radius * Math.sin(Math.toRadians(-angle)));
        }
    
    }
    

    And here is a main class for you to test:

    import javafx.application.Application;
    import javafx.scene.Scene;
    import javafx.scene.chart.PieChart.Data;
    import javafx.scene.layout.AnchorPane;
    import javafx.stage.Stage;
    
    public class JavaFXApplication28 extends Application {
    
        @Override
        public void start(Stage stage) {
    
            stage.setTitle("Imported Fruits");
            stage.setWidth(500);
            stage.setHeight(500);
    
            LabeledPieChart chart = new LabeledPieChart();
            chart.setTitle("Imported Fruits");
    
            // Add some data
            addPieChartData(chart, "Grapefruit", 13);
            addPieChartData(chart, "Oranges", 25);
            addPieChartData(chart, "Plums", 10);
            addPieChartData(chart, "Pears", 22);
            addPieChartData(chart, "Apples", 30);
    
            AnchorPane anchor = new AnchorPane();
            Scene scene = new Scene(anchor);
            anchor.getChildren().add(chart);
    
            AnchorPane.setBottomAnchor(chart, 0.0);
            AnchorPane.setTopAnchor(chart, 0.0);
            AnchorPane.setLeftAnchor(chart, 0.0);
            AnchorPane.setRightAnchor(chart, 0.0);
    
            stage.setScene(scene);
            stage.show();
        }
    
        public void addPieChartData(LabeledPieChart pChart, String name, double value) {
            final Data data = new Data(name, value);
            pChart.getData().add(data);
        }
    
        /**
         * @param args the command line arguments
         */
        public static void main(String[] args) {
            launch(args);
        }
    
    }