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

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

Go to download

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, ...).

There is a newer version: 11.2.7
Show newest version
package de.gsi.chart.renderer.spi;

import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import de.gsi.chart.Chart;
import de.gsi.chart.XYChart;
import de.gsi.chart.XYChartCss;
import de.gsi.chart.axes.Axis;
import de.gsi.chart.axes.spi.CategoryAxis;
import de.gsi.dataset.DataSet;
import de.gsi.dataset.DataSet3D;
import de.gsi.dataset.DataSetError;
import de.gsi.dataset.DataSetError.ErrorType;
import de.gsi.dataset.utils.ProcessingProfiler;
import de.gsi.chart.marker.Marker;
import de.gsi.chart.renderer.ErrorStyle;
import de.gsi.chart.renderer.Renderer;
import de.gsi.chart.renderer.RendererDataReducer;
import de.gsi.chart.renderer.spi.utils.BezierCurve;
import de.gsi.chart.renderer.spi.utils.Cache;
import de.gsi.chart.renderer.spi.utils.DefaultRenderColorScheme;
import de.gsi.chart.utils.StyleParser;
import de.gsi.math.ArrayUtils;
import javafx.collections.ObservableList;
import javafx.geometry.Orientation;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
import javafx.scene.shape.FillRule;

/**
 * Renders data points with error bars. It can be used e.g. to render horizontal and/or vertical errors
 *
 * @author R.J. Steinhagen
 */
public class ErrorDataSetRenderer extends AbstractErrorDataSetRendererParameter
        implements Renderer {

    private static final double DEG_TO_RAD = Math.PI / 180.0;
    private static final int MAX_THREADS = Math.max(4, Runtime.getRuntime().availableProcessors());
    // private static final ExecutorService executorService =
    // Executors.newCachedThreadPool();
    private static final ExecutorService executorService = Executors.newFixedThreadPool(2 * MAX_THREADS);

    protected Cache cache = new Cache();
    private Marker marker; // TODO: generate marker
    private boolean isPolarPlot = false;

    /**
     * Creates new ErrorDataSetRenderer.
     */
    public ErrorDataSetRenderer() {
        this(3);
    }

    /**
     * Creates new ErrorDataSetRenderer.
     *
     * @param dashSize the initial size (top/bottom cap) of the dash on top of the error bars
     */
    public ErrorDataSetRenderer(final int dashSize) {
        super();
        setDashSize(dashSize);
    }

    @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());
        }
        final XYChart xyChart = (XYChart) chart;
        isPolarPlot = xyChart.isPolarPlot();

        // make local copy and add renderer specific data sets
        final List localDataSetList = new ArrayList<>(datasets);
        localDataSetList.addAll(super.getDatasets());

        // If there are no data sets
        if (localDataSetList.isEmpty()) {
            return;
        }

        if (getFirstAxis(Orientation.HORIZONTAL) == null) {
            throw new InvalidParameterException("x-Axis must not be null - axesList() = " + getAxes());
        }

        if (getFirstAxis(Orientation.VERTICAL) == null) {
            throw new InvalidParameterException("y-Axis must not be null - axesList() = " + getAxes());
        }

        if (!(getFirstAxis(Orientation.HORIZONTAL) instanceof Axis)) {
            throw new InvalidParameterException("x-Axis must be a derivative of Axis, axis is = "
                    + getFirstAxis(Orientation.HORIZONTAL).getClass().getSimpleName());
        }

        if (!(getFirstAxis(Orientation.VERTICAL) instanceof Axis)) {
            throw new InvalidParameterException("y-Axis must be a derivative of Axis, axis is = "
                    + getFirstAxis(Orientation.VERTICAL).getClass().getSimpleName());
        }

        final Axis xAxis = getFirstAxis(Orientation.HORIZONTAL);
        final double xAxisWidth = xAxis.getWidth();
        final double xMin = xAxis.getValueForDisplay(0);
        final double xMax = xAxis.getValueForDisplay(xAxisWidth);

        ProcessingProfiler.getTimeDiff(start, "init");

        for (int dataSetIndex = localDataSetList.size() - 1; dataSetIndex >= 0; dataSetIndex--) {
            long stop = ProcessingProfiler.getTimeStamp();
            final DataSet dataSet = localDataSetList.get(dataSetIndex);
            if (dataSet instanceof DataSet3D) {
                // this renderer cannot use 3D data sets directly, use
                // MountainRangeRenderer instead
                // continue;
            }

            // N.B. print out for debugging purposes, please keep (used for
            // detecting redundant or too frequent render updates)
            // System.err.println(
            // String.format("render for range [%f,%f] and dataset = '%s'",
            // xMin, xMax, dataSet.getName()));

            dataSet.lock();
            stop = ProcessingProfiler.getTimeDiff(stop, "dataSet.lock()");

            // update categories in case of category axes for the first (index
            // == '0') indexed data set
            if (dataSetIndex == 0) {
                if (getFirstAxis(Orientation.HORIZONTAL) instanceof CategoryAxis) {
                    final CategoryAxis axis = (CategoryAxis) getFirstAxis(Orientation.HORIZONTAL);
                    axis.updateCategories(dataSet);
                }

                if (getFirstAxis(Orientation.VERTICAL) instanceof CategoryAxis) {
                    final CategoryAxis axis = (CategoryAxis) getFirstAxis(Orientation.VERTICAL);
                    axis.updateCategories(dataSet);
                }
            }

            // check for potentially reduced data range we are supposed to plot

            int indexMin = Math.max(0, dataSet.getXIndex(xMin));
            /* indexMax is excluded in the drawing */
            int indexMax = Math.min(dataSet.getXIndex(xMax) + 1, dataSet.getDataCount());
            if (xAxis.isInvertedAxis()) {
                final int temp = indexMin;
                indexMin = indexMax - 1;
                indexMax = temp + 1;
            }

            if (indexMax - indexMin <= 0) {
                // zero length/range data set -> nothing to be drawn
                dataSet.unlock();
                continue;
            }

            stop = ProcessingProfiler.getTimeDiff(stop,
                    "get min/max" + String.format(" from:%d to:%d", indexMin, indexMax));

            final CachedDataPoints localCachedPoints = new CachedDataPoints(indexMin, indexMax, dataSet.getDataCount(),
                    true);
            stop = ProcessingProfiler.getTimeDiff(stop, "get CachedPoints");

            // compute local screen coordinates
            localCachedPoints.computeScreenCoordinates(chart, dataSet, dataSetOffset + dataSetIndex, indexMin,
                    indexMax);
            stop = ProcessingProfiler.getTimeDiff(stop, "computeScreenCoordinates()");
            dataSet.unlock();
            stop = ProcessingProfiler.getTimeDiff(stop, "dataSet.unlock()");

            // invoke data reduction algorithm
            localCachedPoints.reduce();

            synchronized (gc) {
                // draw individual plot components
                drawChartCompontents(gc, localCachedPoints);
            }
            stop = ProcessingProfiler.getTimeStamp();

            localCachedPoints.release();
            ProcessingProfiler.getTimeDiff(stop, "localCachedPoints.release()");
        } // end of 'dataSetIndex' loop
        ProcessingProfiler.getTimeDiff(start);
    }

    private void drawChartCompontents(final GraphicsContext gc, final CachedDataPoints localCachedPoints) {
        final long start = ProcessingProfiler.getTimeStamp();
        switch (getErrorType()) {
        case ERRORBARS:
            drawErrorBars(gc, localCachedPoints);
            break;
        case ERRORSURFACE:
            drawErrorSurface(gc, localCachedPoints);
            break;
        case ERRORCOMBO:
            if (localCachedPoints.getMinXDistance() >= getDashSize() * 2) {
                drawErrorBars(gc, localCachedPoints);
            } else {
                drawErrorSurface(gc, localCachedPoints);
            }
            break;
        case NONE:
        default:
            drawDefaultNoErrors(gc, localCachedPoints);
            break;
        }
        ProcessingProfiler.getTimeDiff(start);
    }

    /**
     * @param dataSet the data set for which the representative icon should be generated
     * @param dsIndex index within renderer set
     * @param width requested width of the returning Canvas
     * @param height requested height of the returning Canvas
     * @return a graphical icon representation of the given data sets
     */
    @Override
    public Canvas drawLegendSymbol(final DataSet dataSet, final int dsIndex, final int width, final int height) {
        final Canvas canvas = new Canvas(width, height);
        final GraphicsContext gc = canvas.getGraphicsContext2D();

        final String style = dataSet.getStyle();
        final Integer layoutOffset = StyleParser.getIntegerPropertyValue(style, XYChartCss.DATASET_LAYOUT_OFFSET);
        final Integer dsIndexLocal = StyleParser.getIntegerPropertyValue(style, XYChartCss.DATASET_INDEX);

        final int dsLayoutIndexOffset = layoutOffset == null ? 0 : layoutOffset.intValue(); // TODO:
                                                                                            // rationalise

        final int plotingIndex = dsLayoutIndexOffset + (dsIndexLocal == null ? dsIndex : dsIndexLocal.intValue());

        gc.save();

        DefaultRenderColorScheme.setLineScheme(gc, dataSet.getStyle(), plotingIndex);
        DefaultRenderColorScheme.setGraphicsContextAttributes(gc, dataSet.getStyle());
        DefaultRenderColorScheme.setFillScheme(gc, dataSet.getStyle(), plotingIndex);
        // DefaultRenderColorScheme.setLineScheme(gc, dataSet.getStyle(),
        // plotingIndex);
        // DefaultRenderColorScheme.setLineScheme(gc, dataSet.getStyle(),
        // plotingIndex);
        // DefaultRenderColorScheme.setLineScheme(gc, dataSet.getStyle(),
        // plotingIndex);
        if (getErrorType() == ErrorStyle.ERRORBARS) {
            final double x = width / 2.0;
            final double y = height / 2.0;
            if (getDashSize() > 2) {
                gc.strokeLine(x - 1.0, 1, x + 1.0, 1.0);
                gc.strokeLine(x - 1.0, height - 2.0, x + 1.0, height - 2.0);
                gc.strokeLine(x, 1.0, x, height - 2.0);
            }

            // DefaultRenderColorScheme.setLineScheme(gc, dataSet.getStyle(),
            // plotingIndex);
            gc.strokeLine(1, y, width, y);
        } else if (getErrorType() == ErrorStyle.ERRORSURFACE) {
            final double y = height / 2.0;
            gc.fillRect(1, 1, width - 2.0, height - 2.0);
            gc.strokeLine(1, y, width - 2.0, y);
        } else if (getErrorType() == ErrorStyle.ERRORCOMBO) {
            final double y = height / 2.0;
            gc.fillRect(1, 1, width - 2.0, height - 2.0);

            // DefaultRenderColorScheme.setLineScheme(gc, dataSet.getStyle(),
            // plotingIndex);
            gc.strokeLine(1, y, width - 2.0, y);
        } else {
            final double x = width / 2.0;
            final double y = height / 2.0;
            if (getDashSize() > 2) {
                gc.strokeLine(x - 1.0, 1.0, x + 1.0, 1.0);
                gc.strokeLine(x - 1.0, height - 2.0, x + 1, height - 2.0);
                gc.strokeLine(x, 1.0, x, height - 2.0);
            }

            // DefaultRenderColorScheme.setLineScheme(gc, dataSet.getStyle(),
            // plotingIndex);
            gc.strokeLine(1, y, width - 2.0, y);
        }
        gc.restore();
        return canvas;
    }

    /**
     * @param gc the graphics context from the Canvas parent
     * @param localCachedPoints reference to local cached data point object
     */
    protected void drawDefaultNoErrors(final GraphicsContext gc, final CachedDataPoints localCachedPoints) {

        drawBars(gc, localCachedPoints);
        drawPolyLine(gc, localCachedPoints);
        drawMarker(gc, localCachedPoints);

    }

    /**
     * @param gc the graphics context from the Canvas parent
     * @param localCachedPoints reference to local cached data point object
     */
    protected void drawPolyLine(final GraphicsContext gc, final CachedDataPoints localCachedPoints) {
        switch (getPolyLineStyle()) {
        case NONE:
            return;
        case AREA:
            drawPolyLineArea(gc, localCachedPoints);
            break;
        case ZERO_ORDER_HOLDER:
        case STAIR_CASE:
            drawPolyLineStairCase(gc, localCachedPoints);
            break;
        case HISTOGRAM:
            drawPolyLineHistogram(gc, localCachedPoints);
            break;
        case HISTOGRAM_FILLED:
            drawPolyLineHistogramFilled(gc, localCachedPoints);
            break;
        case BEZIER_CURVE:
            drawPolyLineHistogramBezier(gc, localCachedPoints);
            break;
        case NORMAL:
        default:
            drawPolyLineLine(gc, localCachedPoints);
            break;
        }
    }

    protected void drawPolyLineLine(final GraphicsContext gc, final CachedDataPoints localCachedPoints) {
        gc.save();
        DefaultRenderColorScheme.setLineScheme(gc, localCachedPoints.defaultStyle,
                localCachedPoints.dataSetIndex + localCachedPoints.dataSetStyleIndex);
        DefaultRenderColorScheme.setGraphicsContextAttributes(gc, localCachedPoints.defaultStyle);

        // gc.strokePolyline(localCachedPoints.xValues,
        // localCachedPoints.yValues, localCachedPoints.actualDataCount);

        for (int i = 0; i < localCachedPoints.actualDataCount - 1; i++) {
            final double x1 = localCachedPoints.xValues[i];
            final double x2 = localCachedPoints.xValues[i + 1];
            final double y1 = localCachedPoints.yValues[i];
            final double y2 = localCachedPoints.yValues[i + 1];
            gc.strokeLine(x1, y1, x2, y2);
        }

        // gc.beginPath();
        // for (int i=0; i< localCachedPoints.actualDataCount -1; i++) {
        // double x0 = localCachedPoints.xValues[i];
        // double x1 = localCachedPoints.xValues[i+1];
        // double y0 = localCachedPoints.yValues[i];
        // double y1 = localCachedPoints.yValues[i+1];
        // gc.moveTo(x0, y0);
        // gc.lineTo(x1, y1);
        // }
        // gc.closePath();
        // gc.stroke();

        gc.restore();
    }

    protected void drawPolyLineArea(final GraphicsContext gc, final CachedDataPoints localCachedPoints) {
        final double zero = localCachedPoints.yZero;
        final int n = localCachedPoints.actualDataCount;
        if (n == 0) {
            return;
        }

        // need to allocate new array :-(
        final double[] newX = cache.getCachedDoubleArray("xDrawPolyLineArea", n + 2);
        final double[] newY = cache.getCachedDoubleArray("yDrawPolyLineArea", n + 2);

        System.arraycopy(localCachedPoints.xValues, 0, newX, 0, n);
        System.arraycopy(localCachedPoints.yValues, 0, newY, 0, n);
        newX[n] = localCachedPoints.xValues[n - 1];
        newY[n] = zero;
        newX[n + 1] = localCachedPoints.xValues[0];
        newY[n + 1] = zero;

        gc.save();
        DefaultRenderColorScheme.setLineScheme(gc, localCachedPoints.defaultStyle,
                localCachedPoints.dataSetIndex + localCachedPoints.dataSetStyleIndex);
        DefaultRenderColorScheme.setGraphicsContextAttributes(gc, localCachedPoints.defaultStyle);
        // use stroke as fill colour
        gc.setFill(gc.getStroke());
        gc.fillPolygon(newX, newY, n + 2);
        gc.restore();

        // release arrays to cache
        cache.release("xDrawPolyLineArea", newX);
        cache.release("yDrawPolyLineArea", newY);
    }

    protected void drawPolyLineStairCase(final GraphicsContext gc, final CachedDataPoints localCachedPoints) {
        final int n = localCachedPoints.actualDataCount;
        if (n == 0) {
            return;
        }

        // need to allocate new array :-(
        final double[] newX = cache.getCachedDoubleArray("xDrawPolyLineStairCase", 2 * n);
        final double[] newY = cache.getCachedDoubleArray("yDrawPolyLineStairCase", 2 * n);

        for (int i = 0; i < n - 1; i++) {
            newX[2 * i] = localCachedPoints.xValues[i];
            newY[2 * i] = localCachedPoints.yValues[i];
            newX[2 * i + 1] = localCachedPoints.xValues[i + 1];
            newY[2 * i + 1] = localCachedPoints.yValues[i];
        }
        // last point
        newX[2 * (n - 1)] = localCachedPoints.xValues[n - 1];
        newY[2 * (n - 1)] = localCachedPoints.yValues[n - 1];
        newX[2 * n - 1] = localCachedPoints.xMax;
        newY[2 * n - 1] = localCachedPoints.yValues[n - 1];

        gc.save();
        DefaultRenderColorScheme.setLineScheme(gc, localCachedPoints.defaultStyle,
                localCachedPoints.dataSetIndex + localCachedPoints.dataSetStyleIndex);
        DefaultRenderColorScheme.setGraphicsContextAttributes(gc, localCachedPoints.defaultStyle);
        // gc.strokePolyline(newX, newY, 2*n);

        for (int i = 0; i < 2 * n - 1; i++) {
            final double x1 = newX[i];
            final double x2 = newX[i + 1];
            final double y1 = newY[i];
            final double y2 = newY[i + 1];
            gc.strokeLine(x1, y1, x2, y2);
        }

        gc.restore();

        // release arrays to cache
        cache.release("xDrawPolyLineStairCase", newX);
        cache.release("yDrawPolyLineStairCase", newY);
    }

    protected void drawPolyLineHistogram(final GraphicsContext gc, final CachedDataPoints localCachedPoints) {
        final int n = localCachedPoints.actualDataCount;
        if (n == 0) {
            return;
        }

        // need to allocate new array :-(
        final double[] newX = cache.getCachedDoubleArray("xDrawPolyLineHistogram", 2 * (n + 1));
        final double[] newY = cache.getCachedDoubleArray("yDrawPolyLineHistogram", 2 * (n + 1));

        final double xRange = localCachedPoints.xMax - localCachedPoints.xMin;
        double diffLeft;
        double diffRight = n > 0 ? 0.5 * (localCachedPoints.xValues[1] - localCachedPoints.xValues[0]) : 0.5 * xRange;
        newX[0] = localCachedPoints.xValues[0] - diffRight;
        newY[0] = localCachedPoints.yZero;
        for (int i = 0; i < n; i++) {
            diffLeft = localCachedPoints.xValues[i] - newX[2 * i];
            diffRight = i + 1 < n ? 0.5 * (localCachedPoints.xValues[i + 1] - localCachedPoints.xValues[i]) : diffLeft;
            if (i == 0) {
                diffLeft = diffRight;
            }

            newX[2 * i + 1] = localCachedPoints.xValues[i] - diffLeft;
            newY[2 * i + 1] = localCachedPoints.yValues[i];
            newX[2 * i + 2] = localCachedPoints.xValues[i] + diffRight;
            newY[2 * i + 2] = localCachedPoints.yValues[i];
        }
        // last point
        newX[2 * (n + 1) - 1] = localCachedPoints.xValues[n - 1] + diffRight;
        newY[2 * (n + 1) - 1] = localCachedPoints.yZero;

        gc.save();
        DefaultRenderColorScheme.setLineScheme(gc, localCachedPoints.defaultStyle,
                localCachedPoints.dataSetIndex + localCachedPoints.dataSetStyleIndex);
        DefaultRenderColorScheme.setGraphicsContextAttributes(gc, localCachedPoints.defaultStyle);
        // gc.strokePolyline(newX, newY, 2*(n+1));

        for (int i = 0; i < 2 * (n + 1) - 1; i++) {
            final double x1 = newX[i];
            final double x2 = newX[i + 1];
            final double y1 = newY[i];
            final double y2 = newY[i + 1];
            gc.strokeLine(x1, y1, x2, y2);
        }

        gc.restore();

        // release arrays to cache
        cache.release("xDrawPolyLineHistogram", newX);
        cache.release("yDrawPolyLineHistogram", newY);
    }

    protected void drawPolyLineHistogramFilled(final GraphicsContext gc, final CachedDataPoints localCachedPoints) {
        final int n = localCachedPoints.actualDataCount;
        if (n == 0) {
            return;
        }

        // need to allocate new array :-(
        final double[] newX = cache.getCachedDoubleArray("xDrawPolyLineHistogram", 2 * (n + 1));
        final double[] newY = cache.getCachedDoubleArray("yDrawPolyLineHistogram", 2 * (n + 1));

        final double xRange = localCachedPoints.xMax - localCachedPoints.xMin;
        double diffLeft;
        double diffRight = n > 0 ? 0.5 * (localCachedPoints.xValues[1] - localCachedPoints.xValues[0]) : 0.5 * xRange;
        newX[0] = localCachedPoints.xValues[0] - diffRight;
        newY[0] = localCachedPoints.yZero;
        for (int i = 0; i < n; i++) {
            diffLeft = localCachedPoints.xValues[i] - newX[2 * i];
            diffRight = i + 1 < n ? 0.5 * (localCachedPoints.xValues[i + 1] - localCachedPoints.xValues[i]) : diffLeft;
            if (i == 0) {
                diffLeft = diffRight;
            }

            newX[2 * i + 1] = localCachedPoints.xValues[i] - diffLeft;
            newY[2 * i + 1] = localCachedPoints.yValues[i];
            newX[2 * i + 2] = localCachedPoints.xValues[i] + diffRight;
            newY[2 * i + 2] = localCachedPoints.yValues[i];
        }
        // last point
        newX[2 * (n + 1) - 1] = localCachedPoints.xValues[n - 1] + diffRight;
        newY[2 * (n + 1) - 1] = localCachedPoints.yZero;

        gc.save();
        DefaultRenderColorScheme.setLineScheme(gc, localCachedPoints.defaultStyle,
                localCachedPoints.dataSetIndex + localCachedPoints.dataSetStyleIndex);
        DefaultRenderColorScheme.setGraphicsContextAttributes(gc, localCachedPoints.defaultStyle);
        // use stroke as fill colour
        gc.setFill(gc.getStroke());
        gc.fillPolygon(newX, newY, 2 * (n + 1));
        gc.restore();

        // release arrays to cache
        cache.release("xDrawPolyLineHistogram", newX);
        cache.release("yDrawPolyLineHistogram", newY);
    }

    protected void drawPolyLineHistogramBezier(final GraphicsContext gc, final CachedDataPoints localCachedPoints) {
        final int n = localCachedPoints.actualDataCount;
        if (n < 2) {
            drawPolyLineLine(gc, localCachedPoints);
            return;
        }

        // need to allocate new array :-(
        final double[] xCp1 = cache.getCachedDoubleArray("xBezierFirstControlPoint", n);
        final double[] yCp1 = cache.getCachedDoubleArray("yBezierFirstControlPoint", n);
        final double[] xCp2 = cache.getCachedDoubleArray("xBezierSecondControlPoint", n);
        final double[] yCp2 = cache.getCachedDoubleArray("yBezierSecondControlPoint", n);

        BezierCurve.calcCurveControlPoints(localCachedPoints.xValues, localCachedPoints.yValues, xCp1, yCp1, xCp2, yCp2,
                localCachedPoints.actualDataCount);

        gc.save();
        DefaultRenderColorScheme.setLineScheme(gc, localCachedPoints.defaultStyle,
                localCachedPoints.dataSetIndex + localCachedPoints.dataSetStyleIndex);
        DefaultRenderColorScheme.setGraphicsContextAttributes(gc, localCachedPoints.defaultStyle);
        // use stroke as fill colour
        gc.setFill(gc.getStroke());
        gc.beginPath();
        for (int i = 0; i < n - 1; i++) {
            final double x0 = localCachedPoints.xValues[i];
            final double x1 = localCachedPoints.xValues[i + 1];
            final double y0 = localCachedPoints.yValues[i];
            final double y1 = localCachedPoints.yValues[i + 1];

            // coordinates of first Bezier control point.
            final double xc0 = xCp1[i];
            final double yc0 = yCp1[i];
            // coordinates of the second Bezier control point.
            final double xc1 = xCp2[i];
            final double yc1 = yCp2[i];

            gc.moveTo(x0, y0);
            gc.bezierCurveTo(xc0, yc0, xc1, yc1, x1, y1);
        }
        gc.moveTo(localCachedPoints.xValues[n - 1], localCachedPoints.yValues[n - 1]);
        gc.closePath();
        gc.stroke();
        gc.restore();

        // release arrays to cache
        cache.release("xBezierFirstControlPoint", xCp1);
        cache.release("yBezierFirstControlPoint", yCp1);
        cache.release("xBezierSecondControlPoint", xCp2);
        cache.release("yBezierSecondControlPoint", yCp2);
    }

    /**
     * @param gc the graphics context from the Canvas parent
     * @param localCachedPoints reference to local cached data point object
     */
    protected void drawMarker(final GraphicsContext gc, final CachedDataPoints localCachedPoints) {
        if (!isDrawMarker()) {
            return;
        }
        gc.save();
        DefaultRenderColorScheme.setMarkerScheme(gc, localCachedPoints.defaultStyle,
                localCachedPoints.dataSetIndex + localCachedPoints.dataSetStyleIndex);

        // N.B. the markers are drawn in the same colour as the polyline (ie.
        // not the fillColor)
        final Color fillColor = StyleParser.getColorPropertyValue(localCachedPoints.defaultStyle,
                XYChartCss.STROKE_COLOR);
        if (fillColor != null) {
            gc.setFill(fillColor);
        }

        // TODO check how-to draw marker
        // final Marker marker = getMarker();
        // if (marker == null) {
        // // marker = MarkerFactory.getMarker(Marker.CIRCLE);
        // }

        for (int i = 0; i < localCachedPoints.actualDataCount; i++) {
            // marker.draw(gc, localCachedPoints.xValues[i],
            // localCachedPoints.yValues[i],
            // getMarkerSize(), style[i], localCachedPoints.selected[i]);
            final double mSize = getMarkerSize();
            gc.fillRect(localCachedPoints.xValues[i] - mSize, localCachedPoints.yValues[i] - mSize, 2 * mSize,
                    2 * mSize);
        }

        gc.restore();
    }

    /**
     * @param gc the graphics context from the Canvas parent
     * @param localCachedPoints reference to local cached data point object
     */
    protected void drawBars(final GraphicsContext gc, final CachedDataPoints localCachedPoints) {
        if (!isDrawBars()) {
            return;
        }

        final int xOffset = localCachedPoints.dataSetIndex >= 0 ? localCachedPoints.dataSetIndex : 0;
        final int minRequiredWidth = Math.max(getDashSize(), localCachedPoints.minDistanceX);

        final double barWPercentage = getBarWidthPercentage();
        final double dynBarWidth = minRequiredWidth * barWPercentage / 100;
        final double constBarWidth = getBarWidth();
        final double localBarWidth = isDynamicBarWidth() ? dynBarWidth : constBarWidth;
        final double barWidthHalf = localBarWidth / 2 - (isShiftBar() ? xOffset * getShiftBarOffset() : 0);

        gc.save();
        DefaultRenderColorScheme.setMarkerScheme(gc, localCachedPoints.defaultStyle,
                localCachedPoints.dataSetIndex + localCachedPoints.dataSetStyleIndex);
        DefaultRenderColorScheme.setGraphicsContextAttributes(gc, localCachedPoints.defaultStyle);

        if (localCachedPoints.polarPlot) {
            for (int i = 0; i < localCachedPoints.actualDataCount; i++) {
                if (localCachedPoints.selected[i]) {
                    gc.strokeLine(localCachedPoints.xZero, localCachedPoints.yZero, localCachedPoints.xValues[i],
                            localCachedPoints.yValues[i]);
                } else {
                    // work-around: bar colour controlled by the marker color
                    gc.save();
                    gc.setLineWidth(barWidthHalf);
                    gc.strokeLine(localCachedPoints.xZero, localCachedPoints.yZero, localCachedPoints.xValues[i],
                            localCachedPoints.yValues[i]);
                    gc.restore();
                }
            }
        } else {
            for (int i = 0; i < localCachedPoints.actualDataCount; i++) {
                double yDiff = localCachedPoints.yValues[i] - localCachedPoints.yZero;
                double yMin;
                if (yDiff > 0) {
                    yMin = localCachedPoints.yZero;
                } else {
                    yMin = localCachedPoints.yValues[i];
                    yDiff = Math.abs(yDiff);
                }

                if (localCachedPoints.selected[i]) {
                    gc.strokeRect(localCachedPoints.xValues[i] - barWidthHalf, yMin, localBarWidth, yDiff);
                } else {
                    // work-around: bar colour controlled by the marker color
                    gc.fillRect(localCachedPoints.xValues[i] - barWidthHalf, yMin, localBarWidth, yDiff);
                }
            }
        }

        gc.restore();
    }

    /**
     * @param gc the graphics context from the Canvas parent
     * @param lCacheP reference to local cached data point object
     */
    protected void drawErrorBars(final GraphicsContext gc, final CachedDataPoints lCacheP) {

        final long start = ProcessingProfiler.getTimeStamp();

        drawBars(gc, lCacheP);

        final int dashHalf = getDashSize() / 2;
        gc.save();
        DefaultRenderColorScheme.setFillScheme(gc, lCacheP.defaultStyle, lCacheP.dataSetIndex);
        DefaultRenderColorScheme.setGraphicsContextAttributes(gc, lCacheP.defaultStyle);

        for (int i = 0; i < lCacheP.actualDataCount; i++) {

            if (lCacheP.errorType == ErrorType.XY || lCacheP.errorType == ErrorType.XY_ASYMMETRIC) {
                // draw error bars
                gc.strokeLine(lCacheP.xValues[i], lCacheP.errorYNeg[i], lCacheP.xValues[i], lCacheP.errorYPos[i]);
                gc.strokeLine(lCacheP.errorXNeg[i], lCacheP.yValues[i], lCacheP.errorXPos[i], lCacheP.yValues[i]);

                // draw horizontal dashes
                gc.strokeLine(lCacheP.xValues[i] - dashHalf, lCacheP.errorYNeg[i], lCacheP.xValues[i] + dashHalf,
                        lCacheP.errorYNeg[i]);
                gc.strokeLine(lCacheP.xValues[i] - dashHalf, lCacheP.errorYPos[i], lCacheP.xValues[i] + dashHalf,
                        lCacheP.errorYPos[i]);

                // draw vertical dashes
                gc.strokeLine(lCacheP.errorXNeg[i], lCacheP.yValues[i] - dashHalf, lCacheP.errorXNeg[i],
                        lCacheP.yValues[i] + dashHalf);
                gc.strokeLine(lCacheP.errorXPos[i], lCacheP.yValues[i] - dashHalf, lCacheP.errorXPos[i],
                        lCacheP.yValues[i] + dashHalf);
            } else if (lCacheP.errorType == ErrorType.Y || lCacheP.errorType == ErrorType.Y_ASYMMETRIC) {
                // draw error bars
                gc.strokeLine(lCacheP.xValues[i], lCacheP.errorYNeg[i], lCacheP.xValues[i], lCacheP.errorYPos[i]);

                // draw horizontal dashes
                gc.strokeLine(lCacheP.xValues[i] - dashHalf, lCacheP.errorYNeg[i], lCacheP.xValues[i] + dashHalf,
                        lCacheP.errorYNeg[i]);
                gc.strokeLine(lCacheP.xValues[i] - dashHalf, lCacheP.errorYPos[i], lCacheP.xValues[i] + dashHalf,
                        lCacheP.errorYPos[i]);

            } else if (lCacheP.errorType == ErrorType.X || lCacheP.errorType == ErrorType.X_ASYMMETRIC) {
                // draw error bars
                gc.strokeLine(lCacheP.errorXNeg[i], lCacheP.yValues[i], lCacheP.errorXPos[i], lCacheP.yValues[i]);

                // draw horizontal dashes
                gc.strokeLine(lCacheP.xValues[i] - dashHalf, lCacheP.errorYNeg[i], lCacheP.xValues[i] + dashHalf,
                        lCacheP.errorYNeg[i]);
                gc.strokeLine(lCacheP.xValues[i] - dashHalf, lCacheP.errorYPos[i], lCacheP.xValues[i] + dashHalf,
                        lCacheP.errorYPos[i]);
            }
        }
        gc.restore();

        drawPolyLine(gc, lCacheP);
        drawMarker(gc, lCacheP);

        ProcessingProfiler.getTimeDiff(start);
    }

    /**
     * @param gc the graphics context from the Canvas parent
     * @param localCachedPoints reference to local cached data point object
     */
    protected void drawErrorSurface(final GraphicsContext gc, final CachedDataPoints localCachedPoints) {
        final long start = ProcessingProfiler.getTimeStamp();

        DefaultRenderColorScheme.setFillScheme(gc, localCachedPoints.defaultStyle,
                localCachedPoints.dataSetIndex + localCachedPoints.dataSetStyleIndex);

        final int nDataCount = localCachedPoints.actualDataCount;
        final int nPolygoneEdges = 2 * nDataCount;
        final double[] xValuesSurface = cache.getCachedDoubleArray("xValuesSurface", nPolygoneEdges);
        final double[] yValuesSurface = cache.getCachedDoubleArray("yValuesSurface", nPolygoneEdges);

        final int xend = nPolygoneEdges - 1;
        for (int i = 0; i < nDataCount; i++) {
            xValuesSurface[i] = localCachedPoints.xValues[i];
            yValuesSurface[i] = localCachedPoints.errorYNeg[i];
            xValuesSurface[xend - i] = localCachedPoints.xValues[i];
            yValuesSurface[xend - i] = localCachedPoints.errorYPos[i];
        }
        // swap y coordinates at mid-point
        if (nDataCount > 4) {
            final double yTmp = yValuesSurface[nDataCount - 1];
            yValuesSurface[nDataCount - 1] = yValuesSurface[xend - nDataCount + 1];
            yValuesSurface[xend - nDataCount + 1] = yTmp;
        }

        gc.setFillRule(FillRule.EVEN_ODD);
        gc.fillPolygon(xValuesSurface, yValuesSurface, nPolygoneEdges);

        drawPolyLine(gc, localCachedPoints);
        drawBars(gc, localCachedPoints);
        drawMarker(gc, localCachedPoints);

        cache.release("xValuesSurface", xValuesSurface);
        cache.release("yValuesSurface", yValuesSurface);

        ProcessingProfiler.getTimeDiff(start);
    }

    /**
     * @return the instance of this ErrorDataSetRenderer.
     */
    @Override
    protected ErrorDataSetRenderer getThis() {
        return this;
    }

    /**
     * Returns the marker used by this renderer.
     *
     * @return the marker to be drawn on the data points
     */
    public Marker getMarker() {
        return marker;
    }

    /**
     * Replaces marker used by this renderer.
     *
     * @param marker the marker to be drawn on the data points
     */
    public void setMarker(final Marker marker) {
        this.marker = marker;
    }

    /********************************************************************
     ******* private class implementation (data point caching) **********
     *******************************************************************/

    /**
     * local screen data point cache (minimises re-allocation/garbage collection)
     */
    protected class CachedDataPoints {

        protected double[] xValues;
        protected double[] yValues;
        protected double[] errorXNeg;
        protected double[] errorXPos;
        protected double[] errorYNeg;
        protected double[] errorYPos;
        protected boolean[] selected;
        protected String[] styles;
        protected boolean xAxisInverted;
        protected boolean yAxisInverted;
        protected String defaultStyle;
        protected int dataSetIndex;
        protected int dataSetStyleIndex;
        protected ErrorType errorType;
        protected int indexMin;
        protected int indexMax;
        protected int minDistanceX = +Integer.MAX_VALUE;
        protected double xZero; // reference zero 'x' axis coordinate
        protected double yZero; // reference zero 'y' axis coordinate
        protected double yMin;
        protected double yMax;
        protected double xMin;
        protected double xMax;
        protected boolean polarPlot;
        protected double xRange;
        protected double yRange;
        protected double maxRadius;
        protected int maxDataCount;
        protected int actualDataCount; // number of data points that remain
                                       // after data reduction

        public CachedDataPoints(final int indexMin, final int indexMax, final int dataLength, final boolean full) {
            maxDataCount = dataLength;
            xValues = cache.getCachedDoubleArray("xValues", maxDataCount);
            yValues = cache.getCachedDoubleArray("yValues", maxDataCount);
            styles = cache.getCachedStringArray("styles", dataLength);
            this.indexMin = indexMin;
            this.indexMax = indexMax;
            errorYNeg = cache.getCachedDoubleArray("errorYNeg", maxDataCount);
            errorYPos = cache.getCachedDoubleArray("errorYPos", maxDataCount);
            if (full) {
                errorXNeg = cache.getCachedDoubleArray("errorXNeg", maxDataCount);
                errorXPos = cache.getCachedDoubleArray("errorXPos", maxDataCount);
            }
            selected = cache.getCachedBooleanArray("selected", dataLength);
            // ArrayUtils.fillArray(selected, true);
            ArrayUtils.fillArray(styles, null);
        }

        /**
         * computes the minimum distance in between data points N.B. assumes sorted data set points
         * 
         * @return min distance
         */
        private int getMinXDistance() {
            if (minDistanceX < Integer.MAX_VALUE) {
                return minDistanceX;
            }

            if (indexMin >= indexMax) {
                minDistanceX = 1;
                return minDistanceX;
            }

            minDistanceX = Integer.MAX_VALUE;
            for (int i = 1; i < actualDataCount; i++) {
                final double x0 = xValues[i - 1];
                final double x1 = xValues[i];
                minDistanceX = Math.min(minDistanceX, (int) Math.abs(x1 - x0));
            }
            return minDistanceX;
        }

        private void computeScreenCoordinates(final Chart chart, final DataSet dataSet, final int dsIndex,
                final int min, final int max) {
            if (!(getFirstAxis(Orientation.HORIZONTAL) instanceof Axis)) {
                throw new InvalidParameterException(
                        "x Axis not a Axis derivative, xAxis = " + getFirstAxis(Orientation.HORIZONTAL));
            }
            if (!(getFirstAxis(Orientation.VERTICAL) instanceof Axis)) {
                throw new InvalidParameterException(
                        "y Axis not a Axis derivative, yAxis = " + getFirstAxis(Orientation.VERTICAL));
            }
            final Axis xAxis = getFirstAxis(Orientation.HORIZONTAL);
            final Axis yAxis = getFirstAxis(Orientation.VERTICAL);
            xAxisInverted = xAxis.isInvertedAxis();
            yAxisInverted = yAxis.isInvertedAxis();

            indexMin = min;
            indexMax = max;

            // compute cached axis variables ... about 50% faster than the
            // generic template based version from
            // ValueAxsis
            if (xAxis.isLogAxis()) {
                xZero = xAxis.getDisplayPosition(xAxis.getLowerBound());
            } else {
                xZero = xAxis.getDisplayPosition(0);
            }
            if (yAxis.isLogAxis()) {
                yZero = yAxis.getDisplayPosition(yAxis.getLowerBound());
            } else {
                yZero = yAxis.getDisplayPosition(0);
            }

            polarPlot = isPolarPlot;

            yMin = yAxis.getDisplayPosition(yAxis.getLowerBound());
            yMax = yAxis.getDisplayPosition(yAxis.getUpperBound());
            xMin = xAxis.getDisplayPosition(xAxis.getLowerBound());
            xMax = xAxis.getDisplayPosition(xAxis.getUpperBound());

            defaultStyle = dataSet.getStyle();

            xRange = Math.abs(xMax - xMin);
            yRange = Math.abs(yMax - yMin);
            maxRadius = 0.5 * Math.max(Math.min(xRange, yRange), 20) * 0.9;
            // TODO: parameterise '0.9' -> radius axis fills 90% of min canvas
            // axis
            if (polarPlot) {
                xZero = 0.5 * xRange;
                yZero = 0.5 * yRange;
                // System.err.println("maxRadius = " + maxRadius + " range: yMin
                // " + yMin + " yMax" + yMax);
            }

            final Integer layoutOffset = StyleParser.getIntegerPropertyValue(defaultStyle,
                    XYChartCss.DATASET_LAYOUT_OFFSET);
            final Integer dsIndexLocal = StyleParser.getIntegerPropertyValue(defaultStyle, XYChartCss.DATASET_INDEX);

            dataSetStyleIndex = layoutOffset == null ? 0 : layoutOffset.intValue(); // TODO:
                                                                                    // rationalise

            dataSetIndex = dsIndexLocal == null ? dsIndex : dsIndexLocal.intValue();

            // compute screen coordinates of other points
            if (dataSet instanceof DataSetError) {
                final DataSetError ds = (DataSetError) dataSet;
                errorType = ds.getErrorType();
            } else {
                // fall-back for standard DataSet

                // default: ErrorType=Y fall-back also for 'DataSet' without
                // errors
                // rationale: scientific honesty
                // if data points are being compressed, the error of compression
                // (e.g. due to local transients that are being suppressed) are
                // nevertheless being computed and shown even if individual data
                // points have no error
                errorType = ErrorType.Y;
            }

            // special case where users does not want error bars
            if (getErrorType() == ErrorStyle.NONE) {
                errorType = ErrorType.NO_ERROR;
            }

            // ErrorDataSetRenderer.this.setParallelImplementation(false);
            // compute data set to screen coordinates
            if (isParallelImplementation()) {
                final int minthreshold = 1000;
                // Math.min(length / minthreshold, MAX_THREADS);
                int divThread = (int) Math.ceil(Math.abs(max - min) / (double) MAX_THREADS);
                int stepSize = Math.max(divThread, minthreshold);
                final List> workers = new ArrayList<>();
                for (int i = min; i < max; i += stepSize) {
                    final int start = i;
                    workers.add(() -> {
                        computeScreenCoordinates(xAxis, yAxis, dataSet, start, Math.min(max, start + stepSize));
                        return Boolean.TRUE;
                    });
                }

                try {
                    final List> jobs = ErrorDataSetRenderer.executorService.invokeAll(workers);
                    for (final Future future : jobs) {
                        final Boolean r = future.get();
                        if (!r) {
                            throw new IllegalStateException("one parallel worker thread finished execution with error");
                        }
                    }
                } catch (final InterruptedException | ExecutionException e) {
                    throw new IllegalStateException("one parallel worker thread finished execution with error", e);
                }
            } else {
                computeScreenCoordinates(xAxis, yAxis, dataSet, min, max);
            }

        }

        private void computeScreenCoordinates(final Axis xAxis, final Axis yAxis, final DataSet dataSet, final int min,
                final int max) {
            switch (errorType) {
            case NO_ERROR: // no error attached
                if (!polarPlot) {
                    for (int index = min; index < max; index++) {
                        final double x = dataSet.getX(index);
                        final double y = dataSet.getY(index);
                        // check if error should be surrounded by Math.abs(..)
                        // to ensure that they are always positive
                        xValues[index] = xAxis.getDisplayPosition(x);
                        yValues[index] = yAxis.getDisplayPosition(y);
                        if (!Double.isFinite(yValues[index])) {
                            yValues[index] = yMin;
                        }
                    }
                } else {
                    // experimental transform euclidean to polar coordinates

                    for (int index = min; index < max; index++) {
                        final double x = dataSet.getX(index);
                        final double y = dataSet.getY(index);
                        // check if error should be surrounded by Math.abs(..)
                        // to ensure that they are always positive
                        final double phi = x * ErrorDataSetRenderer.DEG_TO_RAD;
                        final double r = maxRadius * Math.abs(1 - yAxis.getDisplayPosition(y) / yRange);
                        xValues[index] = xZero + r * Math.cos(phi);
                        yValues[index] = yZero + r * Math.sin(phi);

                        if (!Double.isFinite(yValues[index])) {
                            yValues[index] = yZero;
                        }
                    }
                }

                return;
            case Y: // only symmetric errors around y
            case Y_ASYMMETRIC: // asymmetric errors around y
                if (!polarPlot) {
                    if (dataSet instanceof DataSetError) {
                        final DataSetError ds = (DataSetError) dataSet;

                        for (int index = min; index < max; index++) {
                            final double x = dataSet.getX(index);
                            final double y = dataSet.getY(index);
                            // check if error should be surrounded by
                            // Math.abs(..)
                            // to ensure that they are always positive
                            xValues[index] = xAxis.getDisplayPosition(x);
                            yValues[index] = yAxis.getDisplayPosition(y);
                            if (Double.isFinite(yValues[index])) {
                                errorYNeg[index] = yAxis.getDisplayPosition(y - ds.getYErrorNegative(index));
                                errorYPos[index] = yAxis.getDisplayPosition(y + ds.getYErrorPositive(index));
                            } else {
                                yValues[index] = yMin;
                                errorYNeg[index] = yMin;
                                errorYPos[index] = yMin;
                            }
                        }
                        return;
                    }

                    // default dataset
                    for (int index = min; index < max; index++) {
                        final double x = dataSet.getX(index);
                        final double y = dataSet.getY(index);
                        // check if error should be surrounded by Math.abs(..)
                        // to ensure that they are always positive
                        xValues[index] = xAxis.getDisplayPosition(x);
                        yValues[index] = yAxis.getDisplayPosition(y);

                        if (!Double.isFinite(xValues[index])) {
                            xValues[index] = xMin;
                        }
                        if (Double.isFinite(yValues[index])) {
                            errorYNeg[index] = yValues[index];
                            errorYPos[index] = yValues[index];
                        } else {
                            yValues[index] = yMin;
                            errorYNeg[index] = yMin;
                            errorYPos[index] = yMin;
                        }
                    }
                } else {
                    for (int index = min; index < max; index++) {
                        final double x = dataSet.getX(index);
                        final double y = dataSet.getY(index);
                        // check if error should be surrounded by Math.abs(..)
                        // to ensure that they are always positive
                        final double phi = x * ErrorDataSetRenderer.DEG_TO_RAD;
                        final double r = maxRadius * Math.abs(1 - yAxis.getDisplayPosition(y) / yRange);
                        xValues[index] = xZero + r * Math.cos(phi);
                        yValues[index] = yZero + r * Math.sin(phi);

                        // ignore errors (for now) -> TODO: add proper
                        // transformation
                        errorXNeg[index] = 0.0;
                        errorXPos[index] = 0.0;
                        errorYNeg[index] = 0.0;
                        errorYPos[index] = 0.0;

                        if (!Double.isFinite(yValues[index])) {
                            yValues[index] = yZero;
                        }
                    }
                }
                return;
            case X: // only symmetric errors around x
            case X_ASYMMETRIC: // asymmetric errors around x
            case XY: // symmetric errors around x and y
            case XY_ASYMMETRIC: // asymmetric errors around x and y
            default:
                if (!(dataSet instanceof DataSetError)) {
                    throw new IllegalStateException("dataSet may not be non-DataSetError at this stage, dataSet = "
                            + dataSet.getName() + " errorType = " + errorType);
                }
                final DataSetError ds = (DataSetError) dataSet;

                if (!polarPlot) {
                    for (int index = min; index < max; index++) {
                        final double x = dataSet.getX(index);
                        final double y = dataSet.getY(index);
                        // check if error should be surrounded by
                        // Math.abs(..) to ensure that they are always positive
                        xValues[index] = xAxis.getDisplayPosition(x);
                        yValues[index] = yAxis.getDisplayPosition(y);

                        if (Double.isFinite(xValues[index])) {
                            errorXNeg[index] = xAxis.getDisplayPosition(x - ds.getXErrorNegative(index));
                            errorXPos[index] = xAxis.getDisplayPosition(x + ds.getXErrorPositive(index));
                        } else {
                            xValues[index] = xMin;
                            errorXNeg[index] = xMin;
                            errorXPos[index] = xMin;
                        }

                        if (Double.isFinite(yValues[index])) {
                            errorYNeg[index] = yAxis.getDisplayPosition(y - ds.getYErrorNegative(index));
                            errorYPos[index] = yAxis.getDisplayPosition(y + ds.getYErrorPositive(index));
                        } else {
                            yValues[index] = yMin;
                            errorYNeg[index] = yMin;
                            errorYPos[index] = yMin;
                        }
                    }
                } else {
                    for (int index = min; index < max; index++) {
                        final double x = dataSet.getX(index);
                        final double y = dataSet.getY(index);
                        // check if error should be surrounded by Math.abs(..)
                        // to ensure that they are always positive
                        final double phi = x * ErrorDataSetRenderer.DEG_TO_RAD;
                        final double r = maxRadius * Math.abs(1 - yAxis.getDisplayPosition(y) / yRange);
                        // double r = maxRadius * Math.abs(-y /
                        // (dataSet.getYMax()-dataSet.getYMin()));
                        xValues[index] = xZero + r * Math.cos(phi);
                        yValues[index] = yZero + r * Math.sin(phi);

                        // ignore errors (for now) -> TODO: add proper
                        // transformation
                        errorXNeg[index] = 0.0;
                        errorXPos[index] = 0.0;
                        errorYNeg[index] = 0.0;
                        errorYPos[index] = 0.0;

                        if (!Double.isFinite(yValues[index])) {
                            yValues[index] = yZero;
                        }
                    }
                }
                return;
            }
        }

        private int minDataPointDistanceX() {
            if (actualDataCount <= 1) {
                minDistanceX = 1;
                return minDistanceX;
            }
            minDistanceX = Integer.MAX_VALUE;
            for (int i = 1; i < actualDataCount; i++) {
                final double x0 = xValues[i - 1];
                final double x1 = xValues[i];
                minDistanceX = Math.min(minDistanceX, (int) Math.abs(x1 - x0));
            }
            return minDistanceX;
        }

        protected void reduce() {
            final long startTimeStamp = ProcessingProfiler.getTimeStamp();
            actualDataCount = 1;

            if (!isReducePoints() || Math.abs(indexMax - indexMin) < getMinRequiredReductionSize()) {
                actualDataCount = indexMax - indexMin;
                System.arraycopy(xValues, indexMin, xValues, 0, actualDataCount);
                System.arraycopy(yValues, indexMin, yValues, 0, actualDataCount);
                System.arraycopy(selected, indexMin, selected, 0, actualDataCount);
                switch (errorType) {
                case NO_ERROR: // no error attached
                    break;
                case Y: // only symmetric errors around y
                case Y_ASYMMETRIC: // asymmetric errors around y
                    System.arraycopy(errorYNeg, indexMin, errorYNeg, 0, actualDataCount);
                    System.arraycopy(errorYPos, indexMin, errorYPos, 0, actualDataCount);
                    break;
                case XY: // symmetric errors around x and y
                case X: // only symmetric errors around x
                case X_ASYMMETRIC: // asymmetric errors around x
                default:
                    System.arraycopy(errorXNeg, indexMin, errorXNeg, 0, actualDataCount);
                    System.arraycopy(errorXPos, indexMin, errorXPos, 0, actualDataCount);
                    System.arraycopy(errorYNeg, indexMin, errorYNeg, 0, actualDataCount);
                    System.arraycopy(errorYPos, indexMin, errorYPos, 0, actualDataCount);
                    break;
                }

                ProcessingProfiler.getTimeDiff(startTimeStamp,
                        String.format("no data reduction (%d)", actualDataCount));
                return;
            }

            final RendererDataReducer cruncher = rendererDataReducerProperty().get();

            if (!isReducePoints()) {

            }

            switch (errorType) {
            case NO_ERROR: // see comment above
            case Y:
                actualDataCount = cruncher.reducePoints(xValues, yValues, null, null, errorYPos, errorYNeg, styles,
                        selected, indexMin, indexMax);
                minDataPointDistanceX();
                break;
            case X:
            case XY:
            default:
                actualDataCount = cruncher.reducePoints(xValues, yValues, errorXPos, errorXNeg, errorYPos, errorYNeg,
                        styles, selected, indexMin, indexMax);

                minDataPointDistanceX();
                break;
            }
            // ProcessingProfiler.getTimeDiff(startTimeStamp,
        }

        @Override
        protected void finalize() throws Throwable {
            release();
            super.finalize();
        }

        public void release() {
            cache.release("xValues", xValues);
            cache.release("yValues", yValues);
            cache.release("errorYNeg", errorYNeg);
            cache.release("errorYPos", errorYPos);
            cache.release("errorXNeg", errorXNeg);
            cache.release("errorXPos", errorXPos);
            cache.release("selected", selected);
            cache.release("styles", styles);
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy