JavaFX Use diagram Legend to toggle show / hide Possible series?

Can a chart legend be used to toggle show / hide series?

I got LineChart

with the legend and too much Series

for you to not read the information well. I was wondering if there is a way to use a legend to switch the series to show / hide?

Most of my names are Series

quite long, and it looks very strange if they are written twice in the legend, so you know which color wich belongs to Series

and the second time besides CheckBox

to switch them.

Edit1: Maybe I was unclear, even if there is no built-in function for this, I could use some input for what workaroud would look like because I can't think of anything.

+3


source to share


3 answers


This is how I solved it - I don't know of a simpler built-in solution

LineChart<Number, Number> chart;

for (Node n : chart.getChildrenUnmodifiable()) {
    if (n instanceof Legend) {
        Legend l = (Legend) n;
        for (Legend.LegendItem li : l.getItems()) {
            for (XYChart.Series<Number, Number> s : chart.getData()) {
                if (s.getName().equals(li.getText())) {
                    li.getSymbol().setCursor(Cursor.HAND); // Hint user that legend symbol is clickable
                    li.getSymbol().setOnMouseClicked(me -> {
                        if (me.getButton() == MouseButton.PRIMARY) {
                            s.getNode().setVisible(!s.getNode().isVisible()); // Toggle visibility of line
                            for (XYChart.Data<Number, Number> d : s.getData()) {
                                if (d.getNode() != null) {
                                    d.getNode().setVisible(s.getNode().isVisible()); // Toggle visibility of every node in the series
                                }
                            }
                        }
                    });
                    break;
                }
            }
        }
    }
}

      



You need to run this code once on your diagram ( LineChart

in this example, but you can probably adapt it to any other diagram). I find the child Legend

and then loop through all of its elements. I am mapping the legend item to the correct series based on the name - in my experience they always match and I couldn't find a better way to match them. Then it's just a matter of adding the correct event handler to that particular legend item.

+4


source


For reference, a similar approach works with JFreeChart

in JavaFX as shown here . Adapted from this example, the option below adds ChartMouseListenerFX

to ChartViewer

. Click on the series or its legend element to make the series invisible; click elsewhere to restore it.

image



import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.entity.ChartEntity;
import org.jfree.chart.entity.LegendItemEntity;
import org.jfree.chart.entity.XYItemEntity;
import org.jfree.chart.fx.ChartViewer;
import org.jfree.chart.fx.interaction.ChartMouseEventFX;
import org.jfree.chart.fx.interaction.ChartMouseListenerFX;
import org.jfree.chart.labels.StandardXYToolTipGenerator;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;

/**
 * @see https://stackoverflow.com/a/44967809/230513
 * @see /questions/982500/choose-the-series-of-data-that-you-want-to-display/3395077#3395077
 */
public class VisibleTest extends Application {

    @Override
    public void start(Stage stage) {
        XYSeriesCollection dataset = new XYSeriesCollection();
        for (int i = 0; i < 3; i++) {
            XYSeries series = new XYSeries("value" + i);
            for (double t = 0; t < 2 * Math.PI; t += 0.5) {
                series.add(t, Math.sin(t) + i);
            }
            dataset.addSeries(series);
        }
        NumberAxis xAxis = new NumberAxis("domain");
        NumberAxis yAxis = new NumberAxis("range");
        XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer(true, true);
        renderer.setBaseToolTipGenerator(new StandardXYToolTipGenerator());
        XYPlot plot = new XYPlot(dataset, xAxis, yAxis, renderer);
        JFreeChart chart = new JFreeChart("Test", plot);
        ChartViewer viewer = new ChartViewer(chart);
        viewer.addChartMouseListener(new ChartMouseListenerFX() {
            @Override
            public void chartMouseClicked(ChartMouseEventFX e) {
                ChartEntity ce = e.getEntity();
                if (ce instanceof XYItemEntity) {
                    XYItemEntity item = (XYItemEntity) ce;
                    renderer.setSeriesVisible(item.getSeriesIndex(), false);
                } else if (ce instanceof LegendItemEntity) {
                    LegendItemEntity item = (LegendItemEntity) ce;
                    Comparable key = item.getSeriesKey();
                    renderer.setSeriesVisible(dataset.getSeriesIndex(key), false);
                } else {
                    for (int i = 0; i < dataset.getSeriesCount(); i++) {
                        renderer.setSeriesVisible(i, true);
                    }
                }
            }

            @Override
            public void chartMouseMoved(ChartMouseEventFX e) {}
        });
        stage.setScene(new Scene(viewer));
        stage.setTitle("JFreeChartFX");
        stage.setWidth(640);
        stage.setHeight(480);
        stage.show();
    }

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

      

+2


source


Thanks for @sillyfly's answer. I was able to post this to Kotlin. It turns out cleanly and concisely with the notation forEach

and filter

.

(Kotlin folk, please let me know any improvements, thanks).

        lineChart.childrenUnmodifiable.forEach { if (it is Legend) {
            it.items.forEach {
                val li = it
                lineChart.data.filter { it.name == li.text }.forEach {
                    li.symbol.cursor = Cursor.HAND
                    val s = it
                    li.symbol.setOnMouseClicked { if (it.button == MouseButton.PRIMARY) {
                        s.node.isVisible = !s.node.isVisible
                        s.data.forEach { it.node.isVisible = !it.node.isVisible }
                    }}
                }
            }
        }
    }

      

0


source







All Articles