Show pies percentage of PieChart - javafx

For the MVCE underneath, how can you add a label on each pie in the piechart that shows its percentage of the total pie? In the JavaDoc they only add a % after the value, which isn't any solution.
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", 13),
new PieChart.Data("Oranges", 25),
new PieChart.Data("Plums", 10),
new PieChart.Data("Pears", 22),
new PieChart.Data("Apples", 30));
final PieChart chart = new PieChart(pieChartData);
chart.setTitle("Imported Fruits");
((Group) scene.getRoot()).getChildren().add(chart);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}

All you have to do is iterate through the pie chart data and add up the values to get the total. Then divide the current value by the total.
Adapting the (awful) example from the link you provided:
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.chart.PieChart;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class PieChartSample extends Application {
#Override public void start(Stage stage) {
Pane root = new Pane();
Scene scene = new Scene(root);
stage.setTitle("Imported Fruits");
stage.setWidth(500);
stage.setHeight(500);
ObservableList<PieChart.Data> pieChartData =
FXCollections.observableArrayList(
new PieChart.Data("Grapefruit", 13),
new PieChart.Data("Oranges", 25),
new PieChart.Data("Plums", 10),
new PieChart.Data("Pears", 22),
new PieChart.Data("Apples", 30));
final PieChart chart = new PieChart(pieChartData);
chart.setTitle("Imported Fruits");
final Label caption = new Label("");
caption.setTextFill(Color.DARKORANGE);
caption.setStyle("-fx-font: 24 arial;");
for (final PieChart.Data data : chart.getData()) {
data.getNode().addEventHandler(MouseEvent.MOUSE_PRESSED,
e -> {
double total = 0;
for (PieChart.Data d : chart.getData()) {
total += d.getPieValue();
}
caption.setTranslateX(e.getSceneX());
caption.setTranslateY(e.getSceneY());
String text = String.format("%.1f%%", 100*data.getPieValue()/total) ;
caption.setText(text);
}
);
}
root.getChildren().addAll(chart, caption);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
If you want to avoid recomputing the total every time there's a mouse event, you can create a DoubleBinding to store it:
DoubleBinding total = Bindings.createDoubleBinding(() ->
pieChartData.stream().collect(Collectors.summingDouble(PieChart.Data::getPieValue)), pieChartData);
and then just
for (final PieChart.Data data : chart.getData()) {
data.getNode().addEventHandler(MouseEvent.MOUSE_PRESSED,
e -> {
caption.setTranslateX(e.getSceneX());
caption.setTranslateY(e.getSceneY());
String text = String.format("%.1f%%", 100*data.getPieValue()/total.get()) ;
caption.setText(text);
}
);
}

Related

How to create a view like this in Java-fx?

I'm having some problems with this view. What I need to do it in JavaFX to add this 'border' and divide the circle into 2 parts.
public void start(Stage primaryStage){
Circle mycircle = new Circle(200,200,200);
mycircle.setFill(Color.GREEN);
BorderPane root = new BorderPane();
root.setCenter(mycircle);
}
I don't have errors but it's not the view what I am looking for. So can anyone help me with this, please
And btw sorry for bad image quality
Would this satisfy your needs?
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class HalfCircleDemo extends Application {
#Override
public void start(final Stage stage) {
BorderPane root = new BorderPane();
Group circleGroup = new Group();
Circle greenCircle = new Circle(200,200,200);
greenCircle.setFill(Color.GREEN);
Circle blueCircle = new Circle(200,200,200);
blueCircle.setFill(Color.BLUE);
Rectangle clip = new Rectangle(400, 200);
greenCircle.setClip(clip);
circleGroup.getChildren().setAll(blueCircle, greenCircle);
root.setCenter(circleGroup);
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
You could use Paths with ArcTo elements:
private static Group createHalfCircles(double radius) {
Path upperHalf = new Path(
new MoveTo(0, radius),
new ArcTo(radius, radius, 0, 2*radius, radius, true, false),
new ClosePath());
upperHalf.setFill(Color.PURPLE);
upperHalf.setStroke(null);
Path lowerHalf = new Path(
new MoveTo(2 * radius, radius),
new ArcTo(radius, radius, 0, 0, radius, true, false),
new ClosePath());
lowerHalf.setFill(Color.GREEN);
lowerHalf.setStroke(null);
return new Group(upperHalf, lowerHalf);
}
#Override
public void start(Stage primaryStage) throws Exception {
Scene scene = new Scene(createHalfCircles(50));
primaryStage.setScene(scene);
primaryStage.show();
}

JavaFX How to get a (x, y) coordinate from a plotted dot on a chart with the mouse cursor?

How to obtain the (x; y) coordinate XYChart.Data(x, y) from a plotted
chart symbol by clicking on it or passing the mouse cursor above it?
A label has to receive the obtained coordinate if the mouse has selected it.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class GetChartCoord extends Application {
#Override
public void start(Stage stage) {
VBox vbox = new VBox();
// Creating a chart
final NumberAxis xAxis = new NumberAxis();
final NumberAxis yAxis = new NumberAxis();
LineChart<Number, Number> lineChart = new LineChart<Number, Number>(xAxis, yAxis);
XYChart.Series series = new XYChart.Series();
series.setName("Example 1");
for (int x = 0; x <= 100; x++) {
double y = Math.random()*100;
series.getData().add(new XYChart.Data(x, y));
}
lineChart.getData().add(series);
// This label should receive the coordinate (x; y) from the dot that is
// on the mouse cursor or very next to it
Label labelXY = new Label();
labelXY.setText("(x; y)");
vbox.getChildren().addAll(lineChart, labelXY);
Scene scene = new Scene(vbox, 800, 600);
stage.setScene(scene);
stage.show();
}
}
EDIT:
The answer for that question mentioned by Sedrick solved my problem, but I had to adapt to adapt the code. So I will answer my own question by posting my modified code
Chart.java
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.stage.Stage;
public class ChangeSymbolSize extends Application {
#Override
public void start(Stage stage) {
// Random chart
// Defining the Axis
final NumberAxis xAxis = new NumberAxis();
final NumberAxis yAxis = new NumberAxis();
// Creating the chart
LineChart<Number, Number> lineChart = new LineChart(xAxis, yAxis);
// Preparing the series
XYChart.Series series = new XYChart.Series();
series.setName("Grafico");
for (double x = 0; x <= 10; x++) {
double y = Math.random() * 100;
XYChart.Data chartData;
chartData = new XYChart.Data(x, y);
chartData.setNode(new ShowCoordinatesNode(x, y));
series.getData().add(chartData);
}
// Adding series to chart
lineChart.getData().add(series);
Scene scene = new Scene(lineChart, 800, 600);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
ShowCoordinatesNode.java
import java.text.DecimalFormat;
import javafx.event.EventHandler;
import javafx.scene.Cursor;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
public class ShowCoordinatesNode extends StackPane {
public ShowCoordinatesNode(double x, double y) {
final Label label = createDataThresholdLabel(x, y);
setOnMouseEntered(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
setScaleX(1);
setScaleY(1);
getChildren().setAll(label);
setCursor(Cursor.NONE);
toFront();
}
});
setOnMouseExited(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
getChildren().clear();
setCursor(Cursor.CROSSHAIR);
}
});
}
private Label createDataThresholdLabel(double x, double y) {
DecimalFormat df = new DecimalFormat("0.##");
final Label label = new Label("(" + df.format(x) + "; " + df.format(y) + ")");
label.getStyleClass().addAll("default-color0", "chart-line-symbol", "chart-series-line");
label.setStyle("-fx-font-size: 10; -fx-font-weight: bold;");
label.setMinSize(Label.USE_PREF_SIZE, Label.USE_PREF_SIZE);
return label;
}
}

Display additional values in Pie chart

I have this example of Pie chart data:
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.chart.PieChart;
import javafx.scene.chart.PieChart.Data;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;
import javafx.scene.effect.Glow;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class MainApp extends Application
{
Stage stage;
PieChart chart;
ObservableList<Data> pieChartData = FXCollections.observableArrayList();
Label caption;
#Override
public void start(Stage stage)
{
this.stage = stage;
setUserAgentStylesheet(STYLESHEET_CASPIAN);
Scene scene = new Scene(new Group());
stage.setTitle("Imported Fruits");
stage.setWidth(500);
stage.setHeight(500);
chart = new PieChart(pieChartData);
chart.setTitle("Imported Fruits");
// Add some data
addPieChartData("Grapefruit", 13);
addPieChartData("Oranges", 25);
addPieChartData("Plums", 10);
addPieChartData("Pears", 22);
addPieChartData("Apples", 30);
// Some task which updates the Pie Chart
final Task task;
task = new Task<Void>()
{
#Override
protected Void call() throws Exception
{
int max = 50;
int l = 0;
for (int i = 1; i <= max; i++)
{
updatePieChartData("Grapefruit", l++);
updatePieChartData("Oranges", l++);
Thread.sleep(600);
}
return null;
}
};
new Thread(task).start();
((Group) scene.getRoot()).getChildren().addAll(chart, caption);
stage.setScene(scene);
stage.show();
}
public void addPieChartData(String name, double value)
{
pieChartData.add(new Data(name, value));
caption = new Label();
caption.setTextFill(Color.DARKORANGE);
caption.setStyle("-fx-font: 24 arial;");
for (final Data data : chart.getData())
{
Node node = data.getNode();
node.addEventHandler(MouseEvent.MOUSE_MOVED, new EventHandler<MouseEvent>()
{
#Override
public void handle(MouseEvent e)
{
caption.setTranslateX(e.getSceneX() + 15);
caption.setTranslateY(e.getSceneY());
caption.setText(String.valueOf(data.getPieValue()) + "%");
caption.setVisible(true);
node.setEffect(new Glow());
//String styleString = "-fx-border-color: white; -fx-border-width: 1; -fx-border-style: dashed;";
//node.setStyle(styleString);
}
});
node.addEventHandler(MouseEvent.MOUSE_EXITED, new EventHandler<MouseEvent>()
{
#Override
public void handle(MouseEvent e)
{
caption.setVisible(false);
node.setEffect(null);
//node.setStyle("");
}
});
final MenuItem resizeItem = new MenuItem("Resize");
resizeItem.setOnAction(new EventHandler<ActionEvent>()
{
#Override
public void handle(ActionEvent event)
{
System.out.println("Resize requested");
}
});
final MenuItem aboutItem = new MenuItem("About");
aboutItem.setOnAction(new EventHandler<ActionEvent>()
{
#Override
public void handle(ActionEvent event)
{
System.out.println("About requested");
}
});
final MenuItem changeColorItem = new MenuItem("Change Color");
changeColorItem.setOnAction(new EventHandler<ActionEvent>()
{
#Override
public void handle(ActionEvent event)
{
System.out.println("change Color Item requested");
}
});
final ContextMenu menu = new ContextMenu(resizeItem, aboutItem, changeColorItem);
node.setOnMouseClicked(new EventHandler<MouseEvent>()
{
#Override
public void handle(MouseEvent event)
{
if (MouseButton.SECONDARY.equals(event.getButton()))
{
menu.show(stage, event.getScreenX(), event.getScreenY());
}
}
});
}
}
// updates existing Data-Object if name matches
public void updatePieChartData(String name, double value)
{
for (Data d : pieChartData)
{
if (d.getName().equals(name))
{
d.setPieValue(value);
return;
}
}
addPieChartData(name, value);
}
public static void main(String[] args)
{
launch(args);
}
}
Is there any way similar to tickLabelFormatter in Line chart to display additional values in Pie chart names?
I want to display each Pie slice with names and number.
Just bind the pie chart data name to the required value.
pieChartData.forEach(data ->
data.nameProperty().bind(
Bindings.concat(
data.getName(), " ", data.pieValueProperty(), " Tons"
)
)
);
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.collections.*;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.chart.*;
import javafx.scene.Group;
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", 13),
new PieChart.Data("Oranges", 25),
new PieChart.Data("Plums", 10),
new PieChart.Data("Pears", 22),
new PieChart.Data("Apples", 30));
final PieChart chart = new PieChart(pieChartData);
chart.setTitle("Imported Fruits");
pieChartData.forEach(data ->
data.nameProperty().bind(
Bindings.concat(
data.getName(), " ", data.pieValueProperty(), " Tons"
)
)
);
((Group) scene.getRoot()).getChildren().add(chart);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
If you have changing values (like in the example code) and/or want to avoid displaying the values in the chart legend, you could modify the text label instead of changing the data names.
Each data item has a text node that holds the string which is shown as the pie chart label. This string can be updated before the chart children are drawn by overriding PieChart#layoutChartChildren:
chart = new PieChart(pieChartData) {
#Override
protected void layoutChartChildren(double top, double left, double contentWidth, double contentHeight) {
if (getLabelsVisible()) {
getData().forEach(d -> {
Optional<Node> opTextNode = chart.lookupAll(".chart-pie-label").stream().filter(n -> n instanceof Text && ((Text) n).getText().contains(d.getName())).findAny();
if (opTextNode.isPresent()) {
((Text) opTextNode.get()).setText(d.getName() + " " + d.getPieValue() + " Tons");
}
});
}
super.layoutChartChildren(top, left, contentWidth, contentHeight);
}
};

JavaFX number axis getParent coordinates are offset compared to the chart it is a child to

Can anyone explain in the below code, why using a getParent call on a number axis gives a different scene to local conversion for mouse events compared to the chart which should be it's parent (or my understanding is incorrect). Using the number axis to get the parent and to then do the conversion shows a layout applied to it, and I don't understand why. You can run the code and click to see this.
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class ChartLocal extends Application {
#Override
public void start(Stage stage) {
final BorderPane root = new BorderPane();
final StackPane pane = new StackPane();
final NumberAxis xAxis = new NumberAxis(0, 10, 2);
final NumberAxis yAxis = new NumberAxis(0, 10, 2);
final LineChart<Number, Number> chart = new LineChart<>(xAxis, yAxis);
pane.getChildren().add(chart);
root.setCenter(pane);
chart.setOnMousePressed(new EventHandler<MouseEvent>() {
public void handle(MouseEvent event) {
System.out.println("----");
System.out.println("Event source :" + event.getSource());
System.out.println("Chart :" + chart);
System.out.println("Axis parent :" + xAxis.getParent());
System.out.println("Pos 1 : " + event.getX() + "," + event.getY());
System.out.println("Pos 2 : " + chart.sceneToLocal(event.getSceneX(), 0).getX() +
"," + chart.sceneToLocal(0, event.getSceneY()).getY());
System.out.println("Pos 3 : " + xAxis.getParent().sceneToLocal(event.getSceneX(), 0).getX() +
"," + yAxis.getParent().sceneToLocal(0, event.getSceneY()).getY());
}
});
final Scene scene = new Scene(root, 600, 400);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}

how can i call the JavaFX_Charts Application from java and update its values

I have created a gui application that reads some data and stores them in a file. I want to call from java the JavaFX_Charts application to create and show me on screen the data from file.
The file contains two columns with numbers one for each axis's. I read the file and create two
List<Integer> l1 = new ArrayList<Integer>();
List<Integer> l2 = new ArrayList<Integer>();
How can I call JavaFX_Charts and update it with my values on the 2 lists?
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.stage.Stage;
public class JavaFX_Charts extends Application {
private void init(Stage primaryStage) {
Group root = new Group();
primaryStage.setScene(new Scene(root));
root.getChildren().add(createChart());
}
protected LineChart<Number, Number> createChart() {
final NumberAxis xAxis = new NumberAxis();
final NumberAxis yAxis = new NumberAxis();
final LineChart<Number, Number> lc = new LineChart<Number, Number>(
xAxis, yAxis);
// setup chart
lc.setTitle("Basic LineChart");
xAxis.setLabel("X Axis");
yAxis.setLabel("Y Axis");
// add starting data
XYChart.Series<Number, Number> series = new XYChart.Series<Number, Number>();
series.setName("Data Series 1");
series.getData().add(new XYChart.Data<Number, Number>(20d, 50d));
series.getData().add(new XYChart.Data<Number, Number>(40d, 80d));
series.getData().add(new XYChart.Data<Number, Number>(50d, 90d));
series.getData().add(new XYChart.Data<Number, Number>(70d, 30d));
series.getData().add(new XYChart.Data<Number, Number>(170d, 122d));
lc.getData().add(series);
return lc;
}
#Override
public void start(Stage primaryStage) throws Exception {
init(primaryStage);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I have created two list and filled it up with Random data, then parsing the list to fill the data into the chart
An example to show populating data from list
import java.util.ArrayList;
import java.util.List;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.stage.Stage;
public class JavaFX_Charts extends Application {
private void init(Stage primaryStage) {
Group root = new Group();
primaryStage.setScene(new Scene(root));
root.getChildren().add(createChart());
}
protected LineChart<Number, Number> createChart() {
final NumberAxis xAxis = new NumberAxis();
final NumberAxis yAxis = new NumberAxis();
final LineChart<Number, Number> lc = new LineChart<Number, Number>(
xAxis, yAxis);
// setup chart
lc.setTitle("Basic LineChart");
xAxis.setLabel("X Axis");
yAxis.setLabel("Y Axis");
// add starting data
List<Integer> l1 = new ArrayList<Integer>();
List<Integer> l2 = new ArrayList<Integer>();
//Populating the List with Random data
for(int i=0; i<10; i++){
l1.add((int) (Math.random()*10*i));
l2.add((int) (Math.random()*10*i));
}
XYChart.Series<Number, Number> series = new XYChart.Series<Number, Number>();
series.setName("Data Series 1");
//Fetching data from the list
/**
* Using the size of l1, taking into
* consideration that l1 and l2 are of same size
*/
for(int i=0; i<l1.size(); i++){
series.getData().add(new XYChart.Data<Number, Number>(l1.get(i), l2.get(i)));
}
lc.getData().add(series);
return lc;
}
#Override
public void start(Stage primaryStage) throws Exception {
init(primaryStage);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Update as per comments
To run the same thing in a Swing application, you just need to use a JFXPanel and use the setScene() to set a JavaFX scene into it
Example with Swing
import java.util.ArrayList;
import java.util.List;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
public class JFX_Charts_Swing {
private static void initAndShowGUI() {
// This method is invoked on Swing thread
JFrame frame = new JFrame("Chart FX");
final JFXPanel fxPanel = new JFXPanel();
frame.add(fxPanel);
frame.setBounds(400, 400, 600, 600);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setVisible(true);
// To run on the Javafx thread
Platform.runLater(new Runnable() {
#Override
public void run() {
initFX(fxPanel);
}
});
}
private static void initFX(JFXPanel fxPanel) {
Group root = new Group();
Scene scene = new Scene(root);
root.getChildren().add(createChart());
fxPanel.setScene(scene);
}
protected static LineChart<Number, Number> createChart() {
final NumberAxis xAxis = new NumberAxis();
final NumberAxis yAxis = new NumberAxis();
final LineChart<Number, Number> lc = new LineChart<Number, Number>(
xAxis, yAxis);
// setup chart
lc.setTitle("Basic LineChart");
xAxis.setLabel("X Axis");
yAxis.setLabel("Y Axis");
// add starting data
List<Integer> l1 = new ArrayList<Integer>();
List<Integer> l2 = new ArrayList<Integer>();
// Populating the List with Random data
for (int i = 0; i < 10; i++) {
l1.add((int) (Math.random() * 10 * i));
l2.add((int) (Math.random() * 10 * i));
}
XYChart.Series<Number, Number> series = new XYChart.Series<Number, Number>();
series.setName("Data Series 1");
// Fetching data from the list
/**
* Using the size of l1, taking into consideration that l1 and l2 are of
* same size
*/
for (int i = 0; i < l1.size(); i++) {
series.getData().add(
new XYChart.Data<Number, Number>(l1.get(i), l2.get(i)));
}
lc.getData().add(series);
return lc;
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
initAndShowGUI();
}
});
}
}

Resources