Remove JavaFX 2 LineChart Legend Items

2019-02-22 07:06发布

I have a line chart with many series. These series are grouped into one or more super-series. Each super-series may have many "breaks" in the line in order to accurately depict when a monitor process is not actively collecting data. Each data break is actually starting a new series.

I have been able to successfully overcome several technical issues with this such as the chart assigning a new color to each new series, chart line symbol color not matching the series color, etc. All is working quite well right now, except that each time I add a new series to the chart, it adds an item to the legend.

Is there a way to remove items from the legend, or do I have to hide the default legend and add my own custom legend pane?

3条回答
在下西门庆
2楼-- · 2019-02-22 07:19

Don't show the legend:

chart.setLegendVisible(false);

You can then create your own custom pane to make your own legend and render it how you wish.

查看更多
疯言疯语
3楼-- · 2019-02-22 07:19

After several failed attempts at implementing various suggestions, I found that the best way to allow a user to show/hide a data series in a JavaFx Chart (or sub-classes thereof) is to extend the chart class you want to use and override its updateLegend() method.

It's quite simple actually. Here's an example using a basic HBox as the legend containing check boxes as the legend items. In this example I have decided to make my LineChart with fixed axes types (CategoryAxis and NumberAxis). You might choose to leave your sub-class with generics for axes.

public class AtopLineChart<X, Y> extends LineChart<String, Number>
{

   /**
    * @param xAxis
    * @param yAxis
    */
   public AtopLineChart(final CategoryAxis xAxis, final NumberAxis yAxis)
   {
      super(xAxis, yAxis);
   }

   /* (non-Javadoc)
    * @see javafx.scene.chart.LineChart#updateLegend()
    */
   @Override
   protected void updateLegend()
   {
      final HBox legend = new HBox();
      legend.setVisible(true);
      for (final javafx.scene.chart.XYChart.Series<String, Number> series : getData())
      {
         final CheckBox cb = new CheckBox(series.getName());
         cb.setUserData(series);
         cb.setSelected(true);
         cb.addEventHandler(ActionEvent.ACTION, e ->
         {
            final CheckBox box = (CheckBox) e.getSource();
            @SuppressWarnings("unchecked")
            final Series<String, Number> s = (Series<String, Number>) box.getUserData();
            s.getNode().setVisible(box.isSelected());
         });
         legend.getChildren().add(cb);
      }
      setLegend(legend);
   }
}

I'll leave it as an exercise for the reader to make the legend more readable, for example, borders around each checkbox and binding the color of the series to the something showing that color in the checkbox for the series.

One other thing, you might want to check the getLegendSide() method to decide which kind of layout container to use for the legend, i.e. HBox for TOP and BOTTOM but VBOX for LEFT and RIGHT. Your choice.

查看更多
Bombasti
4楼-- · 2019-02-22 07:34

You can find a node based on it's type (and optionally style name) using this method:

private static Node findNode(final Parent aParent, final String aClassname, final String aStyleName) {

    if (null != aParent) {
        final ObservableList<Node> children = aParent.getChildrenUnmodifiable();
        if (null != children) {
            for (final Node child : children) {

                String className = child.getClass().getName();

                if (className.contains("$")) {
                    className = className.substring(0, className.indexOf("$"));
                }

                if (0 == aClassname.compareToIgnoreCase(className)) {
                    if ((null == aStyleName) || (0 == aStyleName.length())) {
                        // No aStyleName, just return this:
                        return child;
                    }
                    else {
                        final String styleName = child.getStyleClass().toString();
                        if (0 == aStyleName.compareToIgnoreCase(styleName)) {
                            return child;
                        }
                    }
                }

                if (child instanceof Parent) {
                    final Node node = findNode((Parent) child, aClassname, aStyleName);

                    if (null != node) {
                        return node;
                    }
                }
            }
        }
    }

    return null;
}

Calling it with the chart in question to retrieve the Legend node:

Legend legend = (Legend) findNode(chart, Legend.class.getName(), "chart-legend");

Which you can then iterate through the children and remove the ones you don't want to be displayed:

    for (final Node legendItem : legend.getChildren()) {

        final Label legendLabel = (Label) legendItem;

        if (0 == legendLabel.getText().compareToIgnoreCase("the name of the legend I want hidden (or replaced with some other test)")) {
            legend.getChildren().remove(legendItem);
            break;
        }
    }

JavaFX also has a lookup function which "Finds this Node, or the first sub-node, based on the given CSS selector." and acts similar to the findNode function from this answer.

查看更多
登录 后发表回答