Tooltip on Live LineChart

前端 未结 3 1136
情书的邮戳
情书的邮戳 2020-12-20 05:57

I found many examples how to add Tooltip on a LineChart but no information or example how to add Tooltip on Live LineChart.

import java.util.concurrent.Concu         


        
相关标签:
3条回答
  • 2020-12-20 05:57

    First, use a regular AreaChart instead of the anonymous subclass you are using (i.e. don't override the dataAddedItem(...) method). That method creates a default node to display for the data point if none already exists (this is a huge violation of separating data from presentation, imho, but there's nothing we can do about that...); you obviously need a graphic there to attach the tooltip to.

    Once the data point has a node, you don't need to listen to the changes, so in your addDataToSeries() method, remove the listener and just replace it with

            Tooltip t = new Tooltip(data.getYValue().toString() + '\n' + data.getXValue());
            Tooltip.install(data.getNode(), t);
    

    Or, just create your own graphic, attach a tooltip to it, and pass it to data.setNode(...);.

    You will still have a general usability problem; I don't see how the user is going to hover over a data point in the chart when everything is flying by at 5 units per second. And even if they could, by the time the tooltip appeared the points would have moved, so the values would be incorrect...

    Update:

    Just for fun, I tried this:

        ObjectProperty<Point2D> mouseLocationInScene = new SimpleObjectProperty<>();
    
        Tooltip tooltip = new Tooltip();
    
        sc.addEventHandler(MouseEvent.MOUSE_MOVED, evt -> {
            if (! tooltip.isShowing()) {
                mouseLocationInScene.set(new Point2D(evt.getSceneX(), evt.getSceneY()));
            }
        });
    
        tooltip.textProperty().bind(Bindings.createStringBinding(() -> {
            if (mouseLocationInScene.isNull().get()) {
                return "" ;
            }
            double xInXAxis = xAxis.sceneToLocal(mouseLocationInScene.get()).getX() ;
            double x = xAxis.getValueForDisplay(xInXAxis).doubleValue();
            double yInYAxis = yAxis.sceneToLocal(mouseLocationInScene.get()).getY() ;
            double y = yAxis.getValueForDisplay(yInYAxis).doubleValue() ;
            return String.format("[%.3f, %.3f]", x, y);
        }, mouseLocationInScene, xAxis.lowerBoundProperty(), xAxis.upperBoundProperty(),
        yAxis.lowerBoundProperty(), yAxis.upperBoundProperty()));
    
        Tooltip.install(sc, tooltip);
    

    This sets a tooltip on the chart that updates both as you move the mouse and as the chart scrolls below. This combines ideas from this question and this one.

    0 讨论(0)
  • 2020-12-20 06:08

    I didn't really got the problem you want to solve (simply adding a tooltip should be exactly the same) but if you want to "update" your tooltip with the livedata you could simply make a bind between the data and if the tooltip shouldn't update itself change the data of the tooltip in a Platform.runLater().

    0 讨论(0)
  • 2020-12-20 06:13

    Answering an implicit part of the question: how to install a tooltip that's showing the current x/y values if there are no symbols, that is the data has no node? And only for a static chart (or one that's reasonable slow changing such that the location/value of the tooltip wouldn't make sense while showing)

    There are several problems to solve

    • the tooltip has to be installed on the node of the series: same trick as with installing on the node of the data - done in a listener to its nodeProperty, only then can we be certain that the node is created
    • it's text has to be updated when showing: done onShowing (note a bug in the setter, so we need to use the property directly)
    • it's text should be related to the mouse location of its triggering event: as there doesn't seem to be any api that allows access to the triggering event we have to keep track of that location ourselves, below in a mouse-moved that stores the current mouse screen location in the properties of the tooltip
    • coordinate transformation from mouse-coordinates to "world" chart-coordinates

    An example:

    public class ToolTipOnChartSeries extends Application {
    
        private static final Object MOUSE_TRIGGER_LOCATION = "tooltip-last-location";
    
        private ObservableList<XYChart.Series<String, Double>> getChartData() {
            double javaValue = 17.56;
            ObservableList<XYChart.Series<String, Double>> answer = FXCollections.observableArrayList();
            Series<String, Double> java = new Series<String, Double>();
            java.setName("java");
            Tooltip t = new Tooltip();
            t.setOnShowing(e -> {
                Point2D screen = (Point2D) t.getProperties().get(MOUSE_TRIGGER_LOCATION);
                if (screen == null) return;
                XYChart chart = java.getChart();
                double localX = chart.getXAxis().screenToLocal(screen).getX();
                double localY = chart.getYAxis().screenToLocal(screen).getY();
                Object xValue = chart.getXAxis().getValueForDisplay(localX);
                Object yValue = chart.getYAxis().getValueForDisplay(localY);
                t.textProperty().set("x/y: " + t.getX() + " / " + t.getY() 
                        + "\n localX " + localX + "/" + xValue 
                        + "\n localY " + localY + "/" + yValue 
    
                        );
            });
            java.nodeProperty().addListener(new ChangeListener<Node>()
            { 
                @Override
                public void changed(ObservableValue<? extends Node> arg0, Node arg1,
                    Node node)
                {
                    Tooltip.install(node, t);
                    node.setOnMouseMoved(e -> {
                        Point2D screen = new Point2D(e.getScreenX(), e.getScreenY());
                        t.getProperties().put(MOUSE_TRIGGER_LOCATION, screen);
                    });
                    java.nodeProperty().removeListener(this);
                }
            });
            for (int i = 2011; i < 2021; i++) {
                // adding a tooltip to the data node
                final XYChart.Data data = new XYChart.Data(Integer.toString(i), javaValue);
                java.getData().add(data);
                javaValue = javaValue + Math.random() - .5;
            }
            answer.addAll(java); //, c, cpp);
            return answer;
        }
    
        @Override
        public void start(Stage primaryStage) {
            CategoryAxis xAxis = new CategoryAxis();
            NumberAxis yAxis = new NumberAxis();
            LineChart lineChart = new LineChart(xAxis, yAxis);
            lineChart.setCreateSymbols(false);
            lineChart.setData(getChartData());
            lineChart.setTitle("speculations");
            primaryStage.setTitle("LineChart example");
    
            StackPane root = new StackPane();
            root.getChildren().add(lineChart);
            primaryStage.setScene(new Scene(root)); //, 400, 250));
            primaryStage.setTitle(FXUtils.version());
            primaryStage.show();
        }
    
        public static void main(String[] args) {
            launch(args);
        }
    
        @SuppressWarnings("unused")
        private static final Logger LOG = Logger.getLogger(ToolTipOnChartSeries.class
                .getName());
    } 
    

    BTW: fully agree with James on the useability issue: data racing across the chart can't really be handled by a tooltip on the data/series. If you really need that, you'll have to implement some custom marker (like f.i. a vertical line) that's added on a mouse gesture, keep that line sticky (aka: sync'ed to the moving x-values) to the data, and attach the tooltip to that line.

    0 讨论(0)
提交回复
热议问题