2

I have a JavaFX BubbleChart in my visualization and I need to be able to create/display text within each bubble of the chart. In my visualization, I have numerous XYChart.Series with only 1 bubble per series. For each series, I do "series.setName("xxxx");" (where xxxx = unique series name) and I need to be able to display that series name inside the bubble.

I already have implemented a Tooltip (mouse-over event) for the Bubble Chart that displays the series name, but I need to also have the text visible inside the bubble without requiring a mouse-over.

For the sake of having code to work against, here is a basic example with 5 series. How would I go about adding a text inside each Bubble?

Thank you.

public class bubbleChartTest extends Application {

@Override
public void start(Stage stage) {

    final NumberAxis xAxis = new NumberAxis(0, 10, 1);
    final NumberAxis yAxis = new NumberAxis(0, 10, 1);
    final BubbleChart<Number, Number> bc = new BubbleChart<Number, Number>(xAxis, yAxis);

    xAxis.setLabel("X Axis");
    xAxis.setMinorTickCount(2);
    yAxis.setLabel("Y Axis");
    yAxis.setTickLabelGap(2);

    bc.setTitle("Bubble Chart StackOverflow Example");

    XYChart.Series<Number, Number> series1 = new XYChart.Series<Number, Number>();
    series1.setName("Series 1");
    series1.getData().add(new XYChart.Data<Number, Number>(3, 7, 1.5));

    XYChart.Series<Number, Number> series2 = new XYChart.Series<Number, Number>();
    series2.setName("Series 2");
    series2.getData().add(new XYChart.Data<Number, Number>(8, 3, 1));

    XYChart.Series<Number, Number> series3 = new XYChart.Series<Number, Number>();
    series3.setName("Series 3");
    series3.getData().add(new XYChart.Data<Number, Number>(1, 9, 2));

    XYChart.Series<Number, Number> series4 = new XYChart.Series<Number, Number>();
    series4.setName("Series 4");
    series4.getData().add(new XYChart.Data<Number, Number>(4, 1, 0.5));

    XYChart.Series<Number, Number> series5 = new XYChart.Series<Number, Number>();
    series5.setName("Series 5");
    series5.getData().add(new XYChart.Data<Number, Number>(9, 9, 3));

    Scene scene = new Scene(bc);
    bc.getData().addAll(series1, series2, series3, series4, series5);
    stage.setScene(scene);
    stage.show();

    for(XYChart.Series<Number, Number> series : bc.getData()) {
        for(XYChart.Data<Number, Number> data : series.getData()) {
            Tooltip.install(data.getNode(), new Tooltip(series.getName()));
        }
    }
}

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

}

  • A dirty hack would be to subclass BubbleChart and add text to the node for each data point during layout. A less dirty version would be to operate directly on the [nodes](https://docs.oracle.com/javafx/2/api/javafx/scene/Node.html). – G. Bach Sep 23 '15 at 14:33

1 Answers1

2

The node you get from the data is a Stackpane, and the Stackpane is shaped as an Ellipse. You probably need the radius of that Ellipse in x orientation and add a label to the Stackpane. But you need to set the minWidth Property of the Label, otherwise it will only display the three dots. And you need a Property to hold a dynamic font size, because it should look pretty if you want to resize the chart.

You do not need much code to get this work:

    for (XYChart.Series<Number, Number> series : bc.getData()) {
        for (XYChart.Data<Number, Number> data : series.getData()) {
            Node bubble = data.getNode();
            if (bubble != null && bubble instanceof StackPane) {
                StackPane region = (StackPane) bubble;
                if (region.getShape() != null && region.getShape() instanceof Ellipse) {
                    Ellipse ellipse = (Ellipse) region.getShape();
                    DoubleProperty fontSize = new SimpleDoubleProperty(10);

                    Label label = new Label(series.getName());
                    label.setAlignment(Pos.CENTER);
                    label.minWidthProperty().bind(ellipse.radiusXProperty());
                    //fontSize.bind(Bindings.when(ellipse.radiusXProperty().lessThan(40)).then(6).otherwise(10));
                    fontSize.bind(Bindings.divide(ellipse.radiusXProperty(), 5));
                    label.styleProperty().bind(Bindings.concat("-fx-font-size:", fontSize.asString(), ";"));
                    region.getChildren().add(label);
                }
            }
        }
    }

Application

Update

James_D mentioned that the loop isn't much robust in case of changing the, for ex., Shape. So I've changed it a bit to ask for the ellipse instance. This is a bit like the original layoutPlotChildren method from BubbleChart.

Community
  • 1
  • 1
aw-think
  • 4,723
  • 2
  • 21
  • 42
  • Can you bind to `region.widthProperty()` (or values derived from it)? Then you would not have to access the `Ellipse` and would be relying on one fewer implementation detail (so it would be more robust to implementation changes). – James_D Sep 23 '15 at 16:05
  • A short test with region.getWidth() shows me that it isn't set. It always returns zero. – aw-think Sep 23 '15 at 16:10
  • Yeah, it does. You can get there via `region.boundsInLocalProperty()` but it gets pretty ugly. – James_D Sep 23 '15 at 16:20
  • @James_D So this seems to be the reason why the label only shows the three dots, because the stackpane's width is set to zero. I've added the check if it really is an Ellipse. So this is really special for a BubbleChart only, but takes care of the extraValue as a factor for the radius. – aw-think Sep 23 '15 at 16:55