All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.gsi.chart.plugins.DataPointTooltip Maven / Gradle / Ivy

/**
 * Copyright (c) 2016 European Organisation for Nuclear Research (CERN), All Rights Reserved.
 */

package de.gsi.chart.plugins;

import java.util.LinkedList;
import java.util.List;

import de.gsi.chart.Chart;
import de.gsi.chart.XYChart;
import de.gsi.chart.axes.Axis;
import de.gsi.dataset.DataSet;
import de.gsi.dataset.DataSet3D;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.event.EventHandler;
import javafx.geometry.Bounds;
import javafx.geometry.Orientation;
import javafx.geometry.Point2D;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.util.Pair;

/**
 * A tool tip label appearing next to the mouse cursor when placed over a data
 * point's symbol. If symbols are not created/shown for given plot, the tool tip
 * is shown for the closest data point that is within the
 * {@link #pickingDistanceProperty()} from the mouse cursor.
 * 

* CSS style class name: {@value #STYLE_CLASS_LABEL} * * @author Grzegorz Kruk TODO: extend so that label = new Label(); is a generic * object and can also be overwritten with another implementation * (<-> advanced interactor) additional add/remove listener are * needed to edit/update the custom object based on DataPoint (for the * time being private class) */ public class DataPointTooltip extends AbstractDataFormattingPlugin { /** * Name of the CSS class of the tool tip label. */ public static final String STYLE_CLASS_LABEL = "chart-datapoint-tooltip-label"; /** * The default distance between the data point coordinates and mouse cursor * that triggers showing the tool tip label. */ public static final int DEFAULT_PICKING_DISTANCE = 5; private static final int LABEL_X_OFFSET = 15; private static final int LABEL_Y_OFFSET = 5; private final Label label = new Label(); /** * Creates a new instance of DataPointTooltip class with * {{@link #pickingDistanceProperty() picking distance} initialized to * {@value #DEFAULT_PICKING_DISTANCE}. */ public DataPointTooltip() { label.getStyleClass().add(DataPointTooltip.STYLE_CLASS_LABEL); registerInputEventHandler(MouseEvent.MOUSE_MOVED, mouseMoveHandler); } /** * Creates a new instance of DataPointTooltip class. * * @param pickingDistance the initial value for the * {@link #pickingDistanceProperty() pickingDistance} property */ public DataPointTooltip(final double pickingDistance) { this(); setPickingDistance(pickingDistance); } private final DoubleProperty pickingDistance = new SimpleDoubleProperty(this, "pickingDistance", DataPointTooltip.DEFAULT_PICKING_DISTANCE) { @Override protected void invalidated() { if (get() <= 0) { throw new IllegalArgumentException("The " + getName() + " must be a positive value"); } } }; /** * Distance of the mouse cursor from the data point (expressed in display * units) that should trigger showing the tool tip. By default initialized * to {@value #DEFAULT_PICKING_DISTANCE}. * * @return the picking distance property */ public final DoubleProperty pickingDistanceProperty() { return pickingDistance; } /** * Returns the value of the {@link #pickingDistanceProperty()}. * * @return the current picking distance */ public final double getPickingDistance() { return pickingDistanceProperty().get(); } /** * Sets the value of {@link #pickingDistanceProperty()}. * * @param distance the new picking distance */ public final void setPickingDistance(final double distance) { pickingDistanceProperty().set(distance); } private final EventHandler mouseMoveHandler = this::updateToolTip; private void updateToolTip(final MouseEvent event) { final Bounds plotAreaBounds = getChart().getPlotArea().getBoundsInLocal(); // final Bounds plotAreaBounds = getChartPane().getPlotAreaBounds(); final DataPoint dataPoint = findDataPoint(event, plotAreaBounds); if (dataPoint == null) { getChartChildren().remove(label); return; } updateLabel(event, plotAreaBounds, dataPoint); if (!getChartChildren().contains(label)) { getChartChildren().add(label); label.requestLayout(); } } private DataPoint findDataPoint(final MouseEvent event, final Bounds plotAreaBounds) { if (!plotAreaBounds.contains(event.getX(), event.getY())) { return null; } final Point2D mouseLocation = getLocationInPlotArea(event); DataPoint nearestDataPoint = null; Chart chart = getChart(); final DataPoint point = findNearestDataPointWithinPickingDistance(chart, mouseLocation); if (nearestDataPoint == null || point != null && point.distanceFromMouse < nearestDataPoint.distanceFromMouse) { nearestDataPoint = point; } return nearestDataPoint; } private DataPoint findNearestDataPointWithinPickingDistance(final Chart chart, final Point2D mouseLocation) { DataPoint nearestDataPoint = null; if (!(chart instanceof XYChart)) { return null; } final XYChart xyChart = (XYChart) chart; // final double xValue = toDataPoint(xyChart.getYAxis(), // mouseLocation).getXValue().doubleValue(); // TODO: iterate through all axes, renderer and datasets final double xValue = xyChart.getXAxis().getValueForDisplay(mouseLocation.getX()); for (final DataPoint dataPoint : findNeighborPoints(xyChart, xValue)) { // Point2D displayPoint = toDisplayPoint(chart.getYAxis(), // (X)dataPoint.x , dataPoint.y); if (getChart().getFirstAxis(Orientation.HORIZONTAL) instanceof Axis) { final double x = xyChart.getXAxis().getDisplayPosition(dataPoint.x); final double y = xyChart.getYAxis().getDisplayPosition(dataPoint.y); final Point2D displayPoint = new Point2D(x, y); dataPoint.distanceFromMouse = displayPoint.distance(mouseLocation); if (displayPoint.distance(mouseLocation) <= getPickingDistance() && (nearestDataPoint == null || dataPoint.distanceFromMouse < nearestDataPoint.distanceFromMouse)) { nearestDataPoint = dataPoint; } } } return nearestDataPoint; } private List findNeighborPoints(final XYChart chart, final double searchedX) { final List points = new LinkedList<>(); for (final DataSet dataSet : chart.getAllDatasets()) { final Pair neighborPoints = findNeighborPoints(dataSet, searchedX); if (neighborPoints.getKey() != null) { points.add(neighborPoints.getKey()); } if (neighborPoints.getValue() != null) { points.add(neighborPoints.getValue()); } } return points; } /** * Returns label of a data point specified by the index. The label can be * used as a category name if CategoryStepsDefinition is used or for * annotations displayed for data points. * * @param index data point index * @return label of a data point specified by the index or null * if none label has been specified for this data point. */ protected String getDefaultDataLabel(final DataSet dataSet, final int index) { return String.format("%s (%d, %s, %s)", dataSet.getName(), index, Double.toString(dataSet.getX(index)), Double.toString(dataSet.getY(index))); } protected String getDataLabelSafe(final DataSet dataSet, final int index) { String lable = dataSet.getDataLabel(index); if (lable == null) { return getDefaultDataLabel(dataSet, index); } return lable; } /** * Handles series that have data sorted or not sorted with respect to X * coordinate. * * @param dataSet data set * @param searchedX x coordinate * @return return neighouring data points */ private Pair findNeighborPoints(final DataSet dataSet, final double searchedX) { int prevIndex = -1; int nextIndex = -1; double prevX = Double.MIN_VALUE; double nextX = Double.MAX_VALUE; final int nDataCount = (dataSet instanceof DataSet3D) ? ((DataSet3D) dataSet).getXDataCount() : dataSet.getDataCount(); for (int i = 0, size = nDataCount; i < size; i++) { final double currentX = dataSet.getX(i); if (currentX < searchedX) { if (prevX < currentX) { prevIndex = i; prevX = currentX; } } else if (nextX > currentX) { nextIndex = i; nextX = currentX; } } final DataPoint prevPoint = prevIndex == -1 ? null : new DataPoint(getChart(), dataSet.getX(prevIndex), dataSet.getY(prevIndex), getDataLabelSafe(dataSet, prevIndex)); final DataPoint nextPoint = nextIndex == -1 || nextIndex == prevIndex ? null : new DataPoint(getChart(), dataSet.getX(nextIndex), dataSet.getY(nextIndex), getDataLabelSafe(dataSet, nextIndex)); return new Pair<>(prevPoint, nextPoint); } // @SuppressWarnings({ "rawtypes", "unchecked" }) // private List> castXToNumber(final Series // series) { // return series.getData(); // } private void updateLabel(final MouseEvent event, final Bounds plotAreaBounds, final DataPoint dataPoint) { label.setText(formatLabel(dataPoint)); // TODO continue here (new formatting etc.) final double mouseX = event.getX(); final double mouseY = event.getY(); final double width = label.prefWidth(-1); final double height = label.prefHeight(width); double xLocation = mouseX + DataPointTooltip.LABEL_X_OFFSET; double yLocation = mouseY - DataPointTooltip.LABEL_Y_OFFSET - height; if (xLocation + width > plotAreaBounds.getMaxX()) { xLocation = mouseX - DataPointTooltip.LABEL_X_OFFSET - width; } if (yLocation < plotAreaBounds.getMinY()) { yLocation = mouseY + DataPointTooltip.LABEL_Y_OFFSET; } label.resizeRelocate(xLocation, yLocation, width, height); } private String formatDataPoint(final DataPoint dataPoint) { return String.format("DataPoint@(%.3f,%.3f)", dataPoint.x, dataPoint.y); // return formatData(dataPoint.chart.getYAxis(), dataPoint.x, // dataPoint.y); } protected String formatLabel(DataPoint dataPoint) { return String.format("'%s'\n%s", dataPoint.label, formatDataPoint(dataPoint)); } protected class DataPoint { final Chart chart; final double x; final double y; final String label; double distanceFromMouse; DataPoint(final Chart chart, final double x, final double y, final String label) { this.chart = chart; this.x = x; this.y = y; this.label = label; } public Chart getChart() { return chart; } public double getX() { return x; } public double getY() { return y; } public String getLabel() { return label; } public double getDistanceFromMouse() { return distanceFromMouse; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy