Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
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, ...).
package de.gsi.chart;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import com.sun.javafx.charts.ChartLayoutAnimator;
import com.sun.javafx.css.converters.BooleanConverter;
import com.sun.javafx.css.converters.EnumConverter;
import de.gsi.chart.axes.Axis;
import de.gsi.chart.axes.spi.DefaultNumericAxis;
import de.gsi.dataset.DataSet;
import de.gsi.dataset.event.EventListener;
import de.gsi.chart.legend.Legend;
import de.gsi.chart.legend.spi.DefaultLegend;
import de.gsi.chart.plugins.ChartPlugin;
import de.gsi.chart.renderer.Renderer;
import de.gsi.chart.renderer.spi.LabelledMarkerRenderer;
import de.gsi.chart.ui.HiddenSidesPane;
import de.gsi.chart.ui.ResizableCanvas;
import de.gsi.chart.ui.SidesPane;
import de.gsi.chart.ui.css.StylishBooleanProperty;
import de.gsi.chart.ui.css.StylishObjectProperty;
import de.gsi.chart.ui.geometry.Corner;
import de.gsi.chart.ui.geometry.Side;
import de.gsi.dataset.utils.AssertUtils;
import de.gsi.dataset.utils.ProcessingProfiler;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.property.StringPropertyBase;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.WritableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.css.CssMetaData;
import javafx.css.Styleable;
import javafx.css.StyleableObjectProperty;
import javafx.css.StyleableProperty;
import javafx.geometry.Bounds;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.geometry.Point2D;
import javafx.geometry.Pos;
import javafx.scene.CacheHint;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.canvas.Canvas;
import javafx.scene.control.Label;
import javafx.scene.layout.Border;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.BorderStroke;
import javafx.scene.layout.BorderStrokeStyle;
import javafx.scene.layout.BorderWidths;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.RowConstraints;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.util.Duration;
/**
* Chart designed primarily to display data traces using DataSet interfaces which are more flexible and efficient than
* the observable lists used by XYChart. Brief history: original design inspired by Oracle, extended by CERN (i.e.
* plugin concept/zoomer), modified to mitigate JavaFX performance issues and extended renderer
* concept/canvas-concept/interfaces/+more plugins by GSI. Refactored and re-write in 2018 to make it compatible with
* GPLv3 which -- in the spirit of 'Ship of Theseus' -- makes it de-facto a new development. Contributions, bug-fixes,
* and modifications are welcome. Hope you find this library useful and enjoy!
*
* @author original conceptual design by Oracle (2010, 2014)
* @author hbraeun, rstein, major refactoring, re-implementation and re-design
*/
public abstract class Chart extends SidesPane implements Observable {
private static final String CHART_CSS = Chart.class.getResource("chart.css").toExternalForm();
private static final int DEFAULT_TRIGGER_DISTANCE = 50;
protected static final boolean DEBUG = false; // for more verbose debugging
protected BooleanBinding showingBinding;
protected final BooleanProperty showingProperty = new SimpleBooleanProperty(this, "showing", false);
/**
* @return true: if chart is being visible in Scene/Window
*/
public boolean isShowing() {
return showingProperty.get();
}
/**
* @return property indicating if chart is actively visible in Scene/Window
*/
public ReadOnlyBooleanProperty showingProperty() {
return showingProperty;
}
protected final ResizableCanvas canvas = new ResizableCanvas();
/**
* @return the actual canvas the data is being drawn upon
*/
public Canvas getCanvas() {
return canvas;
}
// contains axes (left, bottom, top, right) panes & HiddenSidePane with the
// Canvas at it's centre
protected final GridPane axesAndCanvasPane = new GridPane();
public GridPane getAxesAndCanvasPane() {
return axesAndCanvasPane;
}
protected class ChartHBox extends HBox {
public ChartHBox() {
super();
setAlignment(Pos.CENTER);
setPrefSize(Region.USE_COMPUTED_SIZE, Region.USE_COMPUTED_SIZE);
}
public ChartHBox(final boolean fill) {
this();
setFillHeight(fill);
}
}
protected class ChartVBox extends VBox {
public ChartVBox() {
super();
setAlignment(Pos.CENTER);
setPrefSize(Region.USE_COMPUTED_SIZE, Region.USE_COMPUTED_SIZE);
}
public ChartVBox(final boolean fill) {
this();
setFillWidth(fill);
}
}
protected final Map axesPane = new ConcurrentHashMap<>(4);
{
for (final Side side : Side.values()) {
axesPane.put(side, side.isVertical() ? new ChartHBox() : new ChartVBox());
if (side == Side.CENTER_HOR || side == Side.CENTER_VER) {
axesPane.get(side).setMouseTransparent(true);
}
}
}
public Pane getAxesPane(final Side side) {
return axesPane.get(side);
}
public boolean removeFromAllAxesPanes(final Axis node) {
boolean state = false;
if (!(node instanceof Node)) {
return state;
}
// remove axis from all axis panes
for (final Side side : Side.values()) {
if (getAxesPane(side).getChildren().remove(node)) {
state = true;
}
}
return state;
}
protected final Map titleLegendPane = new ConcurrentHashMap<>(4);
{
for (final Side side : Side.values()) {
titleLegendPane.put(side, side.isVertical() ? new ChartHBox() : new ChartVBox());
}
}
public Pane getTitleLegendPane(final Side side) {
return titleLegendPane.get(side);
}
protected final Map axesCorner = new ConcurrentHashMap<>(4);
{
for (final Corner corner : Corner.values()) {
axesCorner.put(corner, new StackPane());
}
}
public StackPane getAxesCornerPane(final Corner corner) {
return axesCorner.get(corner);
}
protected final Map titleLegendCorner = new ConcurrentHashMap<>(4);
{
for (final Corner corner : Corner.values()) {
titleLegendCorner.put(corner, new StackPane());
}
}
public StackPane getTitleLegendCornerPane(final Corner corner) {
return titleLegendCorner.get(corner);
}
protected final ListChangeListener datasetChangeListener = this::datasetsChanged;
protected final ObservableList allDataSets = FXCollections.observableArrayList();
private final ObservableList datasets = FXCollections.observableArrayList();
{
getDatasets().addListener(datasetChangeListener);
}
/**
* @return datasets attached to the chart and datasets attached to all renderers
*/
public ObservableList getAllDatasets() {
if (getRenderers() == null) {
return allDataSets;
}
allDataSets.clear();
allDataSets.addAll(getDatasets());
getRenderers().stream().filter(renderer -> !(renderer instanceof LabelledMarkerRenderer))
.forEach(renderer -> allDataSets.addAll(renderer.getDatasets()));
return allDataSets;
}
/**
* @return datasets attached to the chart and drawn by all renderers
*/
public ObservableList getDatasets() {
return datasets;
}
private final ObservableList renderers = FXCollections.observableArrayList();
{
getRenderers().addListener(this::rendererChanged);
}
/**
* @return observable list of associated chart renderers
*/
public ObservableList getRenderers() {
return renderers;
}
protected final HiddenSidesPane hiddenPane = new HiddenSidesPane();
protected final Pane plotBackground = new Pane();
protected final Pane plotForeGround = new Pane();
protected final Pane canvasForeground = new Pane();
public Pane getPlotBackground() {
return plotBackground;
}
public Pane getPlotForeground() {
return plotForeGround;
}
public Pane getCanvasForeground() {
return canvasForeground;
}
public HiddenSidesPane getPlotArea() {
return hiddenPane;
}
protected final ObservableList axesList = FXCollections.observableArrayList();
public ObservableList getAxes() {
return axesList;
}
protected final ListChangeListener axesChangeListenerLocal = this::axesChangedLocal;
protected final ListChangeListener axesChangeListener = this::axesChanged;
{
getAxes().addListener(axesChangeListener);
// update listener to propagate axes changes to chart changes
getAxes().addListener(axesChangeListenerLocal);
}
/**
* add Chart specific axis handling (ie. placement around charts, add new DefaultNumericAxis if one is missing,
* etc.)
*
* @param change the new axis change that is being added
*/
protected abstract void axesChanged(final ListChangeListener.Change change);
private final InvalidationListener axisChangeListener = this::axesInvalidated;
protected boolean isAxesUpdate = false;
/**
* function called whenever a axis has been invalidated (e.g. range change or parameter plotting changes). Typically
* calls 'requestLayout()' but can be overwritten in derived classes.
*
* @param axisObj the calling axis object
*/
protected void axesInvalidated(final Object axisObj) {
if (!(axisObj instanceof Axis) || layoutOngoing || isAxesUpdate) {
return;
}
isAxesUpdate = true;
if (DEBUG) {
System.err
.println("chart axesInvalidated() - called by (1) " + ProcessingProfiler.getCallingClassMethod(1));
System.err
.println("chart axesInvalidated() - called by (3) " + ProcessingProfiler.getCallingClassMethod(3));
}
requestLayout();
isAxesUpdate = false;
}
/**
* add Chart specific axis handling (ie. placement around charts, add new DefaultNumericAxis if one is missing,
* etc.)
*
* @param change the new axis change that is being added
*/
protected void axesChangedLocal(final ListChangeListener.Change change) {
while (change.next()) {
change.getRemoved().forEach(set -> {
AssertUtils.notNull("to be removed axis is null", set);
// remove axis invalidation listener
set.removeListener(axisChangeListener);
});
for (final Axis set : change.getAddedSubList()) {
// check if axis is associated with an existing renderer,
// if yes -> throw an exception
AssertUtils.notNull("to be added axis is null", set);
if (!(set instanceof Axis)) {
continue;
}
set.addListener(axisChangeListener);
}
}
requestLayout();
}
public Axis getFirstAxis(final Orientation orientation) {
for (final Axis axis : getAxes()) {
if (axis.getSide() == null) {
continue;
}
switch (orientation) {
case VERTICAL:
if (axis.getSide().isVertical()) {
return axis;
}
break;
case HORIZONTAL:
default:
if (axis.getSide().isHorizontal()) {
return axis;
}
break;
}
}
return null;
}
/** Animator for animating stuff on the chart */
protected final ChartLayoutAnimator animator = new ChartLayoutAnimator(this); // TODO:
// Check
// whether
// 'this'
// or
// chart
// contents
// need
// to
// be
// added
protected final Label titleLabel = new Label();
protected final StringProperty title = new StringPropertyBase() {
@Override
protected void invalidated() {
titleLabel.setText(get());
}
@Override
public Object getBean() {
return Chart.this;
}
@Override
public String getName() {
return "title";
}
};
public final String getTitle() {
return title.get();
}
public final void setTitle(final String value) {
title.set(value);
}
public final StringProperty titleProperty() {
return title;
}
/**
* The side of the chart where the title is displayed
*
* default Side.TOP
*/
private final ObjectProperty titleSide = new StylishObjectProperty(StyleableProperties.TITLE_SIDE, this,
"titleSide", Side.TOP, this::requestLayout) {
@Override
public void set(final Side side) {
AssertUtils.notNull("Side must not be null", side);
for (final Side s : Side.values()) {
getMeasurementBar(s).getChildren().remove(titleLabel);
}
getMeasurementBar(side).getChildren().add(titleLabel);
super.set(side);
}
};
public final Side getTitleSide() {
return titleSide.get();
}
public final void setTitleSide(final Side value) {
titleSide.set(value);
}
public final ObjectProperty titleSideProperty() {
return titleSide;
}
/**
* The side of the chart where the title is displayed
*
* default Side.TOP
*/
private final ObjectProperty measurementBarSide = new StyleableObjectProperty(Side.RIGHT) {
@Override
public void set(final Side side) {
AssertUtils.notNull("Side must not be null", side);
super.set(side);
}
@Override
protected void invalidated() {
requestLayout();
}
@Override
public CssMetaData getCssMetaData() {
return StyleableProperties.MEASUREMENT_SIDE_BAR;
}
@Override
public Object getBean() {
return Chart.this;
}
@Override
public String getName() {
return "measurementBarSide";
}
};
public final Side getMeasurementBarSide() {
return measurementBarSide.get();
}
public final void setMeasurementBarSide(final Side value) {
measurementBarSide.set(value);
}
public final ObjectProperty measurementBarSideProperty() {
return measurementBarSide;
}
/**
* The node to display as the Legend. Subclasses can set a node here to be displayed on a side as the legend. If no
* legend is wanted then this can be set to null
*/
private final ObjectProperty