2015-02-17 5 views
1

Я нашел много примеров того, как добавить подсказку в LineChart, но нет информации или примера того, как добавить всплывающую подсказку в Live LineChart.Tooltip on Live LineChart

import java.util.concurrent.ConcurrentLinkedQueue; 
import java.util.concurrent.ExecutorService; 
import java.util.concurrent.Executors; 
import java.util.concurrent.ThreadFactory; 
import java.util.logging.Level; 
import java.util.logging.Logger; 
import javafx.animation.AnimationTimer; 
import javafx.application.Application; 
import javafx.beans.value.ChangeListener; 
import javafx.beans.value.ObservableValue; 
import javafx.scene.Node; 
import javafx.scene.Scene; 
import javafx.scene.chart.AreaChart; 
import javafx.scene.chart.NumberAxis; 
import javafx.scene.chart.XYChart.Data; 
import javafx.scene.chart.XYChart.Series; 
import javafx.scene.control.Tooltip; 
import javafx.stage.Stage; 

public class MainApp extends Application 
{ 
    private static final int MAX_DATA_POINTS = 50; 

    private Series series; 
    private Series series2; 
    private int xSeriesData = 0; 
    private ConcurrentLinkedQueue<Number> dataQ = new ConcurrentLinkedQueue<Number>(); 
    private ConcurrentLinkedQueue<Number> dataQ2 = new ConcurrentLinkedQueue<Number>(); 
    private ExecutorService executor; 
    private AddToQueue addToQueue; 
    private NumberAxis xAxis; 

    private void init(Stage primaryStage) 
    { 
     xAxis = new NumberAxis(0, MAX_DATA_POINTS, MAX_DATA_POINTS/10); 
     xAxis.setForceZeroInRange(false); 
     xAxis.setAutoRanging(false); 

     NumberAxis yAxis = new NumberAxis(); 
     yAxis.setAutoRanging(true); 

     //-- Chart 
     final AreaChart<Number, Number> sc = new AreaChart<Number, Number>(xAxis, yAxis) 
     { 
      // Override to remove symbols on each data point 
      @Override 
      protected void dataItemAdded(Series<Number, Number> series, int itemIndex, Data<Number, Number> item) 
      { 
      } 
     }; 

     sc.setAnimated(false); 
     sc.setId("liveAreaChart"); 
     sc.setTitle("Animated Area Chart"); 

     //-- Chart Series 
     series = new AreaChart.Series<Number, Number>(); 
     series.setName("Area Chart Series"); 
     series2 = new AreaChart.Series<Number, Number>(); 
     series2.setName("Area Chart Series"); 
     sc.getData().addAll(series, series2); 

     xAxis.setTickLabelsVisible(false); 
     xAxis.setTickMarkVisible(false); 
     xAxis.setMinorTickVisible(false); 

     primaryStage.setScene(new Scene(sc)); 
    } 

    @Override 
    public void start(Stage primaryStage) throws Exception 
    { 
     init(primaryStage); 
     primaryStage.show(); 

     //-- Prepare Executor Services 
     executor = Executors.newCachedThreadPool(new ThreadFactory() 
     { 
      @Override 
      public Thread newThread(Runnable r) 
      { 
       Thread thread = new Thread(r); 
       thread.setDaemon(true); 
       return thread; 
      } 
     }); 
     addToQueue = new AddToQueue(); 
     executor.execute(addToQueue); 
     //-- Prepare Timeline 
     prepareTimeline(); 
    } 

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

    private class AddToQueue implements Runnable 
    { 
     @Override 
     public void run() 
     { 
      try 
      { 
       // add a item of random data to queue 
       dataQ.add(Math.random()); 
       dataQ2.add(Math.random()); 
       Thread.sleep(200); 
       executor.execute(this); 
      } 
      catch (InterruptedException ex) 
      { 
       Logger.getLogger(MainApp.class.getName()).log(Level.SEVERE, null, ex); 
      } 
     } 
    } 

    //-- Timeline gets called in the JavaFX Main thread 
    private void prepareTimeline() 
    { 
     // Every frame to take any data from queue and add to chart 
     new AnimationTimer() 
     { 
      @Override 
      public void handle(long now) 
      { 
       addDataToSeries(); 
      } 
     }.start(); 
    } 

    private void addDataToSeries() 
    { 
     for (int i = 0; i < 20; i++) 
     { //-- add 20 numbers to the plot+ 
      if (dataQ.isEmpty()) 
       break; 

      Data data = new AreaChart.Data(xSeriesData++, dataQ.remove()); 

      series.getData().add(data); 

      data.nodeProperty().addListener(new ChangeListener<Node>() 
      { 
       @Override 
       public void changed(ObservableValue<? extends Node> arg0, Node arg1, 
        Node arg2) 
       { 
        Tooltip t = new Tooltip(data.getYValue().toString() + '\n' + data.getXValue()); 
        Tooltip.install(arg2, t); 
        data.nodeProperty().removeListener(this); 
       } 
      }); 

      if (dataQ2.isEmpty()) 
       break; 
      series2.getData().add(new AreaChart.Data(xSeriesData, dataQ2.remove())); 
     } 
     // remove points to keep us at no more than MAX_DATA_POINTS 
     if (series.getData().size() > MAX_DATA_POINTS) 
     { 
      series.getData().remove(0, series.getData().size() - MAX_DATA_POINTS); 
     } 

     // remove points to keep us at no more than MAX_DATA_POINTS 
     if (series2.getData().size() > MAX_DATA_POINTS) 
     { 
      series2.getData().remove(0, series2.getData().size() - MAX_DATA_POINTS); 
     } 

     // update 
     xAxis.setLowerBound(xSeriesData - MAX_DATA_POINTS); 
     xAxis.setUpperBound(xSeriesData - 1); 
    } 
} 

Это результат, который я хотел бы создать:

enter image description here

Если возможно я установить setCreateSymbols (ложь);

+0

еще раз, код в основном не связан с вашей проблемой и отсутствует какой-либо намек на то, что вы пробовали ... почему бы не привести примеры со статическими данными не к динамическим данным? – kleopatra

+0

@ kleopatra Я добавил код, который я тестировал, но когда я запускаю код, я не вижу подсказки. –

+0

в порядке, см. Сейчас: но это не связано с динамической природой, это связано с тем, что у них нет символа (aka: node) - так что прикрепить всплывающую подсказку нет до – kleopatra

ответ

2

Прежде всего, используйте обычный AreaChart вместо анонимного подкласса, который вы используете (т. Е. Не переопределяете метод dataAddedItem(...)). Этот метод создает узел по умолчанию для отображения для точки данных, если он уже не существует (это огромное нарушение разделения данных из презентации, imho, но мы ничего не можем с этим сделать ...); вам, очевидно, нужна графика, чтобы прикрепить всплывающую подсказку.

После того, как точка данных имеет узел, вы не должны слушать изменения, поэтому в методе addDataToSeries(), удалить слушателя и просто заменить его

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

Или просто создать свой собственный графику, прикрепите к ней всплывающую подсказку и передайте ее data.setNode(...);.

У вас все еще будет общая проблема юзабилити; Я не вижу, как пользователь будет наводиться на точку данных на диаграмме, когда все пролетает со скоростью 5 единиц в секунду. И даже если бы они могли, к тому времени, подсказка появилась очки бы переехал, так что значения было бы неправильно ...

Update:

Просто для удовольствия, я попытался это:

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 question и this one.

+0

плюс +1 - не в последнюю очередь за 1000% огромное нарушение "! Это невероятно раздражает ... BTW: нет необходимости в узле в точке данных (только если отображаются символы), вместо этого установите всплывающую подсказку в серии и обновите ее текст. – kleopatra

+0

:-) альтернативы хороши, и, как всегда, я как ваша магия привязки. Всплывающая подсказка на диаграмме попадает в проблему, если есть несколько серий – kleopatra

1

У меня действительно не было проблемы, которую вы хотите решить (просто добавление всплывающей подсказки должно быть точно таким же), но если вы хотите «обновить» свою подсказку с помощью liveata, вы можете просто установить привязку между данными и если всплывающая подсказка не должна сама обновлять данные всплывающей подсказки в Platform.runLater().

+0

Я добавил код всплывающей подсказки.Когда я запускаю пример, ничего не отображается. –

+0

Возможно, потому, что вы делаете новую подсказку каждый раз, когда слушатель активируется. Попробуйте сделать одну подсказку и просто измените отображаемое значение. – Juce

+0

Я не уверен, какую часть кода мне нужно изменить. Можете ли вы показать мне пример кода? –

2

Ответ на неявную часть вопроса: как установить всплывающую подсказку, отображающую текущие значения x/y, если нет символов, то есть у данных нет узла?И только для статической диаграммы (или тот, который разумно медленное изменения таким образом, что расположение/значение подсказки не будет иметь смысл, показывая)

Есть несколько проблем, решить

  • подсказка должна быть установленным на узле серии: тот же трюк, что и при установке на узле данных, выполненных в слушателе, на его nodeProperty, только тогда мы можем быть уверены, что узел создан
  • его текст должен быть обновлен, когда показ: сделано onShowing (обратите внимание на bug in the setter, поэтому нам нужно использовать недвижимость напрямую)
  • это текст должен быть связан с местоположением мыши его запускающего события: поскольку, похоже, не существует any api that allows access события запуска, мы должны сами отслеживать это местоположение, внизу в перемещении мыши, в котором хранится текущий экран мыши место в свойствах подсказке
  • преобразование координат из мышиных координат в «мир» график-координаты

пример:

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()); 
} 

КСТАТИ: полностью согласен с Джеймсом по вопросу применяемости: передача данных по диаграмме не может быть действительно обработана всплывающей подсказкой данные/серия. Если вам это действительно нужно, вам придется реализовать какой-то пользовательский маркер (например, вертикальную линию), который добавляется на жесте мыши, удерживайте эту строку липкой (ака: sync'ed до движущихся значений x) к данным, и прикрепите всплывающую подсказку к этой строке.

+0

Ах, перекрестная почта с моим обновлением ... также частично вдохновлена ​​[на ваш вопрос] (http://stackoverflow.com/questions/28585330/tooltip-how- к-GET-мыши-координаты, которые запускаемых-на-наконечник). Я просто привязал всплывающую подсказку к диаграмме, хотя ... –