io.fair_acc.chartfx.plugins.AbstractSingleValueIndicator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of chartfx Show documentation
Show all versions of chartfx Show documentation
This charting library ${project.artifactId}- is an extension
in the spirit of Oracle's XYChart and performance/time-proven JDataViewer charting functionalities.
Emphasis was put on plotting performance for both large number of data points and real-time displays,
as well as scientific accuracies leading to error bar/surface plots, and other scientific plotting
features (parameter measurements, fitting, multiple axes, zoom, ...).
The newest version!
/*
* Copyright (c) 2017 European Organisation for Nuclear Research (CERN), All Rights Reserved.
*/
package io.fair_acc.chartfx.plugins;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.scene.Cursor;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.scene.shape.Polygon;
import io.fair_acc.chartfx.axes.Axis;
import io.fair_acc.chartfx.utils.PropUtil;
import io.fair_acc.dataset.events.ChartBits;
/**
* Plugin indicating a specific X or Y value as a line drawn on the plot area, with an optional {@link #textProperty()
* text label} describing the value.
*
* @author mhrabia
*/
public abstract class AbstractSingleValueIndicator extends AbstractValueIndicator implements ValueIndicator {
/**
* The default distance between the data point coordinates and mouse cursor that triggers shifting the line.
*/
protected static final int DEFAULT_PICKING_DISTANCE = 30;
protected static final double MIDDLE_POSITION = 0.5;
protected static final String STYLE_CLASS_LABEL = "value-indicator-label";
protected static final String STYLE_CLASS_LINE = "value-indicator-line";
protected static final String STYLE_CLASS_MARKER = "value-indicator-marker";
protected static double triangleHalfWidth = 5.0;
private boolean autoRemove = false;
/**
* Line indicating the value.
*/
protected final Line line = new Line();
protected final Line pickLine = new Line();
/**
* small triangle marker as handler to shift the line marker
*/
protected final Polygon triangle = new Polygon();
private final DoubleProperty pickingDistance = new SimpleDoubleProperty(this, "pickingDistance",
DEFAULT_PICKING_DISTANCE) {
@Override
protected void invalidated() {
if (get() <= 0) {
throw new IllegalArgumentException("The " + getName() + " must be a positive value");
}
}
};
private final DoubleProperty value = new SimpleDoubleProperty(this, "value") {
@Override
protected void invalidated() {
runPostLayout();
}
};
private final DoubleProperty labelPosition = new SimpleDoubleProperty(this, "labelPosition", 0.5) {
@Override
protected void invalidated() {
if (get() < 0 || get() > 1) {
throw new IllegalArgumentException("labelPosition must be in rage [0,1]");
}
runPostLayout();
}
};
/**
* Creates a new instance of AbstractSingleValueIndicator.
*
* @param axis reference axis
* @param value a X value to be indicated
* @param text the text to be shown by the label. Value of {@link #textProperty()}.
*/
protected AbstractSingleValueIndicator(Axis axis, final double value, final String text) {
super(axis, text);
setValue(value);
initLine();
initTriangle();
editableIndicatorProperty().addListener((ch, o, n) -> updateMouseListener(n));
updateMouseListener(isEditable());
// remove triangle from chart foregrond when the chart is removed
chartProperty().addListener((p, o, n) -> {
if (o != null) {
o.getPlotForeground().getChildren().remove(triangle);
}
});
// Need to add them so that at initialization of the stage the CCS is
// applied and we can calculate label's width and height
getChartChildren().addAll(line, label);
PropUtil.runOnChange(getBitState().onAction(ChartBits.ChartPluginState), this.value);
}
/**
* Returns the value of the {@link #labelPositionProperty()}.
*
* @return the relative position of the {@link #textProperty() text label}
*/
public final double getLabelPosition() {
return labelPositionProperty().get();
}
/**
* Returns the value of the {@link #pickingDistanceProperty()}.
*
* @return the current picking distance
*/
public final double getPickingDistance() {
return pickingDistanceProperty().get();
}
/**
* Returns the indicated value.
*
* @return indicated value
*/
@Override
public final double getValue() {
return valueProperty().get();
}
private void initLine() {
// mouse transparent if not editable
line.setMouseTransparent(true);
pickLine.setPickOnBounds(true);
pickLine.setStroke(Color.TRANSPARENT);
pickLine.setStrokeWidth(getPickingDistance());
pickLine.mouseTransparentProperty().bind(editableIndicatorProperty().not());
pickLine.setOnMousePressed(mouseEvent -> {
if (mouseEvent.isPrimaryButtonDown()) {
/*
* Record a delta distance for the drag and drop operation. Because layoutLine() sets the start/end points
* we have to use these here. It is enough to use the start point. For X indicators, start x and end x are
* identical and for Y indicators start y and end y are identical.
*/
dragDelta.x = pickLine.getStartX() - mouseEvent.getX();
dragDelta.y = -(pickLine.getStartY() - mouseEvent.getY());
pickLine.setCursor(Cursor.MOVE);
mouseEvent.consume();
}
});
}
private void initTriangle() {
triangle.visibleProperty().bind(editableIndicatorProperty());
triangle.mouseTransparentProperty().bind(editableIndicatorProperty().not());
triangle.setPickOnBounds(true);
triangle.setManaged(false); // prevent the triangle from relayouting the whole chart
triangle.setOpacity(0.7);
final double a = AbstractSingleValueIndicator.triangleHalfWidth;
triangle.getPoints().setAll(-a, -a, -a, +a, +a, +a, +a, -a);
triangle.setOnMousePressed(mouseEvent -> {
/*
* Record a delta distance for the drag and drop operation. Because the whole node is translated in
* layoutMarker we use the layout position here.
*/
dragDelta.x = triangle.getLayoutX() - mouseEvent.getX();
dragDelta.y = triangle.getLayoutY() - mouseEvent.getY();
triangle.setCursor(Cursor.MOVE);
mouseEvent.consume();
});
}
/**
* @return {@code true} indicator should be removed if there is no listener attached to it
*/
public boolean isAutoRemove() {
return autoRemove;
}
/**
* Relative position, between 0.0 (left, bottom) and 1.0 (right, top) of the description {@link #textProperty()
* label} in the plot area.
*
* Default value: 0.5
*
*
* @return labelPosition property
*/
public final DoubleProperty labelPositionProperty() {
return labelPosition;
}
/**
* Sets the line coordinates.
*
* @param startX start x coordinate
* @param startY start y coordinate
* @param endX stop x coordinate
* @param endY stop y coordinate
*/
protected void layoutLine(final double startX, final double startY, final double endX, final double endY) {
line.setStartX(startX);
line.setStartY(startY);
line.setEndX(endX);
line.setEndY(endY);
pickLine.setStartX(startX);
pickLine.setStartY(startY);
pickLine.setEndX(endX);
pickLine.setEndY(endY);
addChildNodeIfNotPresent(pickLine);
addChildNodeIfNotPresent(line);
}
/**
* Sets the marker coordinates.
*
* @param startX start x coordinate
* @param startY start y coordinate
* @param endX stop x coordinate
* @param endY stop y coordinate
*/
protected void layoutMarker(final double startX, final double startY, final double endX, final double endY) {
if (!triangle.isVisible()) {
return;
}
triangle.setTranslateX(startX);
triangle.setTranslateY(startY);
triangle.toFront();
// triangle has to be put onto the plot foreground to be able to put it on top of axes
// is removed when the chart is changed
// addChildNodeIfNotPresent(triangle);
if (!getChart().getPlotForeground().getChildren().contains(triangle)) {
getChart().getPlotForeground().getChildren().add(triangle);
}
}
/**
* Distance of the mouse cursor from the line (in pixel) that should trigger the moving of the line. By default
* initialized to {@value #DEFAULT_PICKING_DISTANCE}.
*
* @return the picking distance property
*/
public final DoubleProperty pickingDistanceProperty() {
return pickingDistance;
}
/**
* @param autoRemove {@code true} indicator should be removed if there is no listener attached to it
*/
public void setAutoRemove(boolean autoRemove) {
this.autoRemove = autoRemove;
}
/**
* Sets the new value of the {@link #labelPositionProperty()}.
*
* @param value the label position, between 0.0 and 1.0 (both inclusive)
*/
public final void setLabelPosition(final double value) {
labelPositionProperty().set(value);
}
/**
* Sets the value of {@link #pickingDistanceProperty()}.
*
* @param distance the new picking distance
*/
public final void setPickingDistance(final double distance) {
pickingDistanceProperty().set(distance);
}
/**
* Sets the value that should be indicated.
*
* @param newValue value to be indicated
*/
@Override
public final void setValue(final double newValue) {
valueProperty().set(newValue);
}
private void updateMouseListener(final boolean state) {
if (state) {
pickLine.setOnMouseReleased(mouseEvent -> pickLine.setCursor(Cursor.HAND));
pickLine.setOnMouseEntered(mouseEvent -> pickLine.setCursor(Cursor.HAND));
triangle.setOnMouseReleased(mouseEvent -> triangle.setCursor(Cursor.HAND));
triangle.setOnMouseEntered(mouseEvent -> triangle.setCursor(Cursor.HAND));
label.setOnMouseReleased(mouseEvent -> label.setCursor(Cursor.HAND));
label.setOnMouseEntered(mouseEvent -> label.setCursor(Cursor.HAND));
} else {
pickLine.setOnMouseReleased(null);
pickLine.setOnMouseEntered(null);
triangle.setOnMouseReleased(null);
triangle.setOnMouseEntered(null);
label.setOnMouseReleased(null);
label.setOnMouseEntered(null);
}
}
/**
* Value indicated by this plugin.
*
* @return value property
*/
@Override
public final DoubleProperty valueProperty() {
return value;
}
}