我找到了许多如何在LineChart上添加Tooltip的示例,但没有提供如何在Live LineChart上添加Tooltip的信息或示例。
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);
}
}
这是我想要创建的结果:
如果可能,我设置setCreateSymbols(false);
答案 0 :(得分:2)
首先,使用常规AreaChart
代替您正在使用的匿名子类(即不要覆盖dataAddedItem(...)
方法)。如果没有数据点,那么该方法会创建一个默认节点来显示数据点(这很大程度上违反了将数据与表示分离,imho,但我们无能为力......);你显然需要一个图形来附加工具提示。
数据点有节点后,您不需要收听更改,因此在addDataToSeries()
方法中,删除侦听器并将其替换为
Tooltip t = new Tooltip(data.getYValue().toString() + '\n' + data.getXValue());
Tooltip.install(data.getNode(), t);
或者,只需创建自己的图片,附加工具提示,然后将其传递给data.setNode(...);
。
您仍然会遇到一般性问题;当一切以每秒5个单位飞行时,我不知道用户如何将鼠标悬停在图表中的数据点上。即使它们可以,当工具提示出现时,点会移动,因此值不正确......
更新
为了好玩,我尝试了这个:
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的想法。
答案 1 :(得分:2)
回答问题的隐含部分:如果没有符号,如何安装显示当前x / y值的工具提示,即数据没有节点?并且只针对静态图表(或者合理缓慢变化的图表,使得工具提示的位置/值在显示时没有意义)
要解决几个问题
一个例子:
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:完全同意James的可用性问题:数据/系列的工具提示无法真正处理整个图表中的数据竞争。如果你真的需要它,你将不得不实现一些自定义标记(如垂直线),它是在鼠标手势上添加的,保持该线粘性(也就是说:同步到移动的x值)到数据,并将工具提示附加到该行。
答案 2 :(得分:1)
我没有真正解决你想要解决的问题(简单地添加工具提示应该完全相同)但是如果你想要更新&#34;你的工具提示与livingata你可以简单地在数据之间进行绑定,如果工具提示不应该自我更新,则更改Platform.runLater()中工具提示的数据。