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

de.gsi.chart.renderer.spi.HistoryDataSetRenderer Maven / Gradle / Ivy

package de.gsi.chart.renderer.spi;

import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.gsi.chart.Chart;
import de.gsi.chart.XYChart;
import de.gsi.chart.XYChartCss;
import de.gsi.chart.axes.Axis;
import de.gsi.dataset.DataSet;
import de.gsi.dataset.EditableDataSet;
import de.gsi.dataset.utils.ProcessingProfiler;
import de.gsi.chart.renderer.Renderer;
import de.gsi.chart.utils.FXUtils;
import de.gsi.chart.utils.StyleParser;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.canvas.GraphicsContext;

/**
 * Renders the data set with the pre-described
 *
 * @author R.J. Steinhagen
 */
public class HistoryDataSetRenderer extends ErrorDataSetRenderer implements Renderer {

    private static final Logger LOGGER = LoggerFactory.getLogger(HistoryDataSetRenderer.class);
    protected static final int DEFAULT_HISTORY_DEPTH = 3;
    protected final ObservableList emptyList = FXCollections.observableArrayList();
    protected final ObservableList chartDataSetsCopy = FXCollections.observableArrayList();
    protected final ObservableList renderers = FXCollections.observableArrayList();
    protected boolean itself = false;

    public HistoryDataSetRenderer() {
        this(HistoryDataSetRenderer.DEFAULT_HISTORY_DEPTH);
    }

    public HistoryDataSetRenderer(final int historyDepth) {
        super();

        if (historyDepth < 0) {
            throw new IllegalArgumentException(
                    String.format("historyDepth=='%d' should be larger than '0'", historyDepth));
        }

        for (int i = 0; i < historyDepth; i++) {
            final ErrorDataSetRenderer newRenderer = new ErrorDataSetRenderer();
            newRenderer.bind(this);
            // do not show history sets in legend (single exception to binding)
            newRenderer.showInLegendProperty().unbind();
            newRenderer.setShowInLegend(false);
            renderers.add(newRenderer);
        }

        getAxes().addListener(HistoryDataSetRenderer.this::axisChanged);

        // special data set handling to re-add local datasets from dependent
        // renderers

        super.getDatasets().addListener((ListChangeListener) e -> {
            while (e.next()) {
                if (e.wasAdded()) {
                    final ObservableList localList = FXCollections.observableArrayList();
                    for (final Renderer r : renderers) {
                        for (final DataSet set : r.getDatasets()) {
                            // don't add duplicates
                            if (!getDatasets().contains(set)) {
                                localList.add(set);
                            }
                        }
                    }
                    // this funny looking expression avoids infinite loops
                    if (!localList.isEmpty() && !itself) {
                        itself = true;
                        super.getDatasets().addAll(localList);
                        itself = false;
                    }
                }
            }
        });
    }

    protected void axisChanged(final ListChangeListener.Change change) {
        while (change.next()) {
            if (change.wasRemoved()) {
                for (final ErrorDataSetRenderer renderer : renderers) {
                    renderer.getAxes().removeAll(change.getRemoved());
                }
            }

            if (change.wasAdded()) {
                for (final ErrorDataSetRenderer renderer : renderers) {
                    renderer.getAxes().addAll(change.getAddedSubList());
                }
            }
        }
    }

    /**
     * @return all DataSets that are either from the calling graph or this first specific renderer
     */
    private ObservableList getLocalDataSets() {
        final ObservableList retVal = FXCollections.observableArrayList();
        retVal.addAll(getDatasets());

        final List removeList = new ArrayList<>();
        for (final Renderer r : renderers) {
            removeList.addAll(r.getDatasets());
        }

        retVal.removeAll(removeList);
        return retVal;
    }

    @Override
    public void render(final GraphicsContext gc, final Chart chart, final int dataSetOffset,
            final ObservableList datasets) {
        final long start = ProcessingProfiler.getTimeStamp();
        if (!(chart instanceof XYChart)) {
            throw new InvalidParameterException(
                    "must be derivative of XYChart for renderer - " + this.getClass().getSimpleName());
        }
        // add local datasets from upstream chart if not already present
        final ObservableList localList = FXCollections.observableArrayList();
        for (final DataSet set : datasets) {
            // don't add duplicates
            if (!getDatasets().contains(set)) {
                localList.add(set);
            }
        }
        getDatasets().addAll(localList);

        int dsIndex = 0;
        for (final DataSet ds : super.getDatasets()) {
            // add index if missing
            modifyStyle(ds, dataSetOffset + dsIndex);
            dsIndex++;
        }

        // render in reverse order
        final int nRenderer = renderers.size();
        for (int index = nRenderer - 1; index >= 0; index--) {
            final ErrorDataSetRenderer renderer = renderers.get(index);
            renderer.render(gc, chart, dataSetOffset, emptyList);
        }

        super.render(gc, chart, dataSetOffset, emptyList);

        ProcessingProfiler.getTimeDiff(start);
    }

    protected void modifyStyle(final DataSet dataSet, final int dataSetIndex) {
        // modify style and add dsIndex if there is not strokeColor or dsIndex
        // Marker
        final String style = dataSet.getStyle();
        final Map map = StyleParser.splitIntoMap(style);

        final String stroke = map.get(XYChartCss.DATASET_STROKE_COLOR.toLowerCase());
        final String fill = map.get(XYChartCss.DATASET_FILL_COLOR.toLowerCase());
        final String index = map.get(XYChartCss.DATASET_INDEX.toLowerCase());

        if (stroke == null && fill == null && index == null) {
            map.put(XYChartCss.DATASET_INDEX, Integer.toString(dataSetIndex));
            dataSet.setStyle(StyleParser.mapToString(map));
        }

    }

    public void shiftHistory() {
        final int nRenderer = renderers.size();
        if (nRenderer <= 0) {
            return;
        }

        final ObservableList oldDataSetsToRemove = renderers.get(nRenderer - 1).getDatasets();
        if (!oldDataSetsToRemove.isEmpty()) {
            try {
                FXUtils.runAndWait(() -> getDatasets().removeAll(oldDataSetsToRemove));
            } catch (InterruptedException | ExecutionException e) {
                HistoryDataSetRenderer.LOGGER.error("remove oldDataSetsToRemove ", e);
            }

        }

        // create local copy of to be shifted data set
        final ObservableList copyDataSet = getDatasetsCopy(getLocalDataSets());

        for (int index = nRenderer - 1; index >= 0; index--) {
            final ErrorDataSetRenderer renderer = renderers.get(index);
            final boolean isFirstRenderer = index == 0;
            final ErrorDataSetRenderer previousRenderer = isFirstRenderer ? this : renderers.get(index - 1);

            final ObservableList copyList = isFirstRenderer ? copyDataSet : previousRenderer.getDatasets();

            final int fading = (int) (Math.pow(getIntensityFading(), index + 2.0) * 100);
            for (final DataSet ds : copyList) {
                if (ds instanceof EditableDataSet) {
                    ((EditableDataSet) ds).setName(ds.getName().split("_")[0] + "History_{-" + index + "}");
                }

                // modify style
                final String style = ds.getStyle();
                final Map map = StyleParser.splitIntoMap(style);
                map.put(XYChartCss.DATASET_INTENSITY.toLowerCase(), Double.toString(fading));
                map.put(XYChartCss.DATASET_SHOW_IN_LEGEND.toLowerCase(), Boolean.toString(false));
                ds.setStyle(StyleParser.mapToString(map));

                if (!getDatasets().contains(ds)) {
                    try {
                        FXUtils.runAndWait(() -> getDatasets().add(ds));
                    } catch (InterruptedException | ExecutionException e) {
                        HistoryDataSetRenderer.LOGGER.error("add missing dataset", e);
                    }
                }
            }

            try {
                FXUtils.runAndWait(() -> renderer.getDatasets().setAll(copyList));
            } catch (InterruptedException | ExecutionException e) {
                HistoryDataSetRenderer.LOGGER.error("add new copied dataset to getDatasets()", e);
            }
        }

        // N.B. added explicit garbage collection to reduce dynamic footprint
        // otherwise this would cause a big saw-tooth like memory footprint
        // which obfuscates debugging/memory-leak
        // checking -> tradeoff between: 'reduced memory footprint' vs.
        // 'significantly reduced CPU efficiency'
        // System.gc();
    }

    /**
     * clear renderer history
     */
    public void clearHistory() {
        for (final Renderer renderer : renderers) {
            try {
                FXUtils.runAndWait(() -> {
                    super.getDatasets().removeAll(renderer.getDatasets());
                    renderer.getDatasets().clear();
                });
            } catch (InterruptedException | ExecutionException e) {
                HistoryDataSetRenderer.LOGGER.error("error in clearHistory()", e);
            }
        }
    }

    private static String setLegendCounter(final String oldStyle, final int count) {
        final Map map = StyleParser.splitIntoMap(oldStyle);
        map.put(XYChartCss.DATASET_INDEX, Integer.toString(count));
        return StyleParser.mapToString(map);
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy