How to Display Bar Value on Top of Bar Javafx

how to display bar value on top of bar javafx

Inside a ChangeListener for the each data item's node property, you can call the following function to add a label to the top of the bar:

private void displayLabelForData(XYChart.Data<String, Number> data) {
final Node node = data.getNode();
final Text dataText = new Text(data.getYValue() + "");
node.parentProperty().addListener(new ChangeListener<Parent>() {
@Override public void changed(ObservableValue<? extends Parent> ov, Parent oldParent, Parent parent) {
Group parentGroup = (Group) parent;
parentGroup.getChildren().add(dataText);
}
});

node.boundsInParentProperty().addListener(new ChangeListener<Bounds>() {
@Override public void changed(ObservableValue<? extends Bounds> ov, Bounds oldBounds, Bounds bounds) {
dataText.setLayoutX(
Math.round(
bounds.getMinX() + bounds.getWidth() / 2 - dataText.prefWidth(-1) / 2
)
);
dataText.setLayoutY(
Math.round(
bounds.getMinY() - dataText.prefHeight(-1) * 0.5
)
);
}
});
}

The code works by adding a text label to the parent of each bar node, then dynamically positioning the text label based on the bar and text's bounds each time the bar is resized.

I created a sample solution for this.

labeledchartwithlegend

Adding values on top of each on a realtime javafx bar chart

The following is an mre implementing some changes in your code and using the solution proposed in this answer:

import java.util.Calendar;
import java.util.TimeZone;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ChangeListener;
import javafx.geometry.Bounds;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.chart.BarChart;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.text.Text;
import javafx.stage.Stage;

public class App extends Application {
private ScheduledExecutorService scheduledExecutorService;

final static String austria = "Austria", brazil = "Brazil", france = "France";
private IntegerProperty secondA, secondB , secondC;
private Text secondAText, secondBText , secondCText;

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

@Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("Realtime Bar Chart Demo");

//defining the axes
final CategoryAxis xAxis = new CategoryAxis();
final NumberAxis yAxis = new NumberAxis();

xAxis.setAnimated(false);
yAxis.setAnimated(false);

//creating the bar chart with two axis
final BarChart<String,Number> bc = new BarChart<>(xAxis,yAxis);
bc.setAnimated(false);
bc.setTitle("Country Summary");
xAxis.setLabel("Country");
yAxis.setLabel("Value");

//defining a series to display data
XYChart.Series<String, Number> seriesA = new XYChart.Series<>();
Data<String, Number> dataA = new XYChart.Data<>(austria,0);
seriesA.getData().add(dataA);
seriesA.setName("Austra");

secondA = new SimpleIntegerProperty(0);
secondAText = new Text("");
secondA.addListener((ChangeListener<Number>) (observable, oldValue, newValue) -> {
dataA.setYValue(newValue);
secondAText.setText(String.valueOf(newValue));
});

XYChart.Series<String, Number> seriesB = new XYChart.Series<>();
Data<String, Number> dataB = new XYChart.Data<>(brazil,0);
seriesB.getData().add(dataB);
seriesB.setName("Brazil");
secondB = new SimpleIntegerProperty(0);
secondB.bind(secondA.add(27));
secondBText = new Text("");
secondB.addListener((ChangeListener<Number>) (observable, oldValue, newValue) -> {
dataB.setYValue(newValue);
secondBText.setText(String.valueOf(newValue));
});

XYChart.Series<String, Number> seriesC = new XYChart.Series<>();
Data<String, Number> dataC = new XYChart.Data<>(france,0);
seriesC.getData().add(dataC);
seriesC.setName("France");

secondC = new SimpleIntegerProperty(0);
secondC.bind(secondA.add(14));
secondCText = new Text("");
secondC.addListener((ChangeListener<Number>) (observable, oldValue, newValue) -> {
dataC.setYValue(newValue);
secondCText.setText(String.valueOf(newValue));
});

// add series to chart
bc.getData().add(seriesA);
bc.getData().add(seriesB);
bc.getData().add(seriesC);

displayLabelForData(dataA, secondAText);
displayLabelForData(dataB, secondBText);
displayLabelForData(dataC, secondCText);

// setup scene
Scene scene = new Scene(bc, 800, 600);
primaryStage.setScene(scene);

// show the stage
primaryStage.show();

// setup a scheduled executor to periodically put data into the chart
scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();

// input data onto graph per second
scheduledExecutorService.scheduleAtFixedRate(() -> {
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));

// Update the chart
Platform.runLater(() -> {
secondA.set( cal.get(Calendar.SECOND));
});
}, 0, 1, TimeUnit.SECONDS);
}

@Override
public void stop() throws Exception {
super.stop();
scheduledExecutorService.shutdownNow();
}

private void displayLabelForData(XYChart.Data<String, Number> data, Text text) {

final Node node = data.getNode();
((Group) node.getParent()).getChildren().add(text);

node.boundsInParentProperty().addListener((ChangeListener<Bounds>) (ov, oldBounds, bounds) -> {
text.setLayoutX(
Math.round( bounds.getMinX() + bounds.getWidth() / 2 - text.prefWidth(-1) / 2));
text.setLayoutY(Math.round( bounds.getMinY() - text.prefHeight(-1) * 0.5));
});
}
}

Side note: You could use javafx tools to run the periodic task:

    Timeline timeline = new Timeline(new KeyFrame(
Duration.seconds(1),
e -> {
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
secondA.set( cal.get(Calendar.SECOND));
}
));
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();

Sample Image

Adding an Image Inside Each Bar on a BarChart JavaFX

Here is an example of how to add images to BarChart's data:

import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.chart.BarChart;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class BarChartExample extends Application {

@Override
public void start(Stage stage) {
stage.setTitle("Bar Chart Sample");
final CategoryAxis xAxis = new CategoryAxis();
final NumberAxis yAxis = new NumberAxis();
final BarChart<String, Number> barChart = new BarChart<>(xAxis, yAxis);
barChart.setTitle("Country Summary");
xAxis.setLabel("Country");
yAxis.setLabel("Value");

XYChart.Series series = new XYChart.Series();
series.setName("2003");
series.getData().addAll(new XYChart.Data("Brazil", 20148.82),
new XYChart.Data("France", 10000),
new XYChart.Data("Italy", 35407.15),
new XYChart.Data("USA", 17000));

Platform.runLater(() -> {
series.getData().forEach(seriesData -> {
XYChart.Data data = (XYChart.Data) seriesData;
StackPane node = (StackPane) data.getNode();
String name = (String) data.getXValue();
ImageView imageView = getImageForCountry(name);
imageView.fitWidthProperty().bind(node.widthProperty().multiply(.5));
imageView.setPreserveRatio(true);
node.getChildren().add(imageView);
});
});
barChart.getData().addAll(series);
Scene scene = new Scene(barChart, 800, 600);
stage.setScene(scene);
stage.show();
}

private ImageView getImageForCountry(String countryName) {
URL url = getClass().getResource(countryName.toLowerCase() + ".png");
Image image = new Image(url);
return new ImageView(image);
}

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

Output:

output

How to display a different value on top of bar

You can do this using a ValueFormatter. You simply write a class that implements this interface and override the method:

String getFormattedValue(float value, Entry entry, int dataSetIndex, ViewPortHandler viewPortHandler)

Put inside the method the logic you need to convert the actual value into the value you want to display. For example, the following ValueFormatter simply multiplies the actual chart value by 10:

public class TenTimesValueFormatter implements ValueFormatter {
@Override
protected String getFormattedValue(float value, Entry entry, int dataSetIndex, ViewPortHandler viewPortHandler) {
return Integer.toString(value * 10);
}
}

You can consume it like this:

barData.setValueFormatter(new TenTimesValueFormatter());

You will probably need:

mChart.getXAxis().setGranularity(1);
mChart.getXAxis().setGranularityEnabled(true);

Update: if you need a ValueFormatter that simply takes values from a backing array, you could do something like this:

public class IndexValueFormatter implements IValueFormatter {

private final String [] values;

public IndexValueFormatter(String[] values) {
this.values = values;
}

@Override
public String getFormattedValue(float value, Entry entry, int dataSetIndex, ViewPortHandler viewPortHandler) {
int index = Math.round(value);

if (index < 0 || index >= values.length || index != (int)value)
return "";

return values[index];
}
}

How to clear text added in a javafx barchart?

Personally I'd prefer subclassing BarChart and overriding the proper methods.

This is just a demo for your needs, there's refinement to be done, i. e. overriding all related methods, formatter for the text, centering the text, resize chart area to fit the added text, etc. But I guess you'll get the drift:

import java.util.HashMap;
import java.util.Map;

import javafx.application.Application;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.chart.Axis;
import javafx.scene.chart.BarChart;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.stage.Stage;

public class BarChartSample extends Application {

final static String austria = "Austria";
final static String brazil = "Brazil";
final static String france = "France";
final static String italy = "Italy";
final static String usa = "USA";

/**
* Barchart with a clear button
*/
@Override
public void start(Stage stage) {

final CategoryAxis xAxis = new CategoryAxis();
final NumberAxis yAxis = new NumberAxis();
final BarChartExt<String, Number> bc = new BarChartExt<String, Number>(xAxis, yAxis);

bc.setTitle("Country Summary");
xAxis.setLabel("Country");
yAxis.setLabel("Value");

XYChart.Series series1 = new XYChart.Series();
series1.setName("2003");
series1.getData().add(new XYChart.Data(austria, 25601.34));
series1.getData().add(new XYChart.Data(brazil, 20148.82));
series1.getData().add(new XYChart.Data(france, 10000));
series1.getData().add(new XYChart.Data(italy, 35407.15));
series1.getData().add(new XYChart.Data(usa, 12000));

XYChart.Series series2 = new XYChart.Series();
series2.setName("2004");
series2.getData().add(new XYChart.Data(austria, 57401.85));
series2.getData().add(new XYChart.Data(brazil, 41941.19));
series2.getData().add(new XYChart.Data(france, 45263.37));
series2.getData().add(new XYChart.Data(italy, 117320.16));
series2.getData().add(new XYChart.Data(usa, 14845.27));

XYChart.Series series3 = new XYChart.Series();
series3.setName("2005");
series3.getData().add(new XYChart.Data(austria, 45000.65));
series3.getData().add(new XYChart.Data(brazil, 44835.76));
series3.getData().add(new XYChart.Data(france, 18722.18));
series3.getData().add(new XYChart.Data(italy, 17557.31));
series3.getData().add(new XYChart.Data(usa, 92633.68));

bc.getData().addAll(series1, series2, series3);

Button clearButton = new Button("Clear");
clearButton.setOnAction(e -> {

bc.getData().clear();

});

HBox toolbar = new HBox();
toolbar.getChildren().addAll(clearButton);

VBox root = new VBox();
VBox.setVgrow(bc, Priority.ALWAYS);
root.getChildren().addAll(toolbar, bc);

Scene scene = new Scene(root, 800, 600);

stage.setScene(scene);
stage.show();
}

/**
* Custom barchart with text on top of bars
*
* @param <X>
* @param <Y>
*/
private static class BarChartExt<X, Y> extends BarChart<X, Y> {

/**
* Registry for text nodes of the bars
*/
Map<Node, Node> nodeMap = new HashMap<>();

public BarChartExt(Axis xAxis, Axis yAxis) {
super(xAxis, yAxis);
}

/**
* Add text for bars
*/
@Override
protected void seriesAdded(Series<X, Y> series, int seriesIndex) {

super.seriesAdded(series, seriesIndex);

for (int j = 0; j < series.getData().size(); j++) {

Data<X, Y> item = series.getData().get(j);

Node text = new Text(String.valueOf(item.getYValue()));
nodeMap.put(item.getNode(), text);
getPlotChildren().add(text);

}

}

/**
* Remove text of bars
*/
@Override
protected void seriesRemoved(final Series<X, Y> series) {

for (Node bar : nodeMap.keySet()) {

Node text = nodeMap.get(bar);
getPlotChildren().remove(text);

}

nodeMap.clear();

super.seriesRemoved(series);
}

/**
* Adjust text of bars, position them on top
*/
@Override
protected void layoutPlotChildren() {

super.layoutPlotChildren();

for (Node bar : nodeMap.keySet()) {

Node text = nodeMap.get(bar);

text.relocate(bar.getBoundsInParent().getMinX(), bar.getBoundsInParent().getMinY() - 30);

}

}
}

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

Screenshot:

Sample Image



Related Topics



Leave a reply



Submit