net.tourbook.chart.ChartComponentGraph.java Source code

Java tutorial

Introduction

Here is the source code for net.tourbook.chart.ChartComponentGraph.java

Source

/*******************************************************************************
 * Copyright (C) 2005, 2017 Wolfgang Schramm and Contributors
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
 *******************************************************************************/
package net.tourbook.chart;

import java.io.InputStream;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;

import net.tourbook.common.PointLong;
import net.tourbook.common.RectangleLong;
import net.tourbook.common.UI;

import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.layout.PixelConverter;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.MenuAdapter;
import org.eclipse.swt.events.MenuEvent;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.MouseTrackListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.LineAttributes;
import org.eclipse.swt.graphics.Path;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.ScrollBar;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Widget;

/**
 * Draws the graph and axis into the canvas
 * 
 * @author Wolfgang Schramm
 */
public class ChartComponentGraph extends Canvas {

    private static final double FLOAT_ZERO = ChartDataYSerie.FLOAT_ZERO;

    private static final double ZOOM_RATIO = 1.3;
    private static double ZOOM_RATIO_FACTOR = ZOOM_RATIO;

    private static final int BAR_MARKER_WIDTH = 16;

    private static final int[] DOT_DASHES = new int[] { 1, 1 };

    private static final NumberFormat _nf = NumberFormat.getNumberInstance();

    private static final RGB _gridRGB = new RGB(230, 230, 230);
    private static final RGB _gridRGBMajor = new RGB(220, 220, 220);

    private static final int[][] _leftAccelerator = new int[][] { { -40, -200 }, { -30, -50 }, { -20, -10 },
            { -10, -5 },
            // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
            // !!! move 2 instead of 1, with 1 it would sometimes not move, needs more investigation
            // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
            { 0, -2 } };

    private static final int[][] _rightAccelerator = new int[][] { { 10, 2 }, { 20, 5 }, { 30, 10 }, { 40, 50 },
            { Integer.MAX_VALUE, 200 } };

    private final LineAttributes LINE_DASHED = new LineAttributes(5);
    {
        LINE_DASHED.dashOffset = 3;
        LINE_DASHED.style = SWT.LINE_CUSTOM;
        LINE_DASHED.dash = new float[] { 1f, 2f };
        LINE_DASHED.width = 1f;
    }
    Chart _chart;

    private final ChartComponents _chartComponents;

    /**
     * This image contains one single graph without title and x-axis with units.
     * <p>
     * This image was created to fix clipping bugs which occured when gradient filling was painted
     * with a path.
     */
    private Image _chartImage_10_Graphs;

    /**
     * This image contains the chart without additional layers.
     */
    private Image _chartImage_20_Chart;

    /**
     * Contains custom layers like the markers or tour segments which are painted in the foreground.
     */
    private Image _chartImage_30_Custom;

    /**
     * Contains layers like the x/y sliders, x-marker, selection or hovered line/bar.
     */
    private Image _chartImage_40_Overlay;

    /**
     * 
     */
    private ChartDrawingData _chartDrawingData;

    /**
     * drawing data which is used to draw the chart, when this list is empty, an error is displayed
     */
    private ArrayList<GraphDrawingData> _allGraphDrawingData = new ArrayList<GraphDrawingData>();
    private ArrayList<GraphDrawingData> _revertedGraphDrawingData;

    /**
     * Zoom ratio between the visible and the virtual chart width, is 1.0 when not zoomed, is > 1
     * when zoomed.
     */
    private double _graphZoomRatio = 1;

    /**
     * Contains the width for a zoomed graph this includes also the invisible parts.
     */
    private long _xxDevGraphWidth;

    /**
     * When the graph is zoomed, the chart shows only a part of the whole graph in the viewport.
     * This value contains the left border of the viewport.
     */
    long _xxDevViewPortLeftBorder;

    /**
     * ratio for the position where the chart starts on the left side within the virtual graph width
     */
    private double _zoomRatioLeftBorder;

    /**
     * ratio where the mouse was double clicked, this position is used to zoom the chart with the
     * mouse
     */
    private double _zoomRatioCenter;

    /**
     * This zoom ratio is used when zooming the next time with the keyboard, when 0 this is ignored.
     */
    private double _zoomRatioCenterKey;

    /**
     * when the slider is dragged and the mouse up event occures, the graph is zoomed to the sliders
     * when set to <code>true</code>
     */
    boolean _canAutoZoomToSlider;

    /**
     * when <code>true</code> the vertical sliders will be moved to the border when the chart is
     * zoomed
     */
    boolean _canAutoMoveSliders;

    /**
     * true indicates the graph needs to be redrawn in the paint event
     */
    private boolean _isChartDirty;

    /**
     * true indicates the slider needs to be redrawn in the paint event
     */
    private boolean _isSliderDirty;

    /**
     * when <code>true</code> the custom layers above the graph image needs to be redrawn in the
     * next paint event
     */
    private boolean _isCustomLayerImageDirty;

    /**
     * set to <code>true</code> when the selection needs to be redrawn
     */
    private boolean _isSelectionDirty;

    /**
     * status for the x-slider, <code>true</code> indicates, the slider is visible
     */
    private boolean _isXSliderVisible;

    /**
     * true indicates that the y-sliders is visible
     */
    private boolean _isYSliderVisible;

    /*
     * chart slider
     */
    private final ChartXSlider _xSliderA;
    private final ChartXSlider _xSliderB;

    /**
     * xSliderDragged is set when the slider is being dragged, otherwise it is to <code>null</code>
     */
    private ChartXSlider _xSliderDragged;

    /**
     * This is the slider which is drawn on top of the other, this is normally the last dragged
     * slider
     */
    private ChartXSlider _xSliderOnTop;

    /**
     * this is the slider which is below the top slider
     */
    private ChartXSlider _xSliderOnBottom;

    /**
     * contains the x-slider when the mouse is over it, or <code>null</code> when the mouse is not
     * over it
     */
    private ChartXSlider _mouseOverXSlider;

    /**
     * Contains the slider which has the focus.
     */
    private ChartXSlider _selectedXSlider;

    /**
     * Device position of the x-slider line when the slider is dragged. The position can be outside
     * of the viewport which causes autoscrolling.
     */
    private int _devXDraggedXSliderLine;

    /**
     * Mouse device position when autoscrolling is done with the mouse but without a x-slider
     */
    private int _devXAutoScrollMousePosition = Integer.MIN_VALUE;

    /**
     * list for all y-sliders
     */
    private ArrayList<ChartYSlider> _ySliders;

    /**
     * contextLeftSlider is set when the right mouse button was clicked and the left slider was hit
     */
    private ChartXSlider _contextLeftSlider;

    /**
     * contextRightSlider is set when the right mouse button was clicked and the right slider was
     * hit
     */
    private ChartXSlider _contextRightSlider;

    /**
     * cursor when the graph can be resizes
     */
    private Cursor _cursorResizeLeftRight;

    private Cursor _cursorArrow;
    private Cursor _cursorDragged;
    private Cursor _cursorDragXSlider_ModeZoom;
    private Cursor _cursorDragXSlider_ModeSlider;
    private Cursor _cursorHoverXSlider;
    private Cursor _cursorModeZoom;
    private Cursor _cursorModeZoomMove;
    private Cursor _cursorModeSlider;
    private Cursor _cursorMove1x;
    private Cursor _cursorMove2x;
    private Cursor _cursorMove3x;
    private Cursor _cursorMove4x;
    private Cursor _cursorMove5x;
    private Cursor _cursorResizeTopDown;
    private Cursor _cursorXSliderLeft;
    private Cursor _cursorXSliderRight;

    private Color _gridColor;
    private Color _gridColorMajor;

    /**
     * Contains a {@link ChartTitleSegment} when a tour title area is hovered, otherwise
     * <code>null</code> .
     */
    private ChartTitleSegment _hoveredTitleSegment;

    /**
     * serie index for the hovered bar, the bar is hidden when -1;
     */
    private int _hoveredBarSerieIndex = -1;

    private int _hoveredBarValueIndex;
    private boolean _isHoveredBarDirty;

    private ToolTipV1 _hoveredBarToolTip;

    private boolean _isHoveredLineVisible = false;
    private int _hoveredValuePointIndex = -1;

    private ArrayList<RectangleLong[]> _lineFocusRectangles = new ArrayList<RectangleLong[]>();
    private ArrayList<PointLong[]> _lineDevPositions = new ArrayList<PointLong[]>();

    /**
     * Tooltip for value points, can be <code>null</code> when not set.
     */
    IValuePointToolTip valuePointToolTip;

    private ChartYSlider _hitYSlider;
    private ChartYSlider _ySliderDragged;
    private int _ySliderGraphX;

    private boolean _isSetXSliderPositionLeft;
    private boolean _isSetXSliderPositionRight;

    /**
     * <code>true</code> when the x-marker is moved with the mouse
     */
    private boolean _isXMarkerMoved;

    /**
     * x-position when the x-marker was started to drag
     */
    private int _devXMarkerDraggedStartPos;

    /**
     * x-position when the x-marker is moved
     */
    private int _devXMarkerDraggedPos;

    private int _movedXMarkerStartValueIndex;
    private int _movedXMarkerEndValueIndex;

    private double _xMarkerValueDiff;

    /**
     * <code>true</code> when the chart is dragged with the mouse
     */
    private boolean _isChartDragged = false;

    /**
     * <code>true</code> when the mouse button in down but not moved
     */
    private boolean _isChartDraggedStarted = false;

    private Point _draggedChartStartPos;
    private Point _draggedChartDraggedPos;

    private boolean[] _selectedBarItems;

    private final int[] _drawAsyncCounter = new int[1];

    private boolean _isAutoScroll;
    private boolean _isDisableHoveredLineValueIndex;
    private int[] _autoScrollCounter = new int[1];

    private final ColorCache _colorCache = new ColorCache();

    private boolean _isSelectionVisible;

    /**
     * Is <code>true</code> when this chart gained the focus, <code>false</code> when the focus is
     * lost.
     */
    private boolean _isFocusActive;

    private boolean _isOverlayDirty;

    /**
     * widget relative position of the mouse in the mouse down event
     */
    private int _devXMouseDown;
    private int _devYMouseDown;
    private int _devXMouseMove;
    private int _devYMouseMove;

    private boolean _isPaintDraggedImage = false;

    /**
     * is <code>true</code> when data for a graph is available
     */
    private boolean _isGraphVisible = false;

    /**
     * Client area for this canvas
     */
    Rectangle _clientArea;

    /**
     * After a resize the custom overlay must be recomputed
     */
    private int _isCustomOverlayInvalid;

    private PixelConverter _pc;

    /**
     * Is <code>true</code> when a chart can be overlapped. The overlap feature is currently
     * supported for graphs which all have the chart type ChartType.LINE.
     */
    boolean _canChartBeOverlapped;

    /**
     * Is <code>true</code> when overlapped graphs are enabled.
     */
    boolean _isChartOverlapped;

    /**
     * Cache font to improve performance.
     */
    private Font _uiFont;

    /**
     * Configuration how the chart title segment is displayed.
     */
    ChartTitleSegmentConfig chartTitleSegmentConfig = new ChartTitleSegmentConfig();

    private ILineSelectionPainter _lineSelectionPainter;

    /**
     * Constructor
     * 
     * @param parent
     *            the parent of this control.
     * @param style
     *            the style of this control.
     */
    ChartComponentGraph(final Chart chartWidget, final Composite parent, final int style) {

        // create composite with horizontal scrollbars
        super(parent, SWT.H_SCROLL | SWT.NO_BACKGROUND);

        _chart = chartWidget;
        _uiFont = _chart.getFont();

        _pc = new PixelConverter(_chart);

        _cursorResizeLeftRight = new Cursor(getDisplay(), SWT.CURSOR_SIZEWE);
        _cursorResizeTopDown = new Cursor(getDisplay(), SWT.CURSOR_SIZENS);
        _cursorDragged = new Cursor(getDisplay(), SWT.CURSOR_SIZEALL);
        _cursorArrow = new Cursor(getDisplay(), SWT.CURSOR_ARROW);

        _cursorModeSlider = createCursorFromImage(Messages.Image_cursor_mode_slider);
        _cursorModeZoom = createCursorFromImage(Messages.Image_cursor_mode_zoom);
        _cursorModeZoomMove = createCursorFromImage(Messages.Image_cursor_mode_zoom_move);
        _cursorDragXSlider_ModeZoom = createCursorFromImage(Messages.Image_Cursor_DragXSlider_ModeZoom);
        _cursorDragXSlider_ModeSlider = createCursorFromImage(Messages.Image_Cursor_DragXSlider_ModeSlider);
        _cursorHoverXSlider = createCursorFromImage(Messages.Image_Cursor_Hover_XSlider);

        _cursorMove1x = createCursorFromImage(Messages.Image_Cursor_Move1x);
        _cursorMove2x = createCursorFromImage(Messages.Image_Cursor_Move2x);
        _cursorMove3x = createCursorFromImage(Messages.Image_Cursor_Move3x);
        _cursorMove4x = createCursorFromImage(Messages.Image_Cursor_Move4x);
        _cursorMove5x = createCursorFromImage(Messages.Image_Cursor_Move5x);

        _cursorXSliderLeft = createCursorFromImage(Messages.Image_Cursor_X_Slider_Left);
        _cursorXSliderRight = createCursorFromImage(Messages.Image_Cursor_X_Slider_Right);

        _gridColor = new Color(getDisplay(), _gridRGB);
        _gridColorMajor = new Color(getDisplay(), _gridRGBMajor);

        _chartComponents = (ChartComponents) parent;

        // setup the x-slider
        _xSliderA = new ChartXSlider(this, Integer.MIN_VALUE, ChartXSlider.SLIDER_TYPE_LEFT);
        _xSliderB = new ChartXSlider(this, Integer.MIN_VALUE, ChartXSlider.SLIDER_TYPE_RIGHT);

        _xSliderOnTop = _xSliderB;
        _xSliderOnBottom = _xSliderA;

        _hoveredBarToolTip = new ToolTipV1(_chart);

        addListener();
        createContextMenu();

        final Point devMouse = this.toControl(getDisplay().getCursorLocation());
        setCursorStyle(devMouse.y);
    }

    /**
     * execute the action which is defined when a bar is selected with the left mouse button
     */
    private void actionSelectBars() {

        if (_hoveredBarSerieIndex < 0) {
            return;
        }

        boolean[] selectedBarItems;

        if (_allGraphDrawingData.size() == 0) {
            selectedBarItems = null;
        } else {

            final GraphDrawingData graphDrawingData = _allGraphDrawingData.get(0);
            final ChartDataXSerie xData = graphDrawingData.getXData();

            selectedBarItems = new boolean[xData._highValuesDouble[0].length];
            selectedBarItems[_hoveredBarValueIndex] = true;
        }

        setSelectedBars(selectedBarItems);

        _chart.fireBarSelectionEvent(_hoveredBarSerieIndex, _hoveredBarValueIndex);
    }

    /**
     * hookup all listeners
     */
    private void addListener() {

        addPaintListener(new PaintListener() {
            @Override
            public void paintControl(final PaintEvent event) {

                if (_isChartDragged) {

                    drawSync_020_MoveCanvas(event.gc);

                } else {

                    //               final long start = System.nanoTime();
                    //               System.out.println();
                    //               System.out.println("onPaint\tstart\t");
                    //               // TODO remove SYSTEM.OUT.PRINTLN

                    drawSync_000_onPaint(event.gc, event.time & 0xFFFFFFFFL);

                    //               System.out.println("onPaint\tend\t" + (((double) System.nanoTime() - start) / 1000000) + "ms");
                    //               System.out.println();
                    //               // TODO remove SYSTEM.OUT.PRINTLN
                }
            }
        });

        // horizontal scrollbar
        final ScrollBar horizontalBar = getHorizontalBar();
        horizontalBar.setEnabled(false);
        horizontalBar.setVisible(false);
        horizontalBar.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(final SelectionEvent event) {
                onScroll(event);
            }
        });

        addMouseMoveListener(new MouseMoveListener() {
            @Override
            public void mouseMove(final MouseEvent e) {
                if (_isGraphVisible) {
                    onMouseMove(e.time & 0xFFFFFFFFL, e.x, e.y);
                }
            }
        });

        addMouseListener(new MouseListener() {
            @Override
            public void mouseDoubleClick(final MouseEvent e) {
                if (_isGraphVisible) {
                    onMouseDoubleClick(e);
                }
            }

            @Override
            public void mouseDown(final MouseEvent e) {
                if (_isGraphVisible) {
                    onMouseDown(e);
                }
            }

            @Override
            public void mouseUp(final MouseEvent e) {
                if (_isGraphVisible) {
                    onMouseUp(e);
                }
            }
        });

        addMouseTrackListener(new MouseTrackListener() {
            @Override
            public void mouseEnter(final MouseEvent e) {
                if (_isGraphVisible) {
                    onMouseEnter(e);
                }
            }

            @Override
            public void mouseExit(final MouseEvent e) {
                if (_isGraphVisible) {
                    onMouseExit(e);
                }
            }

            @Override
            public void mouseHover(final MouseEvent e) {
            }
        });

        addListener(SWT.MouseWheel, new Listener() {
            @Override
            public void handleEvent(final Event event) {
                onMouseWheel(event, false, false);
            }
        });

        addFocusListener(new FocusListener() {

            @Override
            public void focusGained(final FocusEvent e) {

                setFocusToControl();

                _isFocusActive = true;
                _isSelectionDirty = true;

                redraw();
            }

            @Override
            public void focusLost(final FocusEvent e) {

                _isFocusActive = false;
                _isSelectionDirty = true;

                redraw();
            }
        });

        addListener(SWT.Traverse, new Listener() {
            @Override
            public void handleEvent(final Event event) {

                switch (event.detail) {
                case SWT.TRAVERSE_RETURN:
                case SWT.TRAVERSE_ESCAPE:
                case SWT.TRAVERSE_TAB_NEXT:
                case SWT.TRAVERSE_TAB_PREVIOUS:
                case SWT.TRAVERSE_PAGE_NEXT:
                case SWT.TRAVERSE_PAGE_PREVIOUS:
                    event.doit = true;
                    break;
                }
            }
        });

        addControlListener(new ControlListener() {

            @Override
            public void controlMoved(final ControlEvent e) {
            }

            @Override
            public void controlResized(final ControlEvent e) {

                _clientArea = getClientArea();

                _isDisableHoveredLineValueIndex = true;
            }
        });

        addListener(SWT.KeyDown, new Listener() {
            @Override
            public void handleEvent(final Event event) {
                onKeyDown(event);
            }
        });

        addDisposeListener(new DisposeListener() {
            @Override
            public void widgetDisposed(final DisposeEvent e) {
                onDispose();
            }
        });

    }

    private void adjustYSlider() {

        /*
         * check if the y slider was outside of the bounds, recompute the chart when necessary
         */

        final GraphDrawingData drawingData = _ySliderDragged.getDrawingData();

        final ChartDataYSerie yData = _ySliderDragged.getYData();
        final ChartYSlider slider1 = yData.getYSlider1();
        final ChartYSlider slider2 = yData.getYSlider2();

        final int devYBottom = drawingData.getDevYBottom();
        final int devYTop = devYBottom - drawingData.devGraphHeight;

        final float graphYBottom = drawingData.getGraphYBottom();
        final double scaleY = drawingData.getScaleY();

        final int devYSliderLine1 = slider1.getDevYSliderLine();
        final int devYSliderLine2 = slider2.getDevYSliderLine();

        final double graphValue1 = (((double) devYBottom - devYSliderLine1) / scaleY + graphYBottom);
        final double graphValue2 = (((double) devYBottom - devYSliderLine2) / scaleY + graphYBottom);

        // get value which was adjusted
        if (_ySliderDragged == slider1) {
            yData.adjustedYValue = (float) graphValue1;
        } else if (_ySliderDragged == slider2) {
            yData.adjustedYValue = (float) graphValue2;
        } else {
            // this case should not happen
            System.out.println("y-slider is not set correctly\t");//$NON-NLS-1$
            return;
        }

        double minValue;
        double maxValue;

        if (graphValue1 < graphValue2) {

            minValue = graphValue1;
            maxValue = graphValue2;

            // position the lower slider to the bottom of the chart
            slider1.setDevYSliderLine(devYBottom);
            slider2.setDevYSliderLine(devYTop);

        } else {

            // graphValue1 >= graphValue2

            minValue = graphValue2;
            maxValue = graphValue1;

            // position the upper slider to the top of the chart
            slider1.setDevYSliderLine(devYTop);
            slider2.setDevYSliderLine(devYBottom);
        }
        yData.setVisibleMinValue(minValue);
        yData.setVisibleMaxValue(maxValue);

        _ySliderDragged = null;

        // the cursor could be outside of the chart, reset it
        //      setCursorStyle();

        /*
         * the hited slider could be outsite of the chart, hide the labels on the slider
         */
        _hitYSlider = null;

        /*
         * when the chart is synchronized, the y-slider position is modified, so we overwrite the
         * synchronized chart y-slider positions until the zoom in marker is overwritten
         */
        final SynchConfiguration synchedChartConfig = _chartComponents._synchConfigSrc;

        if (synchedChartConfig != null) {

            final ChartYDataMinMaxKeeper synchedChartMinMaxKeeper = synchedChartConfig.getYDataMinMaxKeeper();

            // get the id for the changed y-slider
            final Integer yDataInfo = (Integer) yData.getCustomData(ChartDataYSerie.YDATA_INFO);

            // adjust min value for the changed y-slider
            final HashMap<Integer, Double> minKeeper = synchedChartMinMaxKeeper.getMinValues();
            final Double synchedChartMinValue = minKeeper.get(yDataInfo);

            if (synchedChartMinValue != null) {
                minKeeper.put(yDataInfo, minValue);
            }

            // adjust max value for the changed y-slider
            final HashMap<Integer, Double> maxKeeper = synchedChartMinMaxKeeper.getMaxValues();
            final Double synchedChartMaxValue = maxKeeper.get(yDataInfo);

            if (synchedChartMaxValue != null) {
                maxKeeper.put(yDataInfo, maxValue);
            }
        }

        computeChart();
    }

    /**
     * @param allGraphDrawingData
     * @return Returns <code>true</code> when a chart can be overlapped.
     *         <p>
     *         The overlap feature is currently supported for graphs which all have the chart type
     *         {@link ChartType#LINE}.
     */
    private boolean canChartBeOverlapped(final ArrayList<GraphDrawingData> allGraphDrawingData) {

        for (final GraphDrawingData graphDrawingData : allGraphDrawingData) {

            final ChartType chartType = graphDrawingData.getChartType();

            if (chartType != ChartType.LINE && chartType != ChartType.HORIZONTAL_BAR) {
                return false;
            }
        }

        return true;
    }

    /**
     * when the chart was modified, recompute all
     */
    private void computeChart() {
        getDisplay().asyncExec(new Runnable() {
            @Override
            public void run() {
                if (!isDisposed()) {
                    _chartComponents.onResize();
                }
            }
        });
    }

    private void computeSliderForContextMenu(final int devX, final int devY) {

        ChartXSlider slider1 = null;
        ChartXSlider slider2 = null;

        // reset the context slider
        _contextLeftSlider = null;
        _contextRightSlider = null;

        // check if a slider or the slider line was hit
        if (_xSliderA.getHitRectangle().contains(devX, devY)) {
            slider1 = _xSliderA;
        }

        if (_xSliderB.getHitRectangle().contains(devX, devY)) {
            slider2 = _xSliderB;
        }

        /*
         * check if a slider was hit
         */
        if (slider1 == null && slider2 == null) {
            // no slider was hit
            return;
        }

        /*
         * check if one slider was hit, when yes, the leftslider is set and the right slider is null
         */
        if (slider1 != null && slider2 == null) {
            // only slider 1 was hit
            _contextLeftSlider = slider1;
            return;
        }
        if (slider2 != null && slider1 == null) {
            // only slider 2 was hit
            _contextLeftSlider = slider2;
            return;
        }

        /*
         * both sliders are hit
         */
        final long xSlider1Position = slider1.getHitRectangle().x;
        final long xSlider2Position = slider2.getHitRectangle().x;

        if (xSlider1Position == xSlider2Position) {
            // both sliders are at the same position
            _contextLeftSlider = slider1;
            return;
        }
        if (xSlider1Position < xSlider2Position) {
            _contextLeftSlider = slider1;
            _contextRightSlider = slider2;
        } else {
            _contextLeftSlider = slider2;
            _contextRightSlider = slider1;
        }
    }

    private int computeXMarkerValue(final double[] xValues, final int xmStartIndex, final double valueDraggingDiff,
            final double valueXStartWithOffset) {

        int valueIndex;
        double valueX = xValues[xmStartIndex];
        double valueHalf;

        /*
         * get the marker positon for the next value
         */
        if (valueDraggingDiff > 0) {

            // moved to the right

            for (valueIndex = xmStartIndex; valueIndex < xValues.length; valueIndex++) {

                valueX = xValues[valueIndex];
                valueHalf = ((valueX - xValues[Math.min(valueIndex + 1, xValues.length - 1)]) / 2);

                if (valueX >= valueXStartWithOffset + valueHalf) {
                    break;
                }
            }

        } else {

            // moved to the left

            for (valueIndex = xmStartIndex; valueIndex >= 0; valueIndex--) {

                valueX = xValues[valueIndex];
                valueHalf = ((valueX - xValues[Math.max(0, valueIndex - 1)]) / 2);

                if (valueX < valueXStartWithOffset + valueHalf) {
                    break;
                }
            }
        }

        return Math.max(0, Math.min(valueIndex, xValues.length - 1));
    }

    /**
     * create the context menu
     */
    private void createContextMenu() {

        final MenuManager menuMgr = new MenuManager();

        menuMgr.setRemoveAllWhenShown(true);

        menuMgr.addMenuListener(new IMenuListener() {
            @Override
            public void menuAboutToShow(final IMenuManager menuMgr) {

                actionSelectBars();

                _hoveredBarToolTip.toolTip_20_Hide();
                hideTooltip();

                // get cursor location relativ to this graph canvas
                final Point devMouse = toControl(getDisplay().getCursorLocation());

                computeSliderForContextMenu(devMouse.x, devMouse.y);

                _chart.fillContextMenu(menuMgr, _contextLeftSlider, _contextRightSlider, _hoveredBarSerieIndex,
                        _hoveredBarValueIndex, _devXMouseDown, _devYMouseDown);
            }
        });

        final Menu contextMenu = menuMgr.createContextMenu(this);

        contextMenu.addMenuListener(new MenuAdapter() {
            @Override
            public void menuHidden(final MenuEvent e) {
                _chart.onHideContextMenu(e, ChartComponentGraph.this);
            }

            @Override
            public void menuShown(final MenuEvent e) {
                _chart.onShowContextMenu(e, ChartComponentGraph.this);
            }
        });

        setMenu(contextMenu);
    }

    /**
     * Create a cursor resource from an image file
     * 
     * @param imageName
     * @return
     */
    private Cursor createCursorFromImage(final String imageName) {

        Image cursorImage = null;
        final ImageDescriptor imageDescriptor = Activator.getImageDescriptor(imageName);

        if (imageDescriptor == null) {

            final String resourceName = "icons/" + imageName;//$NON-NLS-1$

            final ClassLoader classLoader = getClass().getClassLoader();

            final InputStream imageStream = classLoader == null
                    ? ClassLoader.getSystemResourceAsStream(resourceName)
                    : classLoader.getResourceAsStream(resourceName);

            if (imageStream == null) {
                return null;
            }

            cursorImage = new Image(Display.getCurrent(), imageStream);

        } else {

            cursorImage = imageDescriptor.createImage();
        }

        final Cursor cursor = new Cursor(getDisplay(), cursorImage.getImageData(), 0, 0);

        cursorImage.dispose();

        return cursor;
    }

    /**
     * Creates the label(s) and the position for each graph
     * 
     * @param gc
     * @param xSlider
     */
    private void createXSliderLabel(final GC gc, final ChartXSlider xSlider) {

        final int devSliderLinePos = (int) (xSlider.getXXDevSliderLinePos() - _xxDevViewPortLeftBorder);

        int sliderValueIndex = xSlider.getValuesIndex();
        // final int valueX = slider.getValueX();

        final ArrayList<ChartXSliderLabel> labelList = new ArrayList<ChartXSliderLabel>();
        xSlider.setLabelList(labelList);

        final ScrollBar hBar = getHorizontalBar();
        final int hBarOffset = hBar.isVisible() ? hBar.getSelection() : 0;

        final int leftPos = hBarOffset;
        final int rightPos = leftPos + getDevVisibleChartWidth();

        // create slider label for each graph
        for (final GraphDrawingData drawingData : _allGraphDrawingData) {

            final ChartDataYSerie yData = drawingData.getYData();
            final int labelFormat = yData.getSliderLabelFormat();
            final int valueDivisor = yData.getValueDivisor();
            final int displayedDigits = yData.getDisplayedFractionalDigits();
            final float[][] allYValues = yData.getHighValuesFloat();
            final ISliderLabelProvider sliderLabelProvider = yData.getSliderLabelProvider();

            final boolean isSliderLabelProviderAvailable = sliderLabelProvider != null;

            if (labelFormat == ChartDataYSerie.SLIDER_LABEL_FORMAT_MM_SS || isSliderLabelProviderAvailable) {

                // format: mm:ss or custom label provider

            } else {

                // use default format: ChartDataYSerie.SLIDER_LABEL_FORMAT_DEFAULT

                _nf.setMinimumFractionDigits(displayedDigits);
                _nf.setMaximumFractionDigits(displayedDigits);
            }

            final ChartXSliderLabel label = new ChartXSliderLabel();
            labelList.add(label);

            // draw label on the left or on the right side of the slider,
            // depending on the slider position
            final float[] yValues = allYValues[0];

            // make sure the slider value index is not of bounds, this can
            // happen when the data have changed
            sliderValueIndex = Math.min(sliderValueIndex, yValues.length - 1);

            final float yValue = yValues[sliderValueIndex];
            // final int xAxisUnit = xData.getAxisUnit();
            final StringBuilder labelText = new StringBuilder();

            // create the slider text
            if (isSliderLabelProviderAvailable) {

                labelText.append(sliderLabelProvider.getLabel(sliderValueIndex));

            } else if (labelFormat == ChartDataYSerie.SLIDER_LABEL_FORMAT_MM_SS) {

                // format: mm:ss

                labelText.append(Util.format_mm_ss((long) yValue));

            } else {

                // use default format: ChartDataYSerie.SLIDER_LABEL_FORMAT_DEFAULT

                if (valueDivisor == 1) {
                    labelText.append(_nf.format(yValue));
                } else {
                    labelText.append(_nf.format(yValue / valueDivisor));
                }
            }

            final String unitLabel = yData.getUnitLabel();
            if (unitLabel.length() > 0) {
                labelText.append(' ');
                labelText.append(unitLabel);
            }
            labelText.append(' ');

            // calculate position of the slider label
            final Point labelExtend = gc.stringExtent(labelText.toString());
            final int labelWidth = labelExtend.x + 0;
            int labelXPos = devSliderLinePos - labelWidth / 2;

            final long labelRightPos = labelXPos + labelWidth;

            if (xSlider == _xSliderDragged) {
                /*
                 * current slider is the dragged slider, clip the slider label position at the
                 * viewport
                 */
                if (labelXPos < leftPos) {
                    labelXPos += (leftPos - labelXPos);
                } else if (labelRightPos >= rightPos) {
                    labelXPos = rightPos - labelWidth - 1;
                }

            } else {
                /*
                 * current slider is not dragged, clip the slider label position at the chart bounds
                 */
                if (labelXPos < 0) {

                    labelXPos = 0;

                } else {

                    /*
                     * show the whole label when the slider is on the right border
                     */
                    if (labelRightPos > getDevVisibleChartWidth()) {
                        labelXPos = getDevVisibleChartWidth() - labelWidth - 1;
                    }
                }
            }

            // show also value index for debugging
            //         label.text = labelText.toString() + " " + xSlider.getValuesIndex();
            label.text = labelText.toString();

            label.height = labelExtend.y - 5;
            label.width = labelWidth;

            label.x = labelXPos;
            label.y = drawingData.getDevYBottom() - drawingData.devGraphHeight - label.height;

            /*
             * get the y position of the marker which marks the y value in the graph
             */
            int devYGraph = drawingData.getDevYBottom()
                    - (int) ((yValue - drawingData.getGraphYBottom()) * drawingData.getScaleY());

            if (yValue < yData.getVisibleMinValue()) {
                devYGraph = drawingData.getDevYBottom();
            }
            if (yValue > yData.getVisibleMaxValue()) {
                devYGraph = drawingData.getDevYTop();
            }
            label.devYGraph = devYGraph;
        }
    }

    void disposeColors() {
        _colorCache.dispose();
    }

    /**
     * @param eventTime
     */
    private void doAutoScroll(final long eventTime) {

        // this is not working the mouse can't sometime not be zoomed to the border, depending on the mouse speed
        //      /*
        //       * check if the mouse has reached the left or right border
        //       */
        //      if (_graphDrawingData == null
        //            || _graphDrawingData.size() == 0
        //            || _hoveredLineValueIndex == 0
        //            || _hoveredLineValueIndex == _graphDrawingData.get(0).getXData()._highValues.length - 1) {
        //
        //         _isAutoScroll = false;
        //
        //         return;
        //      }

        final int AUTO_SCROLL_INTERVAL = 50; // 20ms == 50fps

        _isAutoScroll = true;

        _autoScrollCounter[0]++;

        getDisplay().timerExec(AUTO_SCROLL_INTERVAL, new Runnable() {

            final int __runnableScrollCounter = _autoScrollCounter[0];

            @Override
            public void run() {

                if (__runnableScrollCounter != _autoScrollCounter[0]) {
                    return;
                }

                if (isDisposed() || _isAutoScroll == false
                        || (_xSliderDragged == null && _devXAutoScrollMousePosition == Integer.MIN_VALUE)) {

                    // make sure that autoscroll/automove is disabled

                    _isAutoScroll = false;
                    _devXAutoScrollMousePosition = Integer.MIN_VALUE;

                    return;
                }

                _isDisableHoveredLineValueIndex = true;

                /*
                 * the offset values are determined experimentally and depends on the mouse position
                 */
                int devMouseOffset = 0;

                if (_devXAutoScrollMousePosition != Integer.MIN_VALUE) {

                    boolean isLeft;

                    if (_devXAutoScrollMousePosition < 0) {

                        // autoscroll the graph to the left

                        isLeft = true;

                        if (_xxDevViewPortLeftBorder == 0) {

                            // left border is already reached, auto scrolling is not necessary
                            return;
                        }

                        for (final int[] accelerator : _leftAccelerator) {
                            if (_devXAutoScrollMousePosition < accelerator[0]) {
                                devMouseOffset = accelerator[1];
                                break;
                            }
                        }

                    } else {

                        // autoscroll the graph to the right

                        isLeft = false;

                        final int devXAutoScrollMousePositionRelative = _devXAutoScrollMousePosition
                                - getDevVisibleChartWidth();

                        for (final int[] accelerator : _rightAccelerator) {
                            if (devXAutoScrollMousePositionRelative < accelerator[0]) {
                                devMouseOffset = accelerator[1];
                                break;
                            }
                        }
                    }

                    doAutoScroll_10_RunnableScrollGraph(this, AUTO_SCROLL_INTERVAL, devMouseOffset, isLeft,
                            eventTime);

                } else {

                    if (_devXDraggedXSliderLine < 0) {

                        // move x-slider to the left

                        for (final int[] accelerator : _leftAccelerator) {
                            if (_devXDraggedXSliderLine < accelerator[0]) {
                                devMouseOffset = accelerator[1];
                                break;
                            }
                        }

                    } else {

                        // move x-slider to the right

                        final int devXSliderLineRelative = _devXDraggedXSliderLine - getDevVisibleChartWidth();

                        for (final int[] accelerator : _rightAccelerator) {
                            if (devXSliderLineRelative < accelerator[0]) {
                                devMouseOffset = accelerator[1];
                                break;
                            }
                        }
                    }

                    doAutoScroll_20_RunnableMoveSlider(this, AUTO_SCROLL_INTERVAL, devMouseOffset);
                }
            }
        });
    }

    private void doAutoScroll_10_RunnableScrollGraph(final Runnable runnable, final int autoScrollInterval,
            final int devMouseOffset, final boolean isLeft, final long eventTime) {

        final long xxDevNewPosition = _xxDevViewPortLeftBorder + devMouseOffset;

        // reposition chart
        setChartPosition(xxDevNewPosition);

        // check if scrolling can be redone
        final long devXRightBorder = _xxDevGraphWidth - getDevVisibleChartWidth();

        boolean isRepeatScrolling;
        if (isLeft) {
            isRepeatScrolling = _xxDevViewPortLeftBorder > 1;
        } else {
            isRepeatScrolling = xxDevNewPosition < devXRightBorder;
        }

        // start scrolling again when the bounds have not been reached
        if (isRepeatScrolling) {
            getDisplay().timerExec(autoScrollInterval, runnable);
        } else {
            _isAutoScroll = false;
        }

        doAutoScroll_30_UpdateHoveredListener(eventTime);
    }

    private void doAutoScroll_20_RunnableMoveSlider(final Runnable runnable, final int autoScrollInterval,
            final int devMouseOffset) {

        // get new slider position
        final long xxDevOldSliderLinePos = _xSliderDragged.getXXDevSliderLinePos();
        final long xxDevNewSliderLinePos2 = xxDevOldSliderLinePos + devMouseOffset;
        final long xxDevNewSliderLinePos3 = xxDevNewSliderLinePos2 - _xxDevViewPortLeftBorder;

        // move the slider
        moveXSlider(_xSliderDragged, xxDevNewSliderLinePos3);

        // redraw slider
        _isSliderDirty = true;
        redraw();

        // redraw chart
        setChartPosition(_xSliderDragged, false, true, 0);

        final boolean isRepeatScrollingLeft = _xxDevViewPortLeftBorder > 1;
        final boolean isRepeatScrollingRight = _xxDevViewPortLeftBorder + xxDevNewSliderLinePos3 < _xxDevGraphWidth;
        final boolean isRepeatScrolling = isRepeatScrollingLeft || isRepeatScrollingRight;

        // start scrolling again when the bounds have not been reached
        if (isRepeatScrolling) {
            getDisplay().timerExec(autoScrollInterval, runnable);
        } else {
            _isAutoScroll = false;
        }
    }

    private void doAutoScroll_30_UpdateHoveredListener(final long eventTime) {

        final IHoveredValueListener hoveredListener = _chart._hoveredListener;

        if (_isHoveredLineVisible || hoveredListener != null) {

            final int hoveredLineValueIndexBACKUP = _hoveredValuePointIndex;

            setHoveredLineValue();

            if (_hoveredValuePointIndex != -1) {

                final PointLong devHoveredValueDevPosition = getHoveredValueDevPosition();

                if (hoveredListener != null) {

                    /**
                     * this is very tricky:
                     * <p>
                     * the last mouse move position is used
                     */

                    hoveredListener.hoveredValue(eventTime, _devXMouseMove, _devYMouseMove, _hoveredValuePointIndex,
                            devHoveredValueDevPosition);
                }
            }

            _hoveredValuePointIndex = hoveredLineValueIndexBACKUP;
        }
    }

    private void doAutoZoomToXSliders() {

        if (_canAutoZoomToSlider) {

            // the graph can't be scrolled but the graph should be
            // zoomed to the x-slider positions

            // zoom into the chart
            getDisplay().asyncExec(new Runnable() {
                @Override
                public void run() {
                    if (!isDisposed()) {
                        _chart.onExecuteZoomInWithSlider();
                    }
                }
            });
        }
    }

    /**
     * Draw the graphs into the chart+graph image
     * 
     * @return Return <code>true</code> when the graph was painted directly and not in async mode
     */
    private boolean drawAsync_100_StartPainting() {

        //      // get time when the redraw of the may is requested
        //      final long requestedRedrawTime = System.currentTimeMillis();

        _drawAsyncCounter[0]++;

        //      if (requestedRedrawTime > _lastChartDrawingTime + 100) {
        //
        //         // force a redraw
        //
        //         drawAsync101DoPainting();
        //
        //         return true;
        //
        //      } else {
        //
        getDisplay().asyncExec(new Runnable() {

            final int __runnableBgCounter = _drawAsyncCounter[0];

            @Override
            public void run() {

                /*
                 * create the chart image only when a new onPaint event has not occured
                 */
                if (__runnableBgCounter != _drawAsyncCounter[0]) {
                    // a new onPaint event occured
                    return;
                }

                drawAsync_101_DoPainting();
            }
        });
        //      }

        return false;
    }

    private void drawAsync_101_DoPainting() {

        //      final long startTime = System.nanoTime();
        //      // TODO remove SYSTEM.OUT.PRINTLN

        if (isDisposed()) {
            // this widget is disposed
            return;
        }

        if (_allGraphDrawingData.size() == 0) {
            // drawing data are not set
            return;
        }

        // ensure minimum size
        final int devNewImageWidth = Math.max(ChartComponents.CHART_MIN_WIDTH, getDevVisibleChartWidth());

        /*
         * the image size is adjusted to the client size but it must be within the min/max ranges
         */
        final int devNewImageHeight = Math.max(ChartComponents.CHART_MIN_HEIGHT,
                Math.min(getDevVisibleGraphHeight(), ChartComponents.CHART_MAX_HEIGHT));

        /*
         * when the image is the same size as the new we will redraw it only if it is set to dirty
         */
        if (_isChartDirty == false && _chartImage_20_Chart != null) {

            final Rectangle oldBounds = _chartImage_20_Chart.getBounds();

            if (oldBounds.width == devNewImageWidth && oldBounds.height == devNewImageHeight) {
                return;
            }
        }

        final Rectangle chartImageRect = new Rectangle(0, 0, devNewImageWidth, devNewImageHeight);

        // ensure correct image size
        if (chartImageRect.width <= 0 || chartImageRect.height <= 0) {
            return;
        }

        // create image on which the graph is drawn
        if (Util.canReuseImage(_chartImage_20_Chart, chartImageRect) == false) {
            _chartImage_20_Chart = Util.createImage(getDisplay(), _chartImage_20_Chart, chartImageRect);
        }

        /*
         * The graph image is only a part where ONE single graph is painted without any title or
         * unit tick/values
         */
        final GraphDrawingData graphDrawingData = _allGraphDrawingData.get(0);
        final int devGraphHeight = graphDrawingData.devGraphHeight;
        final Rectangle graphImageRect = new Rectangle(0, 0, //
                devNewImageWidth, devGraphHeight < 1 ? 1 : devGraphHeight + 1); // ensure valid height

        if (Util.canReuseImage(_chartImage_10_Graphs, graphImageRect) == false) {
            _chartImage_10_Graphs = Util.createImage(getDisplay(), _chartImage_10_Graphs, graphImageRect);
        }

        // create chart context
        final GC gcChart = new GC(_chartImage_20_Chart);
        final GC gcGraph = new GC(_chartImage_10_Graphs);
        {
            gcChart.setFont(_uiFont);

            // fill background

            if (graphDrawingData.getChartType() == ChartType.HISTORY) {

                final Color historyColor = new Color(gcChart.getDevice(), 0xf0, 0xf0, 0xf0);
                {
                    gcChart.setBackground(historyColor);
                }
                historyColor.dispose();

            } else {
                gcChart.setBackground(_chart.getBackgroundColor());
            }
            gcChart.fillRectangle(_chartImage_20_Chart.getBounds());

            if (_chartComponents.errorMessage == null) {

                drawAsync_110_GraphImage(gcChart, gcGraph);

            } else {

                // an error was set in the chart data model
                drawSyncBg_999_ErrorMessage(gcChart);
            }
        }
        gcChart.dispose();
        gcGraph.dispose();

        // remove dirty status
        _isChartDirty = false;

        // dragged image will be painted until the graph image is recomputed
        _isPaintDraggedImage = false;

        // force the overlay image to be redrawn
        _isOverlayDirty = true;

        redraw();

        //      final long endTime = System.nanoTime();
        //      System.out.println(UI.timeStampNano()
        //            + " drawAsync100: "
        //            + (((double) endTime - startTime) / 1000000)
        //            + " ms   #:"
        //            + _drawAsyncCounter[0]);
        //      // TODO remove SYSTEM.OUT.PRINTLN
    }

    /**
     * Draw all graphs, each graph is painted in the same canvas (gcGraph) which is painted in the
     * the chart image (gcChart).
     * 
     * @param gcChart
     * @param gcGraph
     */
    private void drawAsync_110_GraphImage(final GC gcChart, final GC gcGraph) {

        int graphNo = 0;
        final int lastGraphNo = _allGraphDrawingData.size();

        final boolean isStackedChart = !_isChartOverlapped;

        ArrayList<GraphDrawingData> allGraphDrawingData;
        if (_canChartBeOverlapped && _isChartOverlapped) {
            allGraphDrawingData = _revertedGraphDrawingData;
        } else {
            allGraphDrawingData = _allGraphDrawingData;
        }

        // reset line positions, they are set when a line graph is painted
        _lineDevPositions.clear();
        _lineFocusRectangles.clear();

        final Color chartBackgroundColor = _chart.getBackgroundColor();
        final Rectangle graphBounds = _chartImage_10_Graphs.getBounds();

        // loop: all graphs in the chart
        for (final GraphDrawingData graphDrawingData : allGraphDrawingData) {

            graphNo++;

            final boolean isFirstGraph = graphNo == 1;
            final boolean isLastGraph = graphNo == lastGraphNo;

            final boolean isFirstOverlappedGraph = _isChartOverlapped && graphNo == 1;
            final boolean isLastOverlappedGraph = _isChartOverlapped && isLastGraph;

            final boolean isDrawBackground = !_canChartBeOverlapped
                    || (_canChartBeOverlapped && (isStackedChart || isFirstOverlappedGraph));

            final boolean isDrawXUnits = !_canChartBeOverlapped
                    || (_canChartBeOverlapped && (isStackedChart || isLastOverlappedGraph));

            final boolean isDrawGrid = !_canChartBeOverlapped
                    || (_canChartBeOverlapped && (isStackedChart || isFirstOverlappedGraph));

            boolean isDrawTitle;
            if (_canChartBeOverlapped && _isChartOverlapped) {
                isDrawTitle = isLastGraph;
            } else {
                isDrawTitle = isFirstGraph;
            }

            // fill background
            if (isDrawBackground) {

                gcGraph.setBackground(chartBackgroundColor);
                gcGraph.fillRectangle(graphBounds);

                if (_chart.isShowSegmentAlternateColor && chartTitleSegmentConfig.isShowSegmentBackground) {
                    drawAsync_150_SegmentBackground(gcGraph, graphDrawingData);
                }
            }

            if (isDrawTitle) {
                drawAsync_200_XTitle(gcChart, graphDrawingData);
            }

            if (isDrawGrid) {

                // draw horizontal grid
                drawAsync_220_HGrid(gcGraph, graphDrawingData);
            }

            if (isDrawXUnits) {

                if (isLastGraph) {
                    // draw the unit label and unit tick for the last graph
                    drawAsync_210_XUnits_VGrid(gcChart, gcGraph, graphDrawingData, true);
                } else {
                    drawAsync_210_XUnits_VGrid(gcChart, gcGraph, graphDrawingData, false);
                }
            }

            if (chartTitleSegmentConfig.isShowSegmentSeparator) {
                drawAsync_240_TourSements(gcGraph, graphDrawingData);
            }

            // draw units and grid on the x and y axis
            final ChartType chartType = graphDrawingData.getChartType();

            if (chartType == ChartType.LINE) {

                drawAsync_500_LineGraph(gcGraph, graphDrawingData, isLastGraph);
                drawAsync_520_RangeMarker(gcGraph, graphDrawingData);

            } else if (chartType == ChartType.BAR) {

                drawAsync_530_BarGraph(gcGraph, graphDrawingData);

            } else if (chartType == ChartType.LINE_WITH_BARS) {

                drawAsync_540_LineWithBarGraph(gcGraph, graphDrawingData);

            } else if (chartType == ChartType.XY_SCATTER) {

                drawAsync_550_XYScatter(gcGraph, graphDrawingData);

            } else if (chartType == ChartType.HORIZONTAL_BAR) {

                drawAsync_560_HorizontalBar(gcGraph, graphDrawingData, isLastGraph);

            } else if (chartType == ChartType.HISTORY) {

                drawAsync_600_History(gcGraph, graphDrawingData);
            }

            // draw only the x-axis, this is drawn lately because the graph can overwrite it
            drawAsync_230_XAxis(gcGraph, graphDrawingData);

            // draw graph image into the chart image
            gcChart.drawImage(_chartImage_10_Graphs, 0, graphDrawingData.getDevYTop());

            //         System.out.println("20 <- 10\tdrawAsync110GraphImage");
            //         // TODO remove SYSTEM.OUT.PRINTLN
        }

        if (_canChartBeOverlapped && _isChartOverlapped) {

            // Revert sequence otherwise they are painted wrong.

            Collections.reverse(_lineDevPositions);
            Collections.reverse(_lineFocusRectangles);
        }

        if (valuePointToolTip != null && _hoveredValuePointIndex != -1) {

            final PointLong hoveredLinePosition = getHoveredValueDevPosition();

            valuePointToolTip.setValueIndex(_hoveredValuePointIndex, _devXMouseMove, _devYMouseMove,
                    hoveredLinePosition, _graphZoomRatio);
        }
    }

    /**
     * Draw segment background
     * 
     * @param gc
     * @param drawingData
     */
    private void drawAsync_150_SegmentBackground(final GC gc, final GraphDrawingData drawingData) {

        final ChartStatisticSegments chartSegments = drawingData.getXData().getChartSegments();

        if (chartSegments == null) {
            return;
        }

        final int devYTop = 0;
        final int devYBottom = drawingData.devGraphHeight;

        final double scaleX = drawingData.getScaleX();

        final double[] segmentStartValue = chartSegments.segmentStartValue;
        final double[] segmentEndValue = chartSegments.segmentEndValue;

        if (segmentStartValue == null || segmentEndValue == null) {
            return;
        }

        final Color alternateColor = new Color(gc.getDevice(), _chart.segmentAlternateColor);
        {
            for (int segmentIndex = 0; segmentIndex < segmentStartValue.length; segmentIndex++) {

                if (segmentIndex % 2 == 1) {

                    // draw segment background color for every second segment

                    final double startValue = segmentStartValue[segmentIndex];
                    final double endValue = segmentEndValue[segmentIndex];

                    final int devXValueStart = (int) ((scaleX * startValue) - _xxDevViewPortLeftBorder);

                    // adjust endValue to fill the last part of the segment
                    final int devValueEnd = (int) (scaleX * (endValue + 1) - _xxDevViewPortLeftBorder);

                    gc.setBackground(alternateColor);
                    gc.fillRectangle(//
                            devXValueStart, devYTop, devValueEnd - devXValueStart, devYBottom - devYTop);
                }
            }
        }
        alternateColor.dispose();
    }

    private void drawAsync_200_XTitle(final GC gc, final GraphDrawingData graphDrawingData) {

        final Display display = Display.getCurrent();

        final int devYBottom = graphDrawingData.getDevYBottom();
        final int devYGraphTop = devYBottom - graphDrawingData.devGraphHeight;

        final ChartDataXSerie xData = graphDrawingData.getXData();
        final ChartStatisticSegments chartSegments = xData.getChartSegments();
        final HistoryTitle historyTitle = xData.getHistoryTitle();

        final int devYTitle = _chartDrawingData.devMarginTop;

        final ArrayList<ChartTitleSegment> chartTitleSegments = _chartDrawingData.chartTitleSegments;

        final int devGraphWidth = getDevVisibleChartWidth();

        if (historyTitle != null) {

            /*
             * draw title for each history top segment
             */

            final double scaleX = graphDrawingData.getScaleX();

            final ArrayList<Long> graphStartValues = historyTitle.graphStart;
            final ArrayList<Long> graphEndValues = historyTitle.graphEnd;
            final ArrayList<String> titleTextList = historyTitle.titleText;

            if (graphStartValues != null && titleTextList != null) {

                final int xUnitTextPos = graphDrawingData.getTitleTextPosition();

                for (int graphIndex = 0; graphIndex < graphStartValues.size(); graphIndex++) {

                    final String titleText = titleTextList.get(graphIndex);
                    final long graphStart = graphStartValues.get(graphIndex);
                    final long graphEnd = graphEndValues.get(graphIndex);

                    final double devXSegmentStart = (scaleX * graphStart - _xxDevViewPortLeftBorder);
                    final double devXSegmentEnd = ((scaleX * graphEnd - _xxDevViewPortLeftBorder)) - 1.0;

                    final double devXSegmentLength = devXSegmentEnd - devXSegmentStart;
                    final double devXSegmentCenter = devXSegmentStart + (devXSegmentLength / 2);

                    final int devXTitleCenter = gc.textExtent(titleText).x / 2;
                    int devX;
                    if (xUnitTextPos == GraphDrawingData.X_UNIT_TEXT_POS_CENTER) {

                        // draw between 2 units

                        devX = (int) (devXSegmentCenter - devXTitleCenter);

                    } else {

                        // draw in the center of the tick

                        devX = (int) (devXSegmentStart - devXTitleCenter);
                    }

                    gc.drawText(titleText, devX, devYTitle, false);

                    //               // debug: draw segments
                    //               final int devYGraphBottom = graphDrawingData.devGraphHeight;
                    //
                    //               gc.setForeground(Display.getCurrent().getSystemColor(SWT.COLOR_RED));
                    //               gc.drawLine((int) devXSegmentStart, 0, (int) devXSegmentStart, devYGraphBottom);
                    //
                    //               gc.setForeground(Display.getCurrent().getSystemColor(SWT.COLOR_BLUE));
                    //               gc.drawLine((int) devXSegmentEnd, 0, (int) devXSegmentEnd, devYGraphBottom);
                }
            }

        } else if (chartSegments != null) {

            /*
             * draw title for each chart segment
             */

            final double scaleX = graphDrawingData.getScaleX();
            final int devVisibleChartWidth = graphDrawingData.getChartDrawingData().devVisibleChartWidth;

            final double[] valueStart = chartSegments.segmentStartValue;
            final double[] valueEnd = chartSegments.segmentEndValue;
            final String[] segmentTitles = chartSegments.segmentTitle;
            final Object[] segmentCustomData = chartSegments.segmentCustomData;

            final boolean isZoomed = getZoomRatio() > 1.0;

            if (valueStart != null && valueEnd != null && segmentTitles != null) {

                final int titlePadding = 5;
                int devXTitleEnd = -1;
                boolean isFirstSegment = true;

                for (int segmentIndex = 0; segmentIndex < valueStart.length; segmentIndex++) {

                    String segmentTitle = segmentTitles[segmentIndex];

                    if (segmentTitle == null || segmentTitle.length() == 0) {

                        /*
                         * Create a dummy title, that the title segment is created. This segment is
                         * used to get the hovered tour when multiple tours are displayed.
                         */
                        segmentTitle = UI.EMPTY_STRING;
                    }

                    final int devXSegmentStart = (int) (scaleX * valueStart[segmentIndex]
                            - _xxDevViewPortLeftBorder);
                    final int devXSegmentEnd = (int) (scaleX * (valueEnd[segmentIndex] + 1)
                            - _xxDevViewPortLeftBorder);

                    if (devXSegmentEnd < 0) {
                        // segment is to the left of the left border
                        continue;
                    }

                    if (devXSegmentStart > devVisibleChartWidth) {
                        // segment is to the right of the right border
                        break;
                    }

                    final Point textExtent = gc.textExtent(segmentTitle);
                    final int titleWidth = textExtent.x;
                    final int titleHeight = textExtent.y;
                    final int titleWidth2 = titleWidth / 2;

                    final int visibleSegmentStart = Math.max(0, devXSegmentStart);
                    final int visibleSegmentEnd = Math.min(devXSegmentEnd, devVisibleChartWidth);
                    final int visibleSegmentWidth = visibleSegmentEnd - visibleSegmentStart;

                    // center in the middle of the visible segment
                    int devXTitle = visibleSegmentStart + visibleSegmentWidth / 2 - titleWidth2;

                    if (isFirstSegment && isZoomed == false) {

                        // title for the first segment is always displayed when not zoomed

                        isFirstSegment = false;

                        // ensure title is not truncated at the left border
                        if (devXTitle < 0) {
                            devXTitle = 0;
                        }
                    }

                    if (chartTitleSegmentConfig.isShowSegmentTitle) {

                        // ensure that the title do not overlap a previous title
                        if (devXTitle > devXTitleEnd) {

                            // keep position when the title is drawn
                            devXTitleEnd = devXTitle + titleWidth + titlePadding;

                            gc.setForeground(display.getSystemColor(SWT.COLOR_BLACK));
                            gc.drawText(segmentTitle, devXTitle, devYTitle, false);
                        }
                    }

                    /*
                     * Keep segment drawing data
                     */
                    final ChartTitleSegment chartTitleSegment = new ChartTitleSegment();

                    if (segmentCustomData != null && segmentCustomData[segmentIndex] instanceof Long) {
                        chartTitleSegment.setTourId((Long) segmentCustomData[segmentIndex]);
                    }

                    chartTitleSegment.devXTitle = devXTitle;
                    chartTitleSegment.devYTitle = devYTitle;

                    chartTitleSegment.titleWidth = titleWidth;
                    chartTitleSegment.titleHeight = titleHeight;

                    chartTitleSegment.devYGraphTop = devYGraphTop;
                    chartTitleSegment.devGraphWidth = _chartDrawingData.devVisibleChartWidth;

                    chartTitleSegment.devXSegment = visibleSegmentStart;
                    chartTitleSegment.devSegmentWidth = visibleSegmentWidth;

                    chartTitleSegments.add(chartTitleSegment);
                }
            }

        } else {

            /*
             * draw default title, center within the chart
             */

            String title = graphDrawingData.getXTitle();
            if (title == null) {

                // ensure the tour segment is visible

                title = UI.EMPTY_STRING;
            }

            final Point textExtent = gc.textExtent(title);
            final int titleWidth = textExtent.x;
            final int titleHeight = textExtent.y;

            final int devXTitle = (devGraphWidth / 2) - (titleWidth / 2);

            if (chartTitleSegmentConfig.isShowSegmentTitle) {

                gc.drawText(title, //
                        devXTitle < 0 ? 0 : devXTitle, devYTitle, true);
            }

            /*
             * Keep segment drawing data
             */
            final ChartTitleSegment chartTitleSegment = new ChartTitleSegment();

            final Object tourIdObject = _chartDrawingData.chartDataModel.getCustomData(Chart.CUSTOM_DATA_TOUR_ID);
            if (tourIdObject instanceof Long) {
                chartTitleSegment.setTourId((Long) tourIdObject);
            }

            chartTitleSegment.devXTitle = devXTitle;
            chartTitleSegment.devYTitle = devYTitle;

            chartTitleSegment.titleWidth = titleWidth;
            chartTitleSegment.titleHeight = titleHeight;

            chartTitleSegment.devYGraphTop = devYGraphTop;
            chartTitleSegment.devGraphWidth = _chartDrawingData.devVisibleChartWidth;

            chartTitleSegment.devXSegment = 0;
            chartTitleSegment.devSegmentWidth = _chartDrawingData.devVisibleChartWidth;

            chartTitleSegments.add(chartTitleSegment);
        }
    }

    /**
     * Draw the unit label, tick and the vertical grid line for the x axis
     * 
     * @param gcChart
     * @param gcGraph
     * @param graphDrawingData
     * @param isDrawUnit
     *            <code>true</code> indicate to draws the unit tick and unit label additional to the
     *            unit grid line
     */
    private void drawAsync_210_XUnits_VGrid(final GC gcChart, final GC gcGraph,
            final GraphDrawingData graphDrawingData, final boolean isDrawUnit) {

        final Display display = getDisplay();

        final ArrayList<ChartUnit> xUnits = graphDrawingData.getXUnits();

        final ChartDataXSerie xData = graphDrawingData.getXData();
        final int devYBottom = graphDrawingData.getDevYBottom();
        final int xUnitTextPos = graphDrawingData.getXUnitTextPos();
        double scaleX = graphDrawingData.getScaleX();

        final boolean isHistory = graphDrawingData.getChartType() == ChartType.HISTORY;
        final boolean isDrawVerticalGrid = _chart.isShowVerticalGridLines || isHistory;
        final boolean[] isDrawUnits = graphDrawingData.isDrawUnits();
        final boolean isXUnitOverlapChecked = graphDrawingData.isXUnitOverlapChecked();
        final double devGraphWidth = graphDrawingData.devVirtualGraphWidth;
        final boolean isCheckUnitBorderOverlap = graphDrawingData.isCheckUnitBorderOverlap();

        final double xAxisStartValue = xData.getXAxisMinValueForced();
        final double scalingFactor = xData.getScalingFactor();
        final double scalingMaxValue = xData.getScalingMaxValue();
        final boolean isExtendedScaling = scalingFactor != 1.0;
        final double extScaleX = ((devGraphWidth - 1) / Math.pow(scalingMaxValue, scalingFactor));

        // check if the x-units has a special scaling
        final double scaleUnitX = graphDrawingData.getScaleUnitX();
        if (scaleUnitX != Double.MIN_VALUE) {
            scaleX = scaleUnitX;
        }

        int unitCounter = 0;
        final int devVisibleChartWidth = getDevVisibleChartWidth();

        boolean isFirstUnit = true;
        int devXLastUnitRightPosition = -1;

        final String unitLabel = graphDrawingData.getXData().getUnitLabel();
        final int devUnitLabelWidth = gcChart.textExtent(unitLabel).x;

        gcChart.setForeground(isHistory //
                ? display.getSystemColor(SWT.COLOR_BLACK)
                : display.getSystemColor(SWT.COLOR_DARK_GRAY));

        gcGraph.setForeground(_gridColor);

        final int xUnitSize = xUnits.size();
        int devNextXUnitTick = Integer.MIN_VALUE;
        int devUnitWidth = 0;
        ChartUnit nextXUnit = null;

        unitLoop:

        for (int unitIndex = 0; unitIndex < xUnitSize; unitIndex++) {

            /*
             * get unit tick position and the width to the next unit tick
             */
            int devXUnitTick;
            ChartUnit xUnit = null;
            if (nextXUnit != null) {

                xUnit = nextXUnit;

                devXUnitTick = devNextXUnitTick;

            } else {

                // this is the first unit

                xUnit = xUnits.get(unitIndex);

                // get dev x-position for the unit tick
                if (isExtendedScaling) {

                    // logarithmic scaling
                    final double scaledUnitValue = ((Math.pow(xUnit.value, scalingFactor)) * extScaleX);
                    devXUnitTick = (int) (scaledUnitValue);

                } else {

                    // scale with devXOffset
                    devXUnitTick = (int) (scaleX * (xUnit.value - xAxisStartValue) - _xxDevViewPortLeftBorder);
                }
            }

            if (unitIndex < xUnitSize - 1) {

                nextXUnit = xUnits.get(unitIndex + 1);

                // get dev x-position for the unit tick
                if (isExtendedScaling) {

                    // extended scaling
                    final double scaledUnitValue = ((Math.pow(nextXUnit.value, scalingFactor)) * extScaleX);
                    devNextXUnitTick = (int) (scaledUnitValue);

                } else {

                    // scale with devXOffset
                    devNextXUnitTick = (int) (scaleX * (nextXUnit.value - xAxisStartValue)
                            - _xxDevViewPortLeftBorder);
                }

                devUnitWidth = devNextXUnitTick - devXUnitTick;
            }

            /*
             * skip units which are outside of the visible area
             */
            if (devXUnitTick < 0 && devNextXUnitTick < 0) {
                continue;
            }
            if (devXUnitTick > devVisibleChartWidth) {
                break;
            }

            if (isDrawUnit) {

                /*
                 * draw unit tick
                 */
                if (devXUnitTick > 0 && (isDrawUnits == null || isDrawUnits[unitCounter])) {

                    // draw unit tick, don't draw it on the vertical 0 line

                    gcChart.setLineStyle(SWT.LINE_SOLID);
                    gcChart.drawLine(devXUnitTick, devYBottom, devXUnitTick, devYBottom + 5);
                }

                /*
                 * draw unit value
                 */
                final int devUnitValueWidth = gcChart.textExtent(xUnit.valueLabel).x;

                if (devUnitWidth != 0 && xUnitTextPos == GraphDrawingData.X_UNIT_TEXT_POS_CENTER) {

                    /*
                     * draw unit value BETWEEN two units
                     */

                    final int devXUnitCenter = (devUnitWidth - devUnitValueWidth) / 2;
                    int devXUnitLabelPosition = devXUnitTick + devXUnitCenter;

                    if (isCheckUnitBorderOverlap && devXUnitLabelPosition < 0) {
                        // position could be < 0 for the first unit
                        devXUnitLabelPosition = 0;
                    }

                    if (isXUnitOverlapChecked == false && devXUnitLabelPosition <= devXLastUnitRightPosition) {

                        // skip unit when it overlaps the previous unit

                    } else {

                        gcChart.drawText(xUnit.valueLabel, devXUnitLabelPosition, devYBottom + 7, true);

                        devXLastUnitRightPosition = devXUnitLabelPosition + devUnitValueWidth + 0;
                    }

                } else {

                    /*
                     * draw unit value in the MIDDLE of the unit tick
                     */

                    final int devUnitValueWidth2 = devUnitValueWidth / 2;
                    int devXUnitValueDefaultPosition = devXUnitTick - devUnitValueWidth2;

                    if (devXUnitTick + devUnitValueWidth2 < 0) {
                        // unit label is not visible
                        continue;
                    }

                    if (isFirstUnit) {

                        // draw first unit

                        isFirstUnit = false;

                        /*
                         * this is the first unit, do not center it on the unit tick, because it
                         * would be clipped on the left border
                         */
                        int devXUnit = devXUnitValueDefaultPosition;

                        if (isCheckUnitBorderOverlap && devXUnit < 0) {
                            devXUnit = 0;
                        }

                        gcChart.drawText(xUnit.valueLabel, devXUnit, devYBottom + 7, true);

                        /*
                         * draw unit label (km, mi, h)
                         */
                        final int devXUnitLabel = devXUnit + devUnitValueWidth + 2;

                        gcChart.drawText(unitLabel, //
                                devXUnitLabel, devYBottom + 7, true);

                        devXLastUnitRightPosition = devXUnitLabel + devUnitLabelWidth + 2;

                    } else {

                        // draw subsequent units

                        if (devXUnitValueDefaultPosition >= 0) {

                            /*
                             * check if the unit value would be clipped at the right border, move it
                             * to the left to make it fully visible
                             */
                            if ((devXUnitTick + devUnitValueWidth2) > devVisibleChartWidth) {

                                devXUnitValueDefaultPosition = devVisibleChartWidth - devUnitValueWidth;

                                // check if the unit value is overlapping the previous unit value
                                if (devXUnitValueDefaultPosition <= devXLastUnitRightPosition + 2) {
                                    break unitLoop;
                                }
                            }

                            if (devXUnitValueDefaultPosition > devXLastUnitRightPosition) {

                                gcChart.drawText(xUnit.valueLabel, devXUnitValueDefaultPosition, devYBottom + 7,
                                        true);

                                devXLastUnitRightPosition = devXUnitValueDefaultPosition + devUnitValueWidth + 2;
                            }
                        }
                    }
                }
            }

            // draw vertical gridline but not on the vertical 0 line
            if (devXUnitTick > 0 && isDrawVerticalGrid) {

                if (xUnit.isMajorValue) {
                    gcGraph.setLineStyle(SWT.LINE_SOLID);
                    gcGraph.setForeground(_gridColorMajor);
                } else {
                    /*
                     * line width is a complicated topic, when it's not set the gridlines of the
                     * first graph is different than the subsequent graphs, but setting it globally
                     * degrades performance dramatically
                     */
                    //               gcGraph.setLineWidth(0);
                    gcGraph.setLineDash(DOT_DASHES);
                    gcGraph.setForeground(_gridColor);
                }
                gcGraph.drawLine(devXUnitTick, 0, devXUnitTick, graphDrawingData.devGraphHeight);

            }

            unitCounter++;
        }
    }

    /**
     * draw the horizontal gridlines or the x-axis
     * 
     * @param gcGraph
     * @param drawingData
     * @param isDrawOnlyXAsis
     */
    private void drawAsync_220_HGrid(final GC gcGraph, final GraphDrawingData drawingData) {

        if (_chart.isShowHorizontalGridLines == false) {
            // h-grid is not visible
            return;
        }

        final ArrayList<ChartUnit> yUnits = drawingData.getYUnits();
        final int unitListSize = yUnits.size();

        final double scaleY = drawingData.getScaleY();
        final float graphYBottom = drawingData.getGraphYBottom();
        final int devGraphHeight = drawingData.devGraphHeight;
        final int devVisibleChartWidth = getDevVisibleChartWidth();

        final boolean isBottomUp = drawingData.getYData().isYAxisDirection();
        final boolean isTopDown = isBottomUp == false;

        final int devYTop = 0;
        final int devYBottom = devGraphHeight;

        int devY;
        int unitIndex = 0;

        // loop: all units
        for (final ChartUnit yUnit : yUnits) {

            final double unitValue = yUnit.value;
            final double devYUnit = (((unitValue - graphYBottom) * scaleY) + 0.5);

            if (isBottomUp || unitListSize == 1) {
                devY = devYBottom - (int) devYUnit;
            } else {
                devY = devYTop + (int) devYUnit;
            }

            // check if a y-unit is on the x axis
            final boolean isXAxis = (isTopDown && unitIndex == unitListSize - 1) || //
                    (isBottomUp && unitIndex == 0);

            if (isXAxis == false) {

                // draw gridlines

                if (yUnit.isMajorValue) {
                    gcGraph.setLineStyle(SWT.LINE_SOLID);
                    gcGraph.setForeground(_gridColorMajor);
                } else {
                    gcGraph.setLineDash(DOT_DASHES);
                    gcGraph.setForeground(_gridColor);
                }
                gcGraph.drawLine(0, devY, devVisibleChartWidth, devY);
            }

            unitIndex++;
        }
    }

    /**
     * draw the horizontal gridlines or the x-axis
     * 
     * @param gcGraph
     * @param drawingData
     * @param isDrawOnlyXAsis
     */
    private void drawAsync_230_XAxis(final GC gcGraph, final GraphDrawingData drawingData) {

        final Display display = getDisplay();

        final ArrayList<ChartUnit> yUnits = drawingData.getYUnits();
        final int unitListSize = yUnits.size();

        final double scaleY = drawingData.getScaleY();
        final float graphYBottom = drawingData.getGraphYBottom();
        final int devGraphHeight = drawingData.devGraphHeight;
        final int devVisibleChartWidth = getDevVisibleChartWidth();

        final boolean isBottomUp = drawingData.getYData().isYAxisDirection();
        final boolean isTopDown = isBottomUp == false;

        final int devYTop = 0;
        final int devYBottom = devGraphHeight;

        int devY;
        int unitIndex = 0;

        // loop: all units
        for (final ChartUnit yUnit : yUnits) {

            final double unitValue = yUnit.value;
            final double devYUnit = (((unitValue - graphYBottom) * scaleY) + 0.5);

            if (isBottomUp || unitListSize == 1) {
                devY = devYBottom - (int) devYUnit;
            } else {
                devY = devYTop + (int) devYUnit;
            }

            // check if a y-unit is on the x axis
            final boolean isXAxis = (isTopDown && unitIndex == unitListSize - 1) //
                    || (isBottomUp && unitIndex == 0);

            if (isXAxis) {

                gcGraph.setLineStyle(SWT.LINE_SOLID);
                gcGraph.setForeground(display.getSystemColor(SWT.COLOR_DARK_GRAY));
                gcGraph.drawLine(0, devY, devVisibleChartWidth, devY);

                // only the x-axis needs to be drawn
                break;
            }

            unitIndex++;
        }
    }

    private void drawAsync_240_TourSements(final GC gcChart, final GraphDrawingData graphDrawingData) {

        final ArrayList<ChartTitleSegment> chartTitleSegments = _chartDrawingData.chartTitleSegments;

        if (chartTitleSegments.size() == 1) {
            // ignore one tour chart
            return;
        }

        final Display display = getDisplay();

        gcChart.setLineAttributes(LINE_DASHED);
        gcChart.setForeground(display.getSystemColor(SWT.COLOR_GRAY));

        for (int segmentIndex = 0; segmentIndex < chartTitleSegments.size(); segmentIndex++) {

            final ChartTitleSegment chartTitleSegment = chartTitleSegments.get(segmentIndex);

            if (chartTitleSegmentConfig.isShowSegmentSeparator) {

                // draw segment start line but not for the first segment
                if (segmentIndex != 0) {

                    final int devX = chartTitleSegment.devXSegment;

                    gcChart.drawLine(//
                            devX, 0, devX, graphDrawingData.getDevYBottom());
                }
            }
        }

        gcChart.setLineStyle(SWT.LINE_SOLID);
    }

    private void drawAsync_500_LineGraph(final GC gcGraph, final GraphDrawingData graphDrawingData,
            final boolean isTopGraph) {

        final ChartDataXSerie xData = graphDrawingData.getXData();
        final ChartDataYSerie yData = graphDrawingData.getYData();

        final int serieSize = xData.getHighValuesDouble()[0].length;
        final double scaleX = graphDrawingData.getScaleX();

        // create line hovered positions
        _lineFocusRectangles.add(new RectangleLong[serieSize]);
        _lineDevPositions.add(new PointLong[serieSize]);
        _isHoveredLineVisible = true;

        final RGB rgbFg = yData.getRgbLine()[0];
        final RGB rgbBgDark = yData.getRgbDark()[0];
        final RGB rgbBgBright = yData.getRgbBright()[0];

        // get the horizontal offset for the graph
        float graphValueOffset;
        if (_chartComponents._synchConfigSrc == null) {
            // a zoom marker is not set, draw it normally
            graphValueOffset = (float) (Math.max(0, _xxDevViewPortLeftBorder) / scaleX);
        } else {
            // adjust the start position to the zoom marker position
            graphValueOffset = (float) (_xxDevViewPortLeftBorder / scaleX);
        }

        final int synchMarkerStartIndex = xData.getSynchMarkerStartIndex();
        final int synchMarkerEndIndex = xData.getSynchMarkerEndIndex();

        if (synchMarkerStartIndex == -1) {

            // synch marker is not displayed

            final int graphLineAlpha = getAlphaLine();
            final int graphFillingAlpha = getAlphaFill(isTopGraph);

            drawAsync_510_LineGraphSegment(gcGraph, graphDrawingData, 0, serieSize, rgbFg, rgbBgDark, rgbBgBright,
                    graphLineAlpha, graphFillingAlpha, graphValueOffset);

        } else {

            // draw synched tour

            final double noneMarkerAlpha = 0.4;

            final int noneMarkerLineAlpha = (int) (_chart.graphTransparencyLine * noneMarkerAlpha);
            final int noneMarkerFillingAlpha = (int) (_chart.graphTransparencyFilling * noneMarkerAlpha);

            // draw graph without marker
            drawAsync_510_LineGraphSegment(gcGraph, graphDrawingData, 0, serieSize, rgbFg, rgbBgDark, rgbBgBright,
                    noneMarkerLineAlpha, noneMarkerFillingAlpha, graphValueOffset);

            // draw the x-marker
            drawAsync_510_LineGraphSegment(gcGraph, graphDrawingData, synchMarkerStartIndex,
                    synchMarkerEndIndex + 0, rgbFg, rgbBgDark, rgbBgBright, _chart.graphTransparencyLine,
                    _chart.graphTransparencyFilling, graphValueOffset);
        }
    }

    /**
     * First draw the graph into a path, the path is then drawn on the device with a transformation.
     * 
     * @param gc
     * @param graphDrawingData
     * @param startIndex
     * @param endIndex
     * @param rgbFg
     * @param rgbBgDark
     * @param rgbBgBright
     * @param graphLineAlpha
     * @param graphFillingAlpha
     * @param graphValueOffset
     * @param isFillGraph
     * @param isSegmented
     *            When <code>true</code> the whole graph is painted with several segments, otherwise
     *            the whole graph is painted once.
     */
    private void drawAsync_510_LineGraphSegment(final GC gc, final GraphDrawingData graphDrawingData,
            final int startIndex, final int endIndex, final RGB rgbFg, final RGB rgbBgDark, final RGB rgbBgBright,
            final int graphLineAlpha, final int graphFillingAlpha, final double graphValueOffset) {

        final ChartDataXSerie xData = graphDrawingData.getXData();
        final ChartDataYSerie yData = graphDrawingData.getYData();

        final float[][] yHighValues = yData.getHighValuesFloat();

        final double[] xValues = xData.getHighValuesDouble()[0];
        final float yValues[] = yHighValues[0];

        final int graphFillMethod = yData.getGraphFillMethod();
        final boolean[] noFill = xData.getNoLine();
        final boolean[] lineGaps = yData.getLineGaps();

        // check array bounds
        final int xValueLength = xValues.length;
        if (startIndex >= xValueLength) {
            return;
        }
        final int yValueLength = yValues.length;

        /*
         * 2nd path is currently used to draw the SRTM altitude line
         */
        final boolean isPath2 = yHighValues.length > 1;
        float[] yValues2 = null;
        if (isPath2) {
            yValues2 = yHighValues[1];
        }

        // get top/bottom border values of the graph
        final float graphYBorderTop = graphDrawingData.getGraphYTop();
        final float graphYBorderBottom = graphDrawingData.getGraphYBottom();
        final int devYTop = graphDrawingData.getDevYTop();
        final int devChartHeight = getDevVisibleGraphHeight();

        final double scaleX = graphDrawingData.getScaleX();
        final double scaleY = graphDrawingData.getScaleY();

        // this feature also needs that the y-axis is scaled accordingly -> this not yet implemted
        //
        //      if (_canChartBeOverlapped && _isChartOverlapped) {
        //
        //         // reduce scale for overlapped graphs
        //
        //         if (!isTopGraph) {
        //
        //            scaleY *= 0.6;
        //         }
        //      }

        final Display display = getDisplay();

        // path is scaled in device pixel
        final Path path = new Path(display);
        final Path path2 = isPath2 ? new Path(display) : null;

        final boolean isShowSkippedValues = _chartDrawingData.chartDataModel.isNoLinesValuesDisplayed();
        final ArrayList<Point> skippedValues = new ArrayList<Point>();

        final int devGraphHeight = graphDrawingData.devGraphHeight;
        final float devYGraphTop = (float) (scaleY * graphYBorderTop);
        final float devYGraphBottom = (float) (scaleY * graphYBorderBottom);

        final RectangleLong[] lineFocusRectangles = _lineFocusRectangles.get(_lineFocusRectangles.size() - 1);
        final PointLong[] lineDevPositions = _lineDevPositions.get(_lineDevPositions.size() - 1);
        RectangleLong prevLineRect = null;

        /*
         * 
         */
        final float devY0Inverse = devGraphHeight + devYGraphBottom;

        /*
         * x-axis line with y==0
         */
        float graphY_XAxisLine = 0;

        if (graphFillMethod == ChartDataYSerie.FILL_METHOD_FILL_BOTTOM
                || graphFillMethod == ChartDataYSerie.FILL_METHOD_CUSTOM
        //            || graphFillMethod == ChartDataYSerie.FILL_METHOD_NO
        ) {

            graphY_XAxisLine = graphYBorderBottom > 0 //
                    ? graphYBorderBottom
                    : graphYBorderTop < 0 //
                            ? graphYBorderTop
                            : graphYBorderBottom;

        } else if (graphFillMethod == ChartDataYSerie.FILL_METHOD_FILL_ZERO) {

            graphY_XAxisLine = graphYBorderBottom > 0 ? graphYBorderBottom //
                    : graphYBorderTop < 0 ? graphYBorderTop //
                            : 0;
        }
        final float devY_XAxisLine = (float) (scaleY * graphY_XAxisLine);

        final double graphXStart = xValues[startIndex] - graphValueOffset;
        final float graphYStart = yValues[startIndex];

        float graphY1Prev = graphYStart;

        double devXPrev = (scaleX * graphXStart);
        float devY1Prev = (float) (scaleY * graphY1Prev);

        double devXPrevNoLine = 0;
        boolean isNoLine = false;
        boolean isLineGap = false;

        final Rectangle chartRectangle = gc.getClipping();
        final int devXVisibleWidth = chartRectangle.width;

        boolean isDrawFirstPoint = true;

        final int lastIndex = endIndex - 1;

        int valueIndexFirstPoint = startIndex;
        int valueIndexLastPoint = startIndex;
        int prevValueIndex = startIndex;

        /*
         * set the hovered index only ONCE because when autoscrolling is done to the right side this
         * can cause that the last value is used for the hovered index instead of the previous
         * before the last
         */
        boolean isSetHoveredIndex = false;

        final long[] devXPositions = new long[endIndex];
        final float devY0 = devY0Inverse - devY_XAxisLine;

        /*
         * draw the lines into the paths
         */
        for (int valueIndex = startIndex; valueIndex < endIndex; valueIndex++) {

            // check array bounds
            if (valueIndex >= yValueLength) {
                break;
            }

            final double graphX = xValues[valueIndex] - graphValueOffset;
            final double devX = graphX * scaleX;
            final float devXf = (float) devX;

            final float graphY1 = yValues[valueIndex];
            final float devY1 = (float) (graphY1 * scaleY);

            float graphY2 = 0;
            float devY2 = 0;

            if (isPath2) {
                graphY2 = yValues2[valueIndex];
                devY2 = (float) (graphY2 * scaleY);
            }

            devXPositions[valueIndex] = (long) devX;

            // check if position is horizontal visible
            if (devX < 0) {

                // keep current position which is used as the painting starting point

                graphY1Prev = graphY1;

                devXPrev = devX;
                devY1Prev = devY1;

                valueIndexFirstPoint = valueIndex;
                prevValueIndex = valueIndex;

                continue;
            }

            /*
             * draw first point
             */
            if (isDrawFirstPoint) {

                // move to the first point

                isDrawFirstPoint = false;

                // set first point before devX==0 that the first line is not visible but correctly painted
                final double devXFirstPoint = devXPrev;
                float devXFirstPointF = (float) devXPrev;

                if (devXFirstPointF <= 0.0f) {
                    /*
                     * Hide the first line from the bottom to the first value point by setting the
                     * position into the hidden area.
                     */
                    devXFirstPointF -= 1f;
                }

                float devYStart = 0;

                if (graphFillMethod == ChartDataYSerie.FILL_METHOD_FILL_BOTTOM
                        || graphFillMethod == ChartDataYSerie.FILL_METHOD_CUSTOM
                        || graphFillMethod == ChartDataYSerie.FILL_METHOD_NO) {

                    // start from the bottom of the graph

                    devYStart = devGraphHeight;

                } else if (graphFillMethod == ChartDataYSerie.FILL_METHOD_FILL_ZERO) {

                    // start from the x-axis, y=0

                    devYStart = devY0;
                }

                final float devY = devY0Inverse - devY1Prev;

                path.moveTo((int) devXFirstPointF, (int) devYStart);
                path.lineTo((int) devXFirstPointF, (int) devY);

                if (isPath2) {
                    path2.moveTo(devXFirstPointF, devY0Inverse - devY2);
                    path2.lineTo(devXFirstPointF, devY0Inverse - devY2);
                }

                /*
                 * set line hover positions for the first point
                 */
                final long devXRect = (long) devXFirstPoint;

                final RectangleLong currentRect = new RectangleLong(devXRect, 0, 1, devChartHeight);
                final PointLong currentPoint = new PointLong(devXRect, (long) (devYTop + devY));

                lineDevPositions[valueIndexFirstPoint] = currentPoint;
                lineFocusRectangles[valueIndexFirstPoint] = currentRect;

                prevLineRect = currentRect;
            }

            /*
             * draw line to current point
             */
            if ((long) devX != (long) devXPrev || graphY1 == 0 || (isPath2 && graphY2 == 0)) {

                // optimization: draw only ONE line for the current x-position
                // but draw to the 0 line otherwise it's possible that a triangle is painted

                float devY = 0;

                if (lineGaps != null && lineGaps[valueIndex]) {

                    isLineGap = true;

                    // keep correct position that the hovered line dev position is painted at the correct position
                    devY = devY0Inverse - devY1;

                } else if (noFill != null && noFill[valueIndex]) {

                    /*
                     * draw NO line, but draw a line at the bottom or the x-axis with y=0
                     */

                    if (graphFillMethod == ChartDataYSerie.FILL_METHOD_FILL_BOTTOM
                            || graphFillMethod == ChartDataYSerie.FILL_METHOD_CUSTOM
                            || graphFillMethod == ChartDataYSerie.FILL_METHOD_NO) {

                        // start from the bottom of the graph

                        devY = devGraphHeight;

                    } else if (graphFillMethod == ChartDataYSerie.FILL_METHOD_FILL_ZERO) {

                        // start from the x-axis, y=0

                        devY = devY0;
                    }

                    /*
                     * Don't draw "fill" line when graph is not filled
                     */
                    if (graphFillMethod != ChartDataYSerie.FILL_METHOD_NO) {

                        path.lineTo((int) devXPrev, (int) devY);
                        path.lineTo((int) devXf, (int) devY);
                    }

                    /*
                     * keep positions, because skipped values will be painted as a dot outside of
                     * the path, but don't draw on the graph bottom or x-axis
                     */
                    if (isShowSkippedValues) {

                        final int devYSkipped = (int) (devY0Inverse - devY1);
                        if (devYSkipped != devY0 && graphY1 != 0) {
                            skippedValues.add(new Point((int) devX, devYSkipped));
                        }
                    }

                    isNoLine = true;
                    devXPrevNoLine = devX;

                    // keep correct position that the hovered line dev position is painted at the correct position
                    devY = devY0Inverse - devY1;

                } else {

                    /*
                     * draw line to the current point
                     */

                    if (isLineGap) {

                        isLineGap = false;

                        devY = devY0Inverse - devY1;

                        path.moveTo(devXf, devY);

                    } else {

                        // check if a NO line was painted
                        if (isNoLine) {

                            isNoLine = false;

                            path.lineTo((int) devXPrevNoLine, (int) (devY0Inverse - devY1Prev));
                        }

                        devY = devY0Inverse - devY1;

                        path.lineTo(devXf, devY);

                        if (isPath2) {
                            path2.lineTo(devXf, devY0Inverse - devY2);
                        }
                    }
                }

                /*
                 * set line hover positions
                 */
                final double devXDiff = (devX - devXPrev) / 2;
                final double devXDiffWidth = devXDiff < 1 ? 1 : (devXDiff + 0.5);
                final double devXRect = devX - devXDiffWidth;

                // add the right part of the rectangle width into the previous rectangle
                prevLineRect.width += devXDiffWidth + 1;

                // check if hovered line is hit, this check is an inline for .contains(...)
                if (isSetHoveredIndex == false && prevLineRect.contains(_devXMouseMove, _devYMouseMove)) {
                    _hoveredValuePointIndex = prevValueIndex;
                    isSetHoveredIndex = true;
                }

                final RectangleLong currentRect = new RectangleLong((long) devXRect, 0, (long) (devXDiffWidth + 1),
                        devChartHeight);
                final PointLong currentPoint = new PointLong((long) devX, (long) (devYTop + devY));

                lineDevPositions[valueIndex] = currentPoint;
                lineFocusRectangles[valueIndex] = currentRect;

                prevLineRect = currentRect;
            }

            /*
             * draw last point
             */
            if (valueIndex == lastIndex || //

            // check if last visible position + 1 is reached
                    devX > devXVisibleWidth) {

                /*
                 * this is the last point for a filled graph
                 */

                final float devY = devY0Inverse - devY1;

                path.lineTo((int) devXf, (int) devY);

                // move path to the final point
                if (graphFillMethod == ChartDataYSerie.FILL_METHOD_FILL_BOTTOM
                        || graphFillMethod == ChartDataYSerie.FILL_METHOD_CUSTOM
                        || graphFillMethod == ChartDataYSerie.FILL_METHOD_NO) {

                    // draw line to the bottom of the graph

                    path.lineTo((int) devXf, devGraphHeight);

                } else if (graphFillMethod == ChartDataYSerie.FILL_METHOD_FILL_ZERO) {

                    // draw line to the x-axis, y=0

                    path.lineTo((int) devXf, (int) devY0);
                }

                // moveTo() is necessary that the graph is filled correctly (to prevent a triangle filled shape)
                // finalize previous subpath
                path.moveTo((int) devXf, 0);

                if (isPath2) {
                    path2.lineTo(devXf, devY0Inverse - devY2);
                    path2.moveTo(devXf, 0);
                }

                valueIndexLastPoint = valueIndex;

                /*
                 * set line rectangle
                 */
                final double devXDiff = (devX - devXPrev) / 2;
                final long devXDiffWidth = devXDiff < 1 ? 1 : (int) (devXDiff + 0.5);
                final long devXRect = (long) (devX - devXDiffWidth);

                // set right part of the rectangle width into the previous rectangle
                prevLineRect.width += devXDiffWidth;

                // check if hovered line is hit, this check is an inline for .contains(...)
                if (isSetHoveredIndex == false && prevLineRect.contains(_devXMouseMove, _devYMouseMove)) {
                    _hoveredValuePointIndex = valueIndex;
                    isSetHoveredIndex = true;
                }

                final RectangleLong lastRect = new RectangleLong(devXRect, 0, devXDiffWidth + 1, devChartHeight);
                final PointLong lastPoint = new PointLong((long) devX, devYTop + (long) devY);

                lineDevPositions[valueIndex] = lastPoint;
                lineFocusRectangles[valueIndex] = lastRect;

                if (isSetHoveredIndex == false && lastRect.contains(_devXMouseMove, _devYMouseMove)) {
                    _hoveredValuePointIndex = valueIndex;
                }

                break;
            }

            devXPrev = devX;
            devY1Prev = devY1;
            prevValueIndex = valueIndex;
        }

        final Color colorLine = new Color(display, rgbFg);
        final Color colorBgDark = new Color(display, rgbBgDark);
        final Color colorBgBright = new Color(display, rgbBgBright);

        final double graphWidth = xValues[Math.min(xValueLength - 1, endIndex)] - graphValueOffset;

        /**
         * force a max width because the fill will not be drawn on Linux
         */
        final int devGraphWidth = Math.min(0x7fff, (int) (graphWidth * scaleX));

        gc.setAntialias(_chart.graphAntialiasing);
        gc.setAlpha(graphFillingAlpha);
        gc.setClipping(path);

        /*
         * fill the graph
         */
        if (graphFillMethod == ChartDataYSerie.FILL_METHOD_FILL_BOTTOM) {

            /*
             * adjust the fill gradient in the height, otherwise the fill is not in the whole
             * rectangle
             */

            gc.setForeground(colorBgDark);
            gc.setBackground(colorBgBright);

            gc.fillGradientRectangle(//
                    0, devGraphHeight, devGraphWidth, -devGraphHeight, true);

        } else if (graphFillMethod == ChartDataYSerie.FILL_METHOD_FILL_ZERO) {

            /*
             * fill above 0 line
             */

            gc.setForeground(colorBgDark);
            gc.setBackground(colorBgBright);

            gc.fillGradientRectangle(//
                    0, (int) devY0, devGraphWidth, -(int) (devYGraphTop - devY_XAxisLine), true);

            /*
             * fill below 0 line
             */
            gc.setForeground(colorBgBright);
            gc.setBackground(colorBgDark);

            gc.fillGradientRectangle(//
                    0, devGraphHeight, // start from the graph bottom
                    devGraphWidth, -(int) Math.min(devGraphHeight, devGraphHeight - devY0Inverse), true);

        } else if (graphFillMethod == ChartDataYSerie.FILL_METHOD_CUSTOM) {

            final IFillPainter customFillPainter = yData.getCustomFillPainter();

            if (customFillPainter != null) {

                gc.setForeground(colorBgDark);
                gc.setBackground(colorBgBright);

                customFillPainter.draw(//
                        gc, graphDrawingData, _chart, devXPositions, valueIndexFirstPoint, valueIndexLastPoint);
            }
        }

        // reset clipping that the line is drawn everywere
        gc.setClipping((Rectangle) null);

        gc.setBackground(colorLine);

        /*
         * paint skipped values
         */
        if (isShowSkippedValues && skippedValues.size() > 0) {
            for (final Point skippedPoint : skippedValues) {
                gc.fillRectangle(skippedPoint.x, skippedPoint.y, 2, 2);
            }
        }

        /*
         * draw line along the path
         */
        gc.setAlpha(graphLineAlpha);

        // set line style
        gc.setLineStyle(SWT.LINE_SOLID);

        // draw the line of the graph
        gc.setForeground(colorLine);

        gc.drawPath(path);

        // dispose resources
        colorLine.dispose();
        colorBgDark.dispose();
        colorBgBright.dispose();

        path.dispose();

        /*
         * draw path2 above the other graph, this is currently used to draw the srtm graph
         */
        if (path2 != null) {

            gc.setForeground(Display.getCurrent().getSystemColor(SWT.COLOR_RED));
            gc.drawPath(path2);

            path2.dispose();
        }

        gc.setAlpha(0xFF);
        gc.setAntialias(SWT.OFF);
    }

    private void drawAsync_520_RangeMarker(final GC gc, final GraphDrawingData drawingData) {

        final ChartDataXSerie xData = drawingData.getXData();
        final ChartDataYSerie yData = drawingData.getYData();

        final int[] startIndex = xData.getRangeMarkerStartIndex();
        final int[] endIndex = xData.getRangeMarkerEndIndex();

        if (startIndex == null) {
            return;
        }

        final double scaleX = drawingData.getScaleX();

        final RGB rgbFg = yData.getRgbLine()[0];
        final RGB rgbBg1 = yData.getRgbDark()[0];
        final RGB rgbBg2 = yData.getRgbBright()[0];

        // get the horizontal offset for the graph
        double graphValueOffset;
        if (_chartComponents._synchConfigSrc == null) {
            // a zoom marker is not set, draw it normally
            graphValueOffset = (Math.max(0, _xxDevViewPortLeftBorder) / scaleX);
        } else {
            // adjust the start position to the zoom marker position
            graphValueOffset = (_xxDevViewPortLeftBorder / scaleX);
        }

        int graphFillingAlpha = (int) (_chart.graphTransparencyFilling * 0.5);
        int graphLineAlpha = (int) (_chart.graphTransparencyFilling * 0.5);

        graphFillingAlpha = graphFillingAlpha < 0 ? 0 : graphFillingAlpha > 255 ? 255 : graphFillingAlpha;
        graphLineAlpha = graphLineAlpha < 0 ? 0 : graphLineAlpha > 255 ? 255 : graphLineAlpha;

        int runningIndex = 0;
        for (final int markerStartIndex : startIndex) {

            // draw range marker
            drawAsync_510_LineGraphSegment(gc, drawingData, markerStartIndex, endIndex[runningIndex] + 1, rgbFg,
                    rgbBg1, rgbBg2, graphLineAlpha, graphFillingAlpha, graphValueOffset);

            runningIndex++;
        }
    }

    /**
     * Draws a bar graph, this requires that drawingData.getChartData2ndValues does not return null,
     * if null is returned, a line graph will be drawn instead
     * 
     * @param gcGraph
     * @param drawingData
     */
    private void drawAsync_530_BarGraph(final GC gcGraph, final GraphDrawingData drawingData) {

        // get the chart data
        final ChartDataXSerie xData = drawingData.getXData();
        final ChartDataYSerie yData = drawingData.getYData();
        final int[][] colorsIndex = yData.getColorsIndex();
        final int graphFillMethod = yData.getGraphFillMethod();

        gcGraph.setLineStyle(SWT.LINE_SOLID);

        // get the colors
        final RGB[] rgbLine = yData.getRgbLine();
        final RGB[] rgbDark = yData.getRgbDark();
        final RGB[] rgbBright = yData.getRgbBright();

        // get the chart values
        final double scaleX = drawingData.getScaleX();
        final double scaleY = drawingData.getScaleY();
        final float graphYBorderBottom = drawingData.getGraphYBottom();
        final boolean isBottomTop = yData.isYAxisDirection();

        // get the horizontal offset for the graph
        float graphValueOffset;
        if (_chartComponents._synchConfigSrc == null) {
            // a synch marker is not set, draw it normally
            graphValueOffset = (float) (Math.max(0, _xxDevViewPortLeftBorder) / scaleX);
        } else {
            // adjust the start position to the synch marker position
            graphValueOffset = (float) (_xxDevViewPortLeftBorder / scaleX);
        }

        final int devGraphCanvasHeight = drawingData.devGraphHeight;

        /*
         * Get the top/bottom for the graph, a chart can contain multiple canvas. Canvas is the area
         * where the graph is painted.
         */
        final int devYCanvasBottom = devGraphCanvasHeight;
        final int devYCanvasTop = 0;

        final int devYChartBottom = drawingData.getDevYBottom();
        final int devYChartTop = devYChartBottom - devGraphCanvasHeight;

        final double[] xValues = xData.getHighValuesDouble()[0];
        final float yHighSeries[][] = yData.getHighValuesFloat();
        final float yLowSeries[][] = yData.getLowValuesFloat();

        final int serieLength = yHighSeries.length;
        final int valueLength = xValues.length;

        // keep the bar rectangles for all canvas
        final Rectangle[][] barRecangles = new Rectangle[serieLength][valueLength];
        final Rectangle[][] barFocusRecangles = new Rectangle[serieLength][valueLength];
        drawingData.setBarRectangles(barRecangles);
        drawingData.setBarFocusRectangles(barFocusRecangles);

        // keep the height for stacked bar charts
        final int devHeightSummary[] = new int[valueLength];

        final int devBarWidthOriginal = drawingData.getBarRectangleWidth();
        final int devBarWidth = Math.max(1, devBarWidthOriginal);
        final int devBarWidth2 = devBarWidth / 2;

        final int serieLayout = yData.getChartLayout();
        final int devBarRectangleStartXPos = drawingData.getDevBarRectangleXPos();
        final int barPosition = drawingData.getBarPosition();

        // loop: all data series
        for (int serieIndex = 0; serieIndex < serieLength; serieIndex++) {

            final float yHighValues[] = yHighSeries[serieIndex];
            float yLowValues[] = null;
            if (yLowSeries != null) {
                yLowValues = yLowSeries[serieIndex];
            }

            int devBarXPos = devBarRectangleStartXPos;
            int devBarWidthPositioned = devBarWidth;

            // reposition the rectangle when the bars are beside each other
            if (serieLayout == ChartDataYSerie.BAR_LAYOUT_BESIDE) {
                devBarXPos += serieIndex * devBarWidth;
                devBarWidthPositioned = devBarWidth - 1;
            }

            int devXPosNextBar = 0;

            // loop: all values in the current serie
            for (int valueIndex = 0; valueIndex < valueLength; valueIndex++) {

                // get the x position
                int devXPos = (int) ((xValues[valueIndex] - graphValueOffset) * scaleX) + devBarXPos;

                // center the bar
                if (devBarWidth > 1 && barPosition == GraphDrawingData.BAR_POS_CENTER) {
                    devXPos -= devBarWidth2;
                }

                float valueYLow = 0;

                if (graphFillMethod == ChartDataYSerie.BAR_DRAW_METHOD_BOTTOM) {

                    // draw at the bottom
                    valueYLow = graphYBorderBottom;

                } else {

                    if (yLowValues == null) {
                        valueYLow = (float) yData.getVisibleMinValue();
                    } else {
                        // check array bounds
                        if (valueIndex >= yLowValues.length) {
                            break;
                        }
                        valueYLow = yLowValues[valueIndex];
                    }
                }

                // check array bounds
                if (valueIndex >= yHighValues.length) {
                    break;
                }
                final float valueYHigh = yHighValues[valueIndex];

                final float barHeight = (Math.max(valueYHigh, valueYLow) - Math.min(valueYHigh, valueYLow));
                if (barHeight == 0) {
                    continue;
                }

                final int devBarHeight = (int) (barHeight * scaleY);

                // get the old y position for stacked bars
                int devYPreviousHeight = 0;
                if (serieLayout == ChartDataYSerie.BAR_LAYOUT_STACKED) {
                    devYPreviousHeight = devHeightSummary[valueIndex];
                }

                /*
                 * get y positions
                 */
                int devYPosChart;
                int devYPosCanvas;
                if (isBottomTop) {

                    final int devYBar = (int) ((valueYHigh - graphYBorderBottom) * scaleY) + devYPreviousHeight;

                    devYPosChart = devYChartBottom - devYBar;
                    devYPosCanvas = devYCanvasBottom - devYBar;

                } else {

                    final int devYBar = (int) ((valueYLow - graphYBorderBottom) * scaleY) + devYPreviousHeight;

                    devYPosChart = devYChartTop + devYBar;
                    devYPosCanvas = devYCanvasTop + devYBar;
                }

                int devXPosShape = devXPos;
                int devShapeBarWidth = devBarWidthPositioned;

                /*
                 * make sure the bars do not overlap
                 */
                if (serieLayout != ChartDataYSerie.BAR_LAYOUT_SINGLE_SERIE) {
                    if (devXPosNextBar > 0) {
                        if (devXPos < devXPosNextBar) {

                            // bars do overlap

                            final int devDiff = devXPosNextBar - devXPos;

                            devXPosShape = devXPos + devDiff;
                            devShapeBarWidth = devBarWidthPositioned - devDiff;
                        }
                    }
                }
                devXPosNextBar = devXPos + devBarWidthPositioned;

                /*
                 * get colors
                 */
                final int colorIndex = colorsIndex[serieIndex][valueIndex];
                final RGB rgbBrightDef = rgbBright[colorIndex];
                final RGB rgbDarkDef = rgbDark[colorIndex];
                final RGB rgbLineDef = rgbLine[colorIndex];

                final Color colorBright = getColor(rgbBrightDef);
                final Color colorDark = getColor(rgbDarkDef);
                final Color colorLine = getColor(rgbLineDef);

                gcGraph.setBackground(colorDark);

                // ensure the bar is visible
                if (devShapeBarWidth < 1) {
                    devShapeBarWidth = 1;
                }

                /*
                 * draw bar
                 */
                final Rectangle barShapeCanvas = new Rectangle(devXPosShape, devYPosCanvas, devShapeBarWidth,
                        devBarHeight);

                if (devBarWidthOriginal > 0) {

                    gcGraph.setForeground(colorBright);
                    gcGraph.fillGradientRectangle(barShapeCanvas.x, barShapeCanvas.y, barShapeCanvas.width,
                            barShapeCanvas.height, false);

                    gcGraph.setForeground(colorLine);
                    gcGraph.drawRectangle(barShapeCanvas);

                } else {

                    gcGraph.setForeground(colorLine);
                    gcGraph.drawLine(barShapeCanvas.x, barShapeCanvas.y, barShapeCanvas.x,
                            (barShapeCanvas.y + barShapeCanvas.height));
                }

                barRecangles[serieIndex][valueIndex] = new Rectangle( //
                        devXPosShape, devYPosChart, devShapeBarWidth, devBarHeight);

                barFocusRecangles[serieIndex][valueIndex] = new Rectangle(//
                        devXPosShape - 2, (devYPosChart - 2), devShapeBarWidth + 4, (devBarHeight + 7));

                // keep the height for the bar
                devHeightSummary[valueIndex] += devBarHeight;
            }
        }

        // reset clipping
        gcGraph.setClipping((Rectangle) null);
    }

    /**
     * Draws a bar graph, this requires that drawingData.getChartData2ndValues does not return
     * <code>null</code>, if <code>null</code> is returned, a line graph will be drawn instead.
     * 
     * @param gc
     * @param drawingData
     */
    private void drawAsync_540_LineWithBarGraph(final GC gc, final GraphDrawingData drawingData) {

        // get the chart data
        final ChartDataXSerie xData = drawingData.getXData();
        final ChartDataYSerie yData = drawingData.getYData();
        final int[][] colorsIndex = yData.getColorsIndex();

        gc.setLineStyle(SWT.LINE_SOLID);

        // get the colors
        final RGB[] rgbLine = yData.getRgbLine();
        final RGB[] rgbDark = yData.getRgbDark();
        final RGB[] rgbBright = yData.getRgbBright();

        // get the chart values
        final double scaleX = drawingData.getScaleX();
        final double scaleY = drawingData.getScaleY();
        final float graphYBottom = drawingData.getGraphYBottom();
        final boolean axisDirection = yData.isYAxisDirection();
        //      final int barPosition = drawingData.getBarPosition();

        // get the horizontal offset for the graph
        float graphValueOffset;
        if (_chartComponents._synchConfigSrc == null) {
            // a zoom marker is not set, draw it normally
            graphValueOffset = (float) (Math.max(0, _xxDevViewPortLeftBorder) / scaleX);
        } else {
            // adjust the start position to the zoom marker position
            graphValueOffset = (float) (_xxDevViewPortLeftBorder / scaleX);
        }

        // get the top/bottom of the graph
        final int devYTop = 0;
        final int devYBottom = drawingData.devGraphHeight;

        // virtual 0 line for the y-axis of the chart in dev units
        //      final float devChartY0Line = (float) devYBottom + (scaleY * graphYBottom);

        gc.setClipping(0, devYTop, gc.getClipping().width, devYBottom - devYTop);

        final double[] xValues = xData.getHighValuesDouble()[0];
        final float yHighSeries[][] = yData.getHighValuesFloat();
        //      final int yLowSeries[][] = yData.getLowValues();

        final int serieLength = yHighSeries.length;
        final int valueLength = xValues.length;

        final int devBarWidthComputed = drawingData.getBarRectangleWidth();
        final int devBarWidth = Math.max(1, devBarWidthComputed);

        final int devBarXPos = drawingData.getDevBarRectangleXPos();

        // loop: all data series
        for (int serieIndex = 0; serieIndex < serieLength; serieIndex++) {

            final float yHighValues[] = yHighSeries[serieIndex];
            //         int yLowValues[] = null;
            //         if (yLowSeries != null) {
            //            yLowValues = yLowSeries[serieIndex];
            //         }

            // loop: all values in the current serie
            for (int valueIndex = 0; valueIndex < valueLength; valueIndex++) {

                // get the x position
                final int devXPos = (int) ((xValues[valueIndex] - graphValueOffset) * scaleX) + devBarXPos;

                //            final int devBarWidthSelected = devBarWidth;
                //            final int devBarWidth2 = devBarWidthSelected / 2;

                //            int devXPosSelected = devXPos;
                //
                //            // center the bar
                //            if (devBarWidthSelected > 1 && barPosition == GraphDrawingData.BAR_POS_CENTER) {
                //               devXPosSelected -= devBarWidth2;
                //            }

                // get the bar height
                final float graphYLow = graphYBottom;
                final float graphYHigh = yHighValues[valueIndex];

                final float graphBarHeight = Math.max(graphYHigh, graphYLow) - Math.min(graphYHigh, graphYLow);

                // skip bars which have no height
                if (graphBarHeight == 0) {
                    continue;
                }

                final int devBarHeight = (int) (graphBarHeight * scaleY);

                // get the y position
                int devYPos;
                if (axisDirection) {
                    devYPos = devYBottom - ((int) ((graphYHigh - graphYBottom) * scaleY));
                } else {
                    devYPos = devYTop + ((int) ((graphYLow - graphYBottom) * scaleY));
                }

                final Rectangle barShape = new Rectangle(devXPos, devYPos, devBarWidth, devBarHeight);

                final int colorSerieIndex = colorsIndex.length >= serieIndex ? colorsIndex.length - 1 : serieIndex;
                final int colorIndex = colorsIndex[colorSerieIndex][valueIndex];

                final RGB rgbBrightDef = rgbBright[colorIndex];
                final RGB rgbDarkDef = rgbDark[colorIndex];
                final RGB rgbLineDef = rgbLine[colorIndex];

                final Color colorBright = getColor(rgbBrightDef);
                final Color colorDark = getColor(rgbDarkDef);
                final Color colorLine = getColor(rgbLineDef);

                gc.setBackground(colorDark);

                /*
                 * draw bar
                 */
                if (devBarWidthComputed > 0) {

                    gc.setForeground(colorBright);
                    gc.fillGradientRectangle(barShape.x, barShape.y, barShape.width, barShape.height, false);

                    gc.setForeground(colorLine);
                    gc.drawRectangle(barShape);

                } else {

                    gc.setForeground(colorLine);
                    gc.drawLine(barShape.x, barShape.y, barShape.x, (barShape.y + barShape.height));
                }
            }
        }

        // reset clipping
        gc.setClipping((Rectangle) null);
    }

    /**
     * Draws a bar graph, this requires that drawingData.getChartData2ndValues does not return null,
     * if null is returned, a line graph will be drawn instead
     * <p>
     * <b> Zooming the chart is not yet supported for this charttype because logarithmic scaling is
     * very complex for a zoomed chart </b>
     * 
     * @param gc
     * @param drawingData
     */
    private void drawAsync_550_XYScatter(final GC gc, final GraphDrawingData drawingData) {

        // get chart data
        final ChartDataXSerie xData = drawingData.getXData();
        final ChartDataYSerie yData = drawingData.getYData();
        final double scaleX = drawingData.getScaleX();
        final double scaleY = drawingData.getScaleY();
        final float graphYBottom = drawingData.getGraphYBottom();
        final double devGraphWidth = drawingData.devVirtualGraphWidth;

        final double xAxisStartValue = xData.getXAxisMinValueForced();
        final double scalingFactor = xData.getScalingFactor();
        final double scalingMaxValue = xData.getScalingMaxValue();
        final boolean isExtendedScaling = scalingFactor != 1.0;
        final double scaleXExtended = ((devGraphWidth - 1) / Math.pow(scalingMaxValue, scalingFactor));

        // get colors
        final RGB[] rgbLine = yData.getRgbLine();

        // get the top/bottom of the graph
        final int devYTop = 0;
        final int devYBottom = drawingData.devGraphHeight;

        //      gc.setAntialias(SWT.ON);
        gc.setLineStyle(SWT.LINE_SOLID);
        gc.setClipping(0, devYTop, gc.getClipping().width, devYBottom - devYTop);

        final double[][] xSeries = xData.getHighValuesDouble();
        final float[][] ySeries = yData.getHighValuesFloat();
        final int size = 6;
        final int size2 = size / 2;

        for (int serieIndex = 0; serieIndex < xSeries.length; serieIndex++) {

            final double[] xValues = xSeries[serieIndex];
            final float yHighValues[] = ySeries[serieIndex];

            gc.setBackground(getColor(rgbLine[serieIndex]));

            // loop: all values in the current serie
            for (int valueIndex = 0; valueIndex < xValues.length; valueIndex++) {

                // check array bounds
                if (valueIndex >= yHighValues.length) {
                    break;
                }

                final double xValue = xValues[valueIndex];
                final float yValue = yHighValues[valueIndex];

                // get the x/y positions
                int devX;
                if (isExtendedScaling) {
                    devX = (int) ((Math.pow(xValue, scalingFactor)) * scaleXExtended);
                } else {
                    devX = (int) (scaleX * (xValue - xAxisStartValue));
                }

                final int devY = devYBottom - ((int) (scaleY * (yValue - graphYBottom)));

                // draw shape
                //            gc.fillRectangle(devXPos - size2, devYPos - size2, size, size);
                gc.fillOval(devX - size2, devY - size2, size, size);
            }
        }

        // reset clipping/antialias
        gc.setClipping((Rectangle) null);
        gc.setAntialias(SWT.OFF);
    }

    /**
     * Is used for drawing gear values.
     * 
     * @param gc
     * @param graphDrawingData
     * @param isTopGraph
     */
    private void drawAsync_560_HorizontalBar(final GC gc, final GraphDrawingData graphDrawingData,
            final boolean isTopGraph) {

        final ChartDataXSerie xData = graphDrawingData.getXData();
        final ChartDataYSerie yData = graphDrawingData.getYData();

        final float[][] yHighValues = yData.getHighValuesFloat();

        final double[] xValues = xData.getHighValuesDouble()[0];
        final float yValues[] = yHighValues[0];
        final float yValues2[] = yHighValues[1];

        final int serieSize = xValues.length;

        // create line hovered positions
        _lineFocusRectangles.add(new RectangleLong[serieSize]);
        _lineDevPositions.add(new PointLong[serieSize]);
        _isHoveredLineVisible = true;

        // check array bounds
        //      final int xValueLength = xValues.length;
        final int yValueLength = yValues.length;

        // get top/bottom border values of the graph
        final float graphYBorderBottom = graphDrawingData.getGraphYBottom();
        final int devYTop = graphDrawingData.getDevYTop();
        final int devChartHeight = getDevVisibleGraphHeight();

        final double scaleX = graphDrawingData.getScaleX();
        final double scaleY = graphDrawingData.getScaleY();

        // get the horizontal offset for the graph
        float graphValueOffset;
        if (_chartComponents._synchConfigSrc == null) {
            // a zoom marker is not set, draw it normally
            graphValueOffset = (float) (Math.max(0, _xxDevViewPortLeftBorder) / scaleX);
        } else {
            // adjust the start position to the zoom marker position
            graphValueOffset = (float) (_xxDevViewPortLeftBorder / scaleX);
        }

        final Display display = getDisplay();

        final int devGraphHeight = graphDrawingData.devGraphHeight;
        final float devYGraphBottom = (float) (scaleY * graphYBorderBottom);

        final RectangleLong[] lineFocusRectangles = _lineFocusRectangles.get(_lineFocusRectangles.size() - 1);
        final PointLong[] lineDevPositions = _lineDevPositions.get(_lineDevPositions.size() - 1);
        RectangleLong prevLineRect = null;

        final float devY0Inverse = devGraphHeight + devYGraphBottom;

        final double graphXStart = xValues[0] - graphValueOffset;
        final float graphYStart = yValues[0];

        float graphYPrev = graphYStart;

        double devXPrev = (scaleX * graphXStart);
        float devYPrev = (float) (scaleY * graphYPrev);

        final int devXOverlap = (int) (0.5 * scaleX) + 0;
        final int devXOverlap2 = devXOverlap * 2 + 0;

        final Rectangle chartRectangle = gc.getClipping();
        final int devXVisibleWidth = chartRectangle.width;

        boolean isDrawFirstPoint = true;

        final int valueSize = xValues.length;
        final int lastIndex = valueSize - 1;

        int valueIndexFirstPoint = 0;
        int prevValueIndex = 0;

        /*
         * set the hovered index only ONCE because when autoscrolling is done to the right side this
         * can cause that the last value is used for the hovered index instead of the previous
         * before the last
         */
        boolean isSetHoveredIndex = false;

        int barXStart = 0;
        int barXEnd = 0;
        int barY = 0;
        final int barHeight = 3;
        final int barHeight2 = barHeight / 2;

        // get the colors
        final RGB[] rgbLine = yData.getRgbLine();
        final RGB[] rgbDark = yData.getRgbDark();
        final RGB[] rgbBright = yData.getRgbBright();

        final RGB[] rgbText = yData.getRgbText();

        final Color colorText = new Color(display, rgbText[0]);
        final Color colorLine = new Color(display, rgbLine[0]);
        final Color colorBgDark = new Color(display, rgbDark[0]);
        final Color colorBgBright = new Color(display, rgbBright[0]);

        gc.setLineStyle(SWT.LINE_SOLID);

        int alphaFill = getAlphaFill(isTopGraph);
        alphaFill = 0xd0;
        gc.setAntialias(_chart.graphAntialiasing);
        gc.setAlpha(alphaFill);

        gc.setForeground(colorBgBright);
        gc.setBackground(colorBgDark);

        boolean isPrevInvalid = true;

        /*
         * draw the lines into the paths
         */
        for (int valueIndex = 0; valueIndex < valueSize; valueIndex++) {

            // check array bounds
            if (valueIndex >= yValueLength) {
                break;
            }

            final float graphY = yValues[valueIndex];

            if (graphY != graphY) {

                // value is NaN

                isPrevInvalid = true;
                isDrawFirstPoint = true;

                continue;
            }

            final double graphX = xValues[valueIndex] - graphValueOffset;
            final double devX = graphX * scaleX;

            final float devY = (float) (graphY * scaleY);

            // check if position is horizontal visible
            if (devX < 0 || isPrevInvalid) {

                // keep current position which is used as the starting point for painting

                isPrevInvalid = false;

                graphYPrev = graphY;

                devXPrev = devX;
                devYPrev = devY;
                prevValueIndex = valueIndex;

                valueIndexFirstPoint = valueIndex;

                continue;
            }

            /*
             * draw first point
             */
            if (isDrawFirstPoint) {

                // move to the first point

                isDrawFirstPoint = false;

                // set first point before devX==0 that the first line is not visible but correctly painted
                final double devXFirstPoint = devXPrev;

                final float devYInverse = devY0Inverse - devYPrev;

                barXStart = (int) devXFirstPoint;
                barY = (int) devYInverse;

                /*
                 * set line hover positions for the first point
                 */
                final long devXRect = (long) devXFirstPoint;

                final RectangleLong currentRect = new RectangleLong(devXRect, 0, 1, devChartHeight);
                final PointLong currentPoint = new PointLong(devXRect, (long) (devYTop + devYInverse));

                lineDevPositions[valueIndexFirstPoint] = currentPoint;
                lineFocusRectangles[valueIndexFirstPoint] = currentRect;

                prevLineRect = currentRect;
            }

            /*
             * draw bar when value has changed
             */
            {
                final float devYBar = devY0Inverse - devY;

                if (devY == devYPrev) {

                    // y has not changed

                } else {

                    // y has changed

                    Color bgColor;
                    if (valueIndex == 0) {

                        // set initial value

                        bgColor = yValues2[0] == 1 ? colorBgBright : colorBgDark;

                    } else {

                        if (yValues2[valueIndex - 1] == 1) {

                            // grosses Kettenblatt

                            bgColor = colorBgBright;

                        } else {

                            // kleines Kettenblatt

                            bgColor = colorBgDark;
                        }
                    }
                    gc.setBackground(bgColor);

                    int barWidth = barXEnd - barXStart + devXOverlap2;

                    // ensure bar is painted otherwise it look ugly
                    if (barWidth < 1) {
                        barWidth = 1;
                    }

                    gc.fillRectangle(//
                            barXStart - devXOverlap, barY - barHeight2, barWidth, barHeight);

                    barXStart = (int) devX;
                    barY = (int) devYBar;
                }

                barXEnd = (int) devX;

                /*
                 * set line hover positions
                 */
                final double devXDiff = (devX - devXPrev) / 2;
                final double devXDiffWidth = devXDiff < 1 ? 1 : (devXDiff + 0.5);
                final double devXRect = devX - devXDiffWidth;

                // add the right part of the rectangle width into the previous rectangle
                prevLineRect.width += devXDiffWidth + 1;

                // check if hovered line is hit
                if (isSetHoveredIndex == false && prevLineRect.contains(_devXMouseMove, _devYMouseMove)) {
                    _hoveredValuePointIndex = prevValueIndex;
                    isSetHoveredIndex = true;
                }

                final RectangleLong currentRect = new RectangleLong((long) devXRect, 0, (long) (devXDiffWidth + 1),
                        devChartHeight);
                final PointLong currentPoint = new PointLong((long) devX, (long) (devYTop + devYBar));

                lineDevPositions[valueIndex] = currentPoint;
                lineFocusRectangles[valueIndex] = currentRect;

                prevLineRect = currentRect;
            }

            /*
             * draw last bar
             */
            if (valueIndex == lastIndex || //

            // check if last visible position + 1 is reached
                    devX > devXVisibleWidth) {

                /*
                 * this is the last point for a filled graph
                 */

                int barWidth = barXEnd - barXStart + devXOverlap2;

                // ensure bar is painted
                if (barWidth < 1) {
                    barWidth = 1;
                }

                Color bgColor;
                if (valueIndex == 0) {

                    // set initial value

                    bgColor = yValues2[0] == 1 ? colorBgBright : colorBgDark;

                } else {

                    if (yValues2[valueIndex - 1] == 1) {

                        // grosses Kettenblatt

                        bgColor = colorBgBright;

                    } else {

                        // kleines Kettenblatt

                        bgColor = colorBgDark;
                    }
                }
                gc.setBackground(bgColor);

                gc.fillRectangle(//
                        barXStart - devXOverlap, barY - barHeight2, barWidth, barHeight);

                /*
                 * set line rectangle
                 */
                final double devXDiff = (devX - devXPrev) / 2;
                final long devXDiffWidth = devXDiff < 1 ? 1 : (int) (devXDiff + 0.5);
                final long devXRect = (long) (devX - devXDiffWidth);

                // set right part of the rectangle width into the previous rectangle
                prevLineRect.width += devXDiffWidth;

                // check if hovered line is hit
                if (isSetHoveredIndex == false && prevLineRect.contains(_devXMouseMove, _devYMouseMove)) {
                    _hoveredValuePointIndex = valueIndex;
                    isSetHoveredIndex = true;
                }

                final float devYInverse = devY0Inverse - devY;

                final RectangleLong lastRect = new RectangleLong(devXRect, 0, devXDiffWidth + 1, devChartHeight);
                final PointLong lastPoint = new PointLong((long) devX, devYTop + (long) devYInverse);

                lineDevPositions[valueIndex] = lastPoint;
                lineFocusRectangles[valueIndex] = lastRect;

                if (isSetHoveredIndex == false && lastRect.contains(_devXMouseMove, _devYMouseMove)) {
                    _hoveredValuePointIndex = valueIndex;
                }

                break;
            }

            devXPrev = devX;
            devYPrev = devY;
            prevValueIndex = valueIndex;
        }

        /**
         * force a max width because the fill will not be drawn on Linux
         */
        //      final double graphWidth = xValues[Math.min(xValueLength - 1, lastIndex)] - graphValueOffset;
        //      final int devGraphWidth = Math.min(0x7fff, (int) (graphWidth * scaleX));

        //      gc.fillGradientRectangle(//
        //            0,
        //            devGraphHeight,
        //            devGraphWidth,
        //            -devGraphHeight,
        //            true);

        // dispose resources
        colorLine.dispose();
        colorBgDark.dispose();
        colorBgBright.dispose();
        colorText.dispose();

        gc.setAlpha(0xFF);
        gc.setAntialias(SWT.OFF);
    }

    private void drawAsync_600_History(final GC gcGraph, final GraphDrawingData graphDrawingData) {

        final ChartDataXSerie xData = graphDrawingData.getXData();

        final double[] xValues = xData.getHighValuesDouble()[0];
        final int serieSize = xValues.length;

        // setup hovered positions
        _lineFocusRectangles.add(new RectangleLong[serieSize]);
        _lineDevPositions.add(new PointLong[serieSize]);
        _isHoveredLineVisible = true;

        // check array bounds
        final int xValueLength = xValues.length;
        if (0 >= xValueLength) {
            return;
        }

        // get top/bottom border values of the graph
        final int devGraphHeight = getDevVisibleGraphHeight();

        final double scaleX = graphDrawingData.getScaleX();

        final RectangleLong[] lineFocusRectangles = _lineFocusRectangles.get(_lineFocusRectangles.size() - 1);
        final PointLong[] lineDevPositions = _lineDevPositions.get(_lineDevPositions.size() - 1);
        RectangleLong prevLineRect = null;

        final double graphValueOffset = (Math.max(0, _xxDevViewPortLeftBorder) / scaleX);
        final double graphXStart = xValues[0] - graphValueOffset;
        double devXPrev = scaleX * graphXStart;

        final Rectangle chartRectangle = gcGraph.getClipping();
        final int devXVisibleWidth = chartRectangle.width;

        boolean isDrawFirstPoint = true;

        final int lastIndex = serieSize - 1;

        int valueIndexFirstPoint = 0;
        int prevValueIndex = 0;

        /*
         * set the hovered index only ONCE because when autoscrolling is done to the right side this
         * can cause that the last value is used for the hovered index instead of the previous
         * before the last
         */
        boolean isSetHoveredIndex = false;

        /*
         * draw the lines into the paths
         */
        for (int valueIndex = 0; valueIndex < serieSize; valueIndex++) {

            final double graphX = xValues[valueIndex] - graphValueOffset;
            final double devX = (graphX * scaleX);

            // check if position is horizontal visible
            if (devX < 0) {

                // keep current position which is used as the painting starting point

                devXPrev = devX;

                valueIndexFirstPoint = valueIndex;
                prevValueIndex = valueIndex;

                continue;
            }

            /*
             * draw first point
             */
            if (isDrawFirstPoint) {

                // move to the first point

                isDrawFirstPoint = false;

                // set first point before devX==0 that the first line is not visible but correctly painted
                final double devXFirstPoint = devXPrev;

                /*
                 * set line hover positions for the first point
                 */
                final long devXRect = (long) devXFirstPoint;

                final RectangleLong currentRect = new RectangleLong(devXRect, 0, 1, devGraphHeight);
                final PointLong currentPoint = new PointLong(devXRect, 0);

                lineDevPositions[valueIndexFirstPoint] = currentPoint;
                lineFocusRectangles[valueIndexFirstPoint] = currentRect;

                prevLineRect = currentRect;
            }

            /*
             * draw line to current point
             */
            if ((int) devX != (int) devXPrev) {

                // optimization: draw only ONE line for the current x-position
                // but draw to the 0 line otherwise it's possible that a triangle is painted

                /*
                 * set line hover positions
                 */
                final double devXDiff = (devX - devXPrev) / 2;
                final long devXDiffWidth = (long) (devXDiff < 1 ? 1 : (devXDiff + 0.5));
                final long devXRect = (long) (devX - devXDiffWidth);

                // add the right part of the rectangle width into the previous rectangle
                prevLineRect.width += devXDiffWidth + 1;

                // check if hovered line is hit, this check is an inline for .contains(...)
                if (isSetHoveredIndex == false && prevLineRect.contains(_devXMouseMove, _devYMouseMove)) {
                    _hoveredValuePointIndex = prevValueIndex;
                    isSetHoveredIndex = true;
                }

                final RectangleLong currentRect = new RectangleLong(devXRect, 0, devXDiffWidth + 1, devGraphHeight);
                final PointLong currentPoint = new PointLong((long) devX, 0);

                lineDevPositions[valueIndex] = currentPoint;
                lineFocusRectangles[valueIndex] = currentRect;

                prevLineRect = currentRect;
            }

            /*
             * draw last point
             */
            if (valueIndex == lastIndex || //

            // check if last visible position + 1 is reached
                    devX > devXVisibleWidth) {

                /*
                 * set line rectangle
                 */
                final double devXDiff = (devX - devXPrev) / 2;
                final long devXDiffWidth = (long) (devXDiff < 1 ? 1 : (devXDiff + 0.5));
                final long devXRect = (long) (devX - devXDiffWidth);

                // set right part of the rectangle width into the previous rectangle
                prevLineRect.width += devXDiffWidth;

                // check if hovered line is hit, this check is an inline for .contains(...)
                if (isSetHoveredIndex == false && prevLineRect.contains(_devXMouseMove, _devYMouseMove)) {
                    _hoveredValuePointIndex = valueIndex;
                    isSetHoveredIndex = true;
                }

                final RectangleLong lastRect = new RectangleLong(devXRect, 0, devXDiffWidth + 1, devGraphHeight);
                final PointLong lastPoint = new PointLong((long) devX, 0);

                lineDevPositions[valueIndex] = lastPoint;
                lineFocusRectangles[valueIndex] = lastRect;

                if (isSetHoveredIndex == false && lastRect.contains(_devXMouseMove, _devYMouseMove)) {
                    _hoveredValuePointIndex = valueIndex;
                }

                break;
            }

            devXPrev = devX;
            prevValueIndex = valueIndex;
        }
    }

    /**
     * Paint event handler
     * 
     * <pre>
     * Top-down sequence how the images are painted
     * 
     * {@link #_chartImage_40_Overlay}
     * {@link #_chartImage_30_Custom}
     * {@link #_chartImage_20_Chart}
     * {@link #_chartImage_10_Graphs}
     * </pre>
     * 
     * @param gc
     * @param eventTime
     */
    private void drawSync_000_onPaint(final GC gc, final long eventTime) {

        //      final long startTime = System.nanoTime();
        //      // TODO remove SYSTEM.OUT.PRINTLN
        //
        //      System.out.println(UI.timeStampNano() + " drawSync_000_onPaint: START");
        //      // TODO remove SYSTEM.OUT.PRINTLN

        if (_allGraphDrawingData == null || _allGraphDrawingData.size() == 0) {

            // fill the image area when there is no graphic
            gc.setBackground(_chart.getBackgroundColor());
            gc.fillRectangle(_clientArea);

            drawSyncBg_999_ErrorMessage(gc);

            return;
        }

        boolean isPaintedDirectly = false;

        if (_isChartDirty) {

            // draw chart

            isPaintedDirectly = drawAsync_100_StartPainting();

            if (isPaintedDirectly == false) {

                /*
                 * paint dragged chart until the chart is recomputed
                 */
                if (_isPaintDraggedImage) {
                    drawSync_020_MoveCanvas(gc);
                    return;
                }

                /*
                 * mac osx is still flickering, added the drawChartImage in version 1.0
                 */
                if (_chartImage_20_Chart != null) {

                    final Image image = drawSync_010_ImageChart(gc, eventTime);
                    if (image == null) {
                        return;
                    }

                    final int gcHeight = _clientArea.height;
                    final int imageHeight = image.getBounds().height;

                    if (gcHeight > imageHeight) {

                        // fill the gap between the image and the drawable area
                        gc.setBackground(_chart.getBackgroundColor());
                        gc.fillRectangle(0, imageHeight, _clientArea.width, _clientArea.height - imageHeight);

                    } else {
                        gc.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_GREEN));
                    }
                } else {
                    gc.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_RED));
                }
            }
        }

        if (_isChartDirty == false || isPaintedDirectly) {

            /*
             * if the graph is not yet drawn (because this is done in another thread) there is
             * nothing to do
             */
            if (_chartImage_20_Chart == null) {
                // fill the image area when there is no graphic
                gc.setBackground(_chart.getBackgroundColor());
                //            gc.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_MAGENTA));
                gc.fillRectangle(_clientArea);
                return;
            }

            // redraw() is done in async painting but NOT after images are painted
            if (isPaintedDirectly) {

                //            System.out.println("isPaintedDirectly\t");
                //            // TODO remove SYSTEM.OUT.PRINTLN
            }

            drawSync_300_Image30Custom();
            drawSync_010_ImageChart(gc, eventTime);
        }

        //      final long endTime = System.nanoTime();
        //      System.out.println(UI.timeStampNano()
        //            + " drawSync_000_onPaint: END  "
        //            + (((double) endTime - startTime) / 1000000)
        //            + " ms   #:"
        //            + _drawAsyncCounter[0]);
        //      System.out.println(UI.timeStampNano() + " \t");
        //      System.out.println(UI.timeStampNano() + " \t");
        //      // TODO remove SYSTEM.OUT.PRINTLN
    }

    private Image drawSync_010_ImageChart(final GC gc, final long eventTime) {

        final boolean isOverlayImageVisible = _isXSliderVisible || _isYSliderVisible || _isXMarkerMoved
                || _isSelectionVisible || _isHoveredLineVisible;

        if (isOverlayImageVisible) {

            drawSync_400_OverlayImage(eventTime);

            if (_chartImage_40_Overlay != null) {
                //            System.out.println("gc <- 40\tdrawSync010ImageChart");
                //            // TODO remove SYSTEM.OUT.PRINTLN

                gc.drawImage(_chartImage_40_Overlay, 0, 0);
            }
            return _chartImage_40_Overlay;

        } else {

            if (_chartImage_20_Chart != null) {
                //            System.out.println("gc <- 20");
                //            // TODO remove SYSTEM.OUT.PRINTLN

                gc.drawImage(_chartImage_20_Chart, 0, 0);
            }
            return _chartImage_20_Chart;
        }
    }

    /**
     * This is painted when the chart is scrolled horizontally or when its dragged with the mouse.
     * 
     * @param gc
     */
    private void drawSync_020_MoveCanvas(final GC gc) {

        if (_draggedChartDraggedPos == null) {
            return;
        }

        final int devXDiff = _draggedChartDraggedPos.x - _draggedChartStartPos.x;
        final int devYDiff = 0;

        // this value is flickering, I've no idea why this is computed
        //      int devXDiff = _draggedChartDraggedPos.x - _draggedChartStartPos.x;
        //      devXDiff = 0;
        //      final int devYDiff = 0;

        /*
         * draw background that the none painted areas do not look ugly when the chart is dragged
         */
        gc.setBackground(_chart.getBackgroundColor());

        if (devXDiff > 0) {
            gc.fillRectangle(0, devYDiff, devXDiff, _clientArea.height);
        } else {
            gc.fillRectangle(_clientArea.width + devXDiff, devYDiff, -devXDiff, _clientArea.height);
        }

        if (_chartImage_40_Overlay != null && _chartImage_40_Overlay.isDisposed() == false) {

            gc.drawImage(_chartImage_40_Overlay, devXDiff, devYDiff);

        } else if (_chartImage_30_Custom != null && _chartImage_30_Custom.isDisposed() == false) {

            gc.drawImage(_chartImage_30_Custom, devXDiff, devYDiff);

        } else if (_chartImage_20_Chart != null && _chartImage_20_Chart.isDisposed() == false) {

            gc.drawImage(_chartImage_20_Chart, devXDiff, devYDiff);
        }
    }

    /**
     * Draws custom foreground layers on top of the graphs.
     */
    private void drawSync_300_Image30Custom() {

        // the layer image has the same size as the graph image
        final Rectangle chartRect = _chartImage_20_Chart.getBounds();

        // ensure correct image size
        if (chartRect.width <= 0 || chartRect.height <= 0) {
            return;
        }

        /*
         * when the existing image is the same size as the new image, we will redraw it only if it's
         * set to dirty
         */
        if (_isCustomLayerImageDirty == false && _chartImage_30_Custom != null) {

            final Rectangle oldBounds = _chartImage_30_Custom.getBounds();

            if (oldBounds.width == chartRect.width && oldBounds.height == chartRect.height) {
                return;
            }
        }

        if (Util.canReuseImage(_chartImage_30_Custom, chartRect) == false) {
            _chartImage_30_Custom = Util.createImage(getDisplay(), _chartImage_30_Custom, chartRect);
        }

        final GC gcCustom = new GC(_chartImage_30_Custom);
        try {

            gcCustom.fillRectangle(chartRect);

            /*
             * draw the chart image with the graphs into the custom layer image, the custom
             * foreground layers are drawn on top of the graphs
             */
            gcCustom.drawImage(_chartImage_20_Chart, 0, 0);

            for (int graphIndex = 0; graphIndex < _allGraphDrawingData.size(); graphIndex++) {

                final GraphDrawingData graphDrawingData = _allGraphDrawingData.get(graphIndex);

                graphDrawingData.graphIndex = graphIndex;

                final ArrayList<IChartLayer> customFgLayers = graphDrawingData.getYData()
                        .getCustomForegroundLayers();

                for (final IChartLayer layer : customFgLayers) {
                    layer.draw(gcCustom, graphDrawingData, _chart, _pc);
                }
            }
            //         System.out.println((UI.timeStampNano() + " [" + getClass().getSimpleName() + "] ") + ("\t"));
            //         // TODO remove SYSTEM.OUT.PRINTLN

        } finally {
            gcCustom.dispose();
        }

        _isCustomLayerImageDirty = false;
    }

    /**
     * Draws the overlays into the graph (fg layer image) slider image which contains the custom
     * layer image
     * 
     * @param eventTime
     */
    private void drawSync_400_OverlayImage(final long eventTime) {

        if (_chartImage_30_Custom == null) {
            return;
        }

        //      final long start = System.nanoTime();

        // the slider image is the same size as the graph image
        final Rectangle graphImageRect = _chartImage_30_Custom.getBounds();

        // check if an overlay image redraw is necessary
        if (_isOverlayDirty == false && _isSliderDirty == false && _isSelectionDirty == false
                && _isHoveredBarDirty == false && _hoveredTitleSegment == null && _hoveredValuePointIndex == -1
                && _chartImage_40_Overlay != null) {

            final Rectangle oldBounds = _chartImage_40_Overlay.getBounds();
            if (oldBounds.width == graphImageRect.width && oldBounds.height == graphImageRect.height) {

                // overlay image is not dirty

                return;
            }
        }

        // ensure correct image size
        if (graphImageRect.width <= 0 || graphImageRect.height <= 0) {
            return;
        }

        if (Util.canReuseImage(_chartImage_40_Overlay, graphImageRect) == false) {
            _chartImage_40_Overlay = Util.createImage(getDisplay(), _chartImage_40_Overlay, graphImageRect);
        }

        if (_chartImage_40_Overlay.isDisposed()) {
            return;
        }

        //      System.out.println((UI.timeStampNano() + " [" + getClass().getSimpleName() + "] ")
        //            + ("\tdrawSync_400_OverlayImage"));
        //      // TODO remove SYSTEM.OUT.PRINTLN

        final GC gcOverlay = new GC(_chartImage_40_Overlay);
        {
            /*
             * copy the graph image into the slider image, the slider will be drawn on top of the
             * graph
             */
            gcOverlay.fillRectangle(graphImageRect);
            gcOverlay.drawImage(_chartImage_30_Custom, 0, 0);

            /*
             * draw x/y-sliders
             */
            if (_isXSliderVisible) {
                createXSliderLabel(gcOverlay, _xSliderOnTop);
                createXSliderLabel(gcOverlay, _xSliderOnBottom);
                updateXSliderYPosition();

                drawSync_410_XSlider(gcOverlay, _xSliderOnBottom);
                drawSync_410_XSlider(gcOverlay, _xSliderOnTop);
            }
            if (_isYSliderVisible) {
                drawSync_420_YSliders(gcOverlay);
            }
            _isSliderDirty = false;

            if (_isXMarkerMoved) {
                drawSync_430_XMarker(gcOverlay);
            }

            if (_isSelectionVisible) {
                drawSync_440_Selection(gcOverlay);
            }

            if (_isHoveredBarDirty) {
                drawSync_450_HoveredBar(gcOverlay);
                _isHoveredBarDirty = false;
            }

            if (_hoveredValuePointIndex != -1 && _lineDevPositions.size() > 0) {

                // hovered lines are set -> draw it
                drawSync_460_HoveredLine(gcOverlay);
            }

            if (_hoveredTitleSegment != null) {
                drawSync_462_HoveredSegment(gcOverlay);
            }

            if (_isOverlayDirty) {
                drawSync_470_CustomOverlay(gcOverlay, eventTime);
                _isOverlayDirty = false;
            }

        }
        gcOverlay.dispose();

        //      System.out.println("time\t" + ((float) (System.nanoTime() - start) / 1000000) + " ms");
        //      // TODO remove SYSTEM.OUT.PRINTLN
    }

    /**
     * @param gcGraph
     * @param slider
     */
    private void drawSync_410_XSlider(final GC gcGraph, final ChartXSlider slider) {

        final Display display = getDisplay();

        final boolean isGraphOverlapped = _chartDrawingData.chartDataModel.isGraphOverlapped();

        final int devSliderLinePos = (int) (slider.getXXDevSliderLinePos() - _xxDevViewPortLeftBorder);

        final int grayColorIndex = 60;
        final Color colorTxt = new Color(display, grayColorIndex, grayColorIndex, grayColorIndex);

        int graphNo = 0;

        final ArrayList<ChartXSliderLabel> labelList = slider.getLabelList();

        // draw slider for each graph
        for (final GraphDrawingData drawingData : _allGraphDrawingData) {

            graphNo++;

            /*
             * Draw only for the last overlapped graph
             */
            final ChartType chartType = drawingData.getChartType();
            final boolean isLastOverlappedGraph = isGraphOverlapped && graphNo == _allGraphDrawingData.size()
                    && (chartType == ChartType.LINE || chartType == ChartType.HORIZONTAL_BAR);

            if (isGraphOverlapped && isLastOverlappedGraph == false) {
                continue;
            }

            final ChartDataYSerie yData = drawingData.getYData();
            final ChartXSliderLabel label = labelList.get(graphNo - 1);

            final Color colorLine = new Color(display, yData.getRgbLine()[0]);
            final Color colorBright = new Color(display, yData.getRgbBright()[0]);
            final Color colorDark = new Color(display, yData.getRgbDark()[0]);

            final int labelHeight = label.height;
            final int labelWidth = label.width;
            final int devXLabel = label.x;
            final int devYLabel = label.y;

            final int devYBottom = drawingData.getDevYBottom();
            final boolean isSliderHovered = _mouseOverXSlider != null && _mouseOverXSlider == slider;

            /*
             * when the mouse is over the slider, the slider is painted in a darker color
             */

            // draw slider line
            if ((_isFocusActive && _selectedXSlider == slider) || isSliderHovered) {
                gcGraph.setAlpha(0xd0);
            } else {
                gcGraph.setAlpha(0x60);
            }
            gcGraph.setForeground(colorLine);
            gcGraph.setLineDash(new int[] { 4, 1, 4, 1 });
            gcGraph.drawLine(devSliderLinePos, devYLabel + labelHeight, devSliderLinePos, devYBottom);

            gcGraph.setBackground(colorDark);
            gcGraph.setForeground(colorBright);

            // draw label border
            gcGraph.setForeground(colorLine);
            gcGraph.setLineStyle(SWT.LINE_SOLID);
            gcGraph.drawRoundRectangle(devXLabel, devYLabel - 4, labelWidth, labelHeight + 3, 4, 4);

            // draw slider label
            gcGraph.setAlpha(0xff);
            gcGraph.setForeground(colorTxt);
            gcGraph.drawText(label.text, devXLabel + 2, devYLabel - 5, true);

            // draw a tiny marker on the graph
            gcGraph.setBackground(colorLine);
            gcGraph.fillRectangle(devSliderLinePos - 3, label.devYGraph - 2, 7, 3);

            /*
             * draw a marker below the x-axis to make the selection more visible
             */
            if (_isFocusActive && slider == _selectedXSlider) {

                final int markerWidth = BAR_MARKER_WIDTH;
                final int markerWidth2 = markerWidth / 2;

                final int devMarkerXPos = devSliderLinePos - markerWidth2;

                final int[] marker = new int[] { devMarkerXPos, devYBottom + 1 + markerWidth2,
                        devMarkerXPos + markerWidth2, devYBottom + 1, devMarkerXPos + markerWidth,
                        devYBottom + 1 + markerWidth2 };

                gcGraph.setAlpha(0xc0);
                gcGraph.setLineStyle(SWT.LINE_SOLID);

                // draw background
                gcGraph.setBackground(colorDark);
                gcGraph.fillPolygon(marker);

                // draw border
                gcGraph.setForeground(colorLine);
                gcGraph.drawPolygon(marker);

                gcGraph.setAlpha(0xff);
            }

            colorLine.dispose();
            colorBright.dispose();
            colorDark.dispose();
        }

        colorTxt.dispose();
    }

    /**
     * Draw the y-slider which it hit.
     * 
     * @param gcGraph
     */
    private void drawSync_420_YSliders(final GC gcGraph) {

        if (_hitYSlider == null) {
            return;
        }

        final Display display = getDisplay();

        //      final int grayColorIndex = 60;
        //      final Color colorTxt = new Color(display, grayColorIndex, grayColorIndex, grayColorIndex);

        final int devXChartWidth = getDevVisibleChartWidth();

        for (final ChartYSlider ySlider : _ySliders) {

            if (_hitYSlider == ySlider) {

                final ChartDataYSerie yData = ySlider.getYData();

                final Color colorLine = new Color(display, yData.getRgbLine()[0]);
                final Color colorBright = new Color(display, yData.getRgbBright()[0]);
                final Color colorDark = new Color(display, yData.getRgbDark()[0]);
                final Color colorText = new Color(display, yData.getRgbText()[0]);

                final GraphDrawingData drawingData = ySlider.getDrawingData();
                final int devYBottom = drawingData.getDevYBottom();
                final int devYTop = devYBottom - drawingData.devGraphHeight;

                final int devYSliderLine = ySlider.getDevYSliderLine();

                // set the label and line NOT outside of the chart
                int devYLabelPos = devYSliderLine;

                if (devYSliderLine > devYBottom) {
                    devYLabelPos = devYBottom;
                } else if (devYSliderLine < devYTop) {
                    devYLabelPos = devYTop;
                }

                // ySlider is the slider which was hit by the mouse, draw the
                // slider label

                final StringBuilder labelText = new StringBuilder();

                final float devYValue = (float) (((double) devYBottom - devYSliderLine) / drawingData.getScaleY()
                        + drawingData.getGraphYBottom());

                final String unitLabel = yData.getUnitLabel();

                // create the slider text
                labelText.append(Util.formatValue(devYValue, yData.getAxisUnit(), yData.getValueDivisor(), true));
                if (unitLabel.length() > 0) {
                    labelText.append(UI.SPACE);
                    labelText.append(unitLabel);
                }
                labelText.append(UI.SPACE);
                final String label = labelText.toString();

                final Point labelExtend = gcGraph.stringExtent(label);

                final int labelHeight = labelExtend.y - 2;
                final int labelWidth = labelExtend.x + 1;
                final int labelX = _ySliderGraphX - labelWidth - 5;
                final int labelY = devYLabelPos - labelHeight;

                // draw label background
                gcGraph.setForeground(colorBright);
                gcGraph.setBackground(colorDark);
                //            gcGraph.setAlpha(0xb0);
                gcGraph.fillGradientRectangle(labelX, labelY, labelWidth, labelHeight, true);

                // draw label border
                //            gcGraph.setAlpha(0xa0);
                gcGraph.setForeground(colorLine);
                gcGraph.drawRectangle(labelX, labelY, labelWidth, labelHeight);
                //            gcGraph.setAlpha(0xff);

                // draw label text
                gcGraph.setForeground(colorText);
                gcGraph.drawText(label, labelX + 3, labelY - 0, true);

                // draw slider line
                gcGraph.setForeground(colorLine);
                gcGraph.setLineDash(DOT_DASHES);
                gcGraph.drawLine(0, devYLabelPos, devXChartWidth, devYLabelPos);

                colorLine.dispose();
                colorBright.dispose();
                colorDark.dispose();
                colorText.dispose();

                // only 1 y-slider can be hit
                break;
            }
        }

        //      colorTxt.dispose();
    }

    private void drawSync_430_XMarker(final GC gc) {

        final Display display = getDisplay();
        final Color colorXMarker = new Color(display, 255, 153, 0);

        final int devDraggingDiff = _devXMarkerDraggedPos - _devXMarkerDraggedStartPos;

        // draw x-marker for each graph
        for (final GraphDrawingData drawingData : _allGraphDrawingData) {

            final ChartDataXSerie xData = drawingData.getXData();

            final double scaleX = drawingData.getScaleX();
            final double valueDraggingDiff = devDraggingDiff / scaleX;

            final int synchStartIndex = xData.getSynchMarkerStartIndex();
            final int synchEndIndex = xData.getSynchMarkerEndIndex();

            final double[] xValues = xData.getHighValuesDouble()[0];
            final double valueXStart = xValues[synchStartIndex];
            final double valueXEnd = xValues[synchEndIndex];

            final int devXStart = (int) (scaleX * valueXStart - _xxDevViewPortLeftBorder);
            final int devXEnd = (int) (scaleX * valueXEnd - _xxDevViewPortLeftBorder);
            int devMovedXStart = devXStart;
            int devMovedXEnd = devXEnd;

            final double valueXStartWithOffset = valueXStart + valueDraggingDiff;
            final double valueXEndWithOffset = valueXEnd + valueDraggingDiff;

            _movedXMarkerStartValueIndex = computeXMarkerValue(xValues, synchStartIndex, valueDraggingDiff,
                    valueXStartWithOffset);

            _movedXMarkerEndValueIndex = computeXMarkerValue(xValues, synchEndIndex, valueDraggingDiff,
                    valueXEndWithOffset);

            devMovedXStart = (int) (scaleX * xValues[_movedXMarkerStartValueIndex] - _xxDevViewPortLeftBorder);
            devMovedXEnd = (int) (scaleX * xValues[_movedXMarkerEndValueIndex] - _xxDevViewPortLeftBorder);

            /*
             * when the moved x-marker is on the right or the left border, make sure that the
             * x-markers don't get too small
             */
            final double valueMovedDiff = xValues[_movedXMarkerEndValueIndex]
                    - xValues[_movedXMarkerStartValueIndex];

            /*
             * adjust start and end position
             */
            if (_movedXMarkerStartValueIndex == 0 && valueMovedDiff < _xMarkerValueDiff) {

                /*
                 * the x-marker is moved to the left, the most left x-marker is on the first
                 * position
                 */

                int valueIndex;

                for (valueIndex = 0; valueIndex < xValues.length; valueIndex++) {
                    if (xValues[valueIndex] >= _xMarkerValueDiff) {
                        break;
                    }
                }

                _movedXMarkerEndValueIndex = valueIndex;

            } else if (_movedXMarkerEndValueIndex == xValues.length - 1 && valueMovedDiff < _xMarkerValueDiff) {

                /*
                 * the x-marker is moved to the right, the most right x-marker is on the last
                 * position
                 */

                int valueIndex;
                final double valueFirstIndex = xValues[xValues.length - 1] - _xMarkerValueDiff;

                for (valueIndex = xValues.length - 1; valueIndex > 0; valueIndex--) {
                    if (xValues[valueIndex] <= valueFirstIndex) {
                        break;
                    }
                }

                _movedXMarkerStartValueIndex = valueIndex;
            }

            if (valueMovedDiff > _xMarkerValueDiff) {

                /*
                 * force the value diff for the x-marker, the moved value diff can't be wider then
                 * one value index
                 */

                final double valueStart = xValues[_movedXMarkerStartValueIndex];
                int valueIndex;
                for (valueIndex = _movedXMarkerEndValueIndex - 0; valueIndex >= 0; valueIndex--) {
                    if (xValues[valueIndex] - valueStart < _xMarkerValueDiff) {
                        valueIndex++;
                        break;
                    }
                }
                valueIndex = Math.min(valueIndex, xValues.length - 1);

                _movedXMarkerEndValueIndex = valueIndex;
            }

            _movedXMarkerEndValueIndex = Math.min(_movedXMarkerEndValueIndex, xValues.length - 1);

            devMovedXStart = (int) (scaleX * xValues[_movedXMarkerStartValueIndex] - _xxDevViewPortLeftBorder);
            devMovedXEnd = (int) (scaleX * xValues[_movedXMarkerEndValueIndex] - _xxDevViewPortLeftBorder);

            final int devYTop = drawingData.getDevYBottom() - drawingData.devGraphHeight;
            final int devYBottom = drawingData.getDevYBottom();

            // draw moved x-marker
            gc.setForeground(colorXMarker);
            gc.setBackground(display.getSystemColor(SWT.COLOR_WHITE));

            gc.setAlpha(0x80);

            gc.fillGradientRectangle(//
                    devMovedXStart, devYBottom, devMovedXEnd - devMovedXStart, devYTop - devYBottom, true);

            gc.drawLine(devMovedXStart, devYTop, devMovedXStart, devYBottom);
            gc.drawLine(devMovedXEnd, devYTop, devMovedXEnd, devYBottom);

            gc.setAlpha(0xff);
        }

        colorXMarker.dispose();
    }

    private void drawSync_440_Selection(final GC gc) {

        _isSelectionDirty = false;

        final ChartType chartType = _chart.getChartDataModel().getChartType();

        if (chartType == ChartType.LINE) {

            if (_isSelectionVisible && _lineSelectionPainter != null) {
                _lineSelectionPainter.drawSelectedLines(gc, _allGraphDrawingData, _isFocusActive);
            }

        } else {

            // loop: all graphs
            for (final GraphDrawingData drawingData : _allGraphDrawingData) {

                if (chartType == ChartType.BAR) {

                    drawSync_444_BarSelection(gc, drawingData);
                }
            }
        }
    }

    private void drawSync_444_BarSelection(final GC gc, final GraphDrawingData drawingData) {

        // check if multiple bars are selected
        boolean drawSelection = false;
        int selectedIndex = 0;
        if (_selectedBarItems != null) {
            int selectionIndex = 0;
            for (final boolean isBarSelected : _selectedBarItems) {
                if (isBarSelected) {
                    if (drawSelection == false) {
                        drawSelection = true;
                        selectedIndex = selectionIndex;
                    } else {
                        drawSelection = false;
                        return;
                    }
                }
                selectionIndex++;
            }
        }

        if (drawSelection == false) {
            return;
        }

        /*
         * a bar is selected
         */

        // get the chart data
        final ChartDataYSerie yData = drawingData.getYData();
        final int[][] colorsIndex = yData.getColorsIndex();

        // get the colors
        final RGB[] rgbLine = yData.getRgbLine();
        final RGB[] rgbDark = yData.getRgbDark();
        final RGB[] rgbBright = yData.getRgbBright();

        final int devYBottom = drawingData.getDevYBottom();
        //      final int devYBottom = drawingData.devGraphHeight;

        final Rectangle[][] barRectangeleSeries = drawingData.getBarRectangles();

        if (barRectangeleSeries == null) {
            return;
        }

        final int markerWidth = BAR_MARKER_WIDTH;
        final int barThickness = 1;
        final int markerWidth2 = markerWidth / 2;

        gc.setLineStyle(SWT.LINE_SOLID);

        // loop: all data series
        for (int serieIndex = 0; serieIndex < barRectangeleSeries.length; serieIndex++) {

            // get selected rectangle
            final Rectangle[] barRectangles = barRectangeleSeries[serieIndex];
            if (barRectangles == null || selectedIndex >= barRectangles.length) {
                continue;
            }

            final Rectangle barRectangle = barRectangles[selectedIndex];
            if (barRectangle == null) {
                continue;
            }

            /*
             * current bar is selected, draw the selected bar
             */

            final Rectangle barShapeSelected = new Rectangle((barRectangle.x - markerWidth2),
                    (barRectangle.y - markerWidth2), (barRectangle.width + markerWidth),
                    (barRectangle.height + markerWidth));

            final Rectangle barBarSelected = new Rectangle(barRectangle.x - 1, barRectangle.y - barThickness,
                    barRectangle.width + barThickness, barRectangle.height + 2 * barThickness);

            final int colorIndex = colorsIndex[serieIndex][selectedIndex];
            final RGB rgbBrightDef = rgbBright[colorIndex];
            final RGB rgbDarkDef = rgbDark[colorIndex];
            final RGB rgbLineDef = rgbLine[colorIndex];

            final Color colorBrightSelected = getColor(rgbBrightDef);
            final Color colorDarkSelected = getColor(rgbDarkDef);
            final Color colorLineSelected = getColor(rgbLineDef);

            // do't write into the x-axis units which also contains the
            // selection marker
            if (barShapeSelected.y + barShapeSelected.height > devYBottom) {
                barShapeSelected.height = devYBottom - barShapeSelected.y;
            }

            // draw the selection darker when the focus is set
            if (_isFocusActive) {
                gc.setAlpha(0xf0);
            } else {
                gc.setAlpha(0xa0);
            }

            // fill bar background
            gc.setForeground(colorDarkSelected);
            gc.setBackground(colorBrightSelected);

            if (barShapeSelected.height < 0) {

                // bar is below the x-axis, just draw a simple line

                gc.setForeground(colorLineSelected);
                gc.drawLine(//
                        barShapeSelected.x, devYBottom + 1, barShapeSelected.x + barShapeSelected.width,
                        devYBottom + 1);
            } else {

                gc.fillGradientRectangle(barShapeSelected.x + 1, barShapeSelected.y + 1, barShapeSelected.width - 1,
                        barShapeSelected.height - 1, true);

                // draw bar border
                gc.setForeground(colorLineSelected);
                gc.drawRoundRectangle(barShapeSelected.x, barShapeSelected.y, barShapeSelected.width,
                        barShapeSelected.height, 4, 4);

                // draw bar thicker
                gc.setBackground(colorDarkSelected);
                gc.fillRoundRectangle(//
                        barBarSelected.x, barBarSelected.y, barBarSelected.width, barBarSelected.height, 2, 2);
            }

            /*
             * draw a marker below the x-axis to make the selection more visible
             */
            if (_isFocusActive) {

                final int devMarkerXPos = barRectangle.x + (barRectangle.width / 2) - markerWidth2;

                final int[] marker = new int[] { devMarkerXPos, devYBottom + 1 + markerWidth2,
                        devMarkerXPos + markerWidth2, devYBottom + 1, devMarkerXPos + markerWidth - 0,
                        devYBottom + 1 + markerWidth2 };

                // draw background
                gc.setBackground(colorDarkSelected);
                gc.fillPolygon(marker);

                // draw border
                gc.setForeground(colorLineSelected);
                gc.drawPolygon(marker);

                gc.setAlpha(0xff);
            }
        }
    }

    private void drawSync_450_HoveredBar(final GC gcOverlay) {

        // check if hovered bar is disabled
        if (_hoveredBarSerieIndex == -1) {
            return;
        }

        // draw only bar chars
        if (_chart.getChartDataModel().getChartType() != ChartType.BAR) {
            return;
        }

        gcOverlay.setLineStyle(SWT.LINE_SOLID);
        gcOverlay.setAlpha(0xd0);

        // loop: all graphs
        for (final GraphDrawingData drawingData : _allGraphDrawingData) {

            // get the chart data
            final ChartDataYSerie yData = drawingData.getYData();
            final int serieLayout = yData.getChartLayout();
            final int[][] colorsIndex = yData.getColorsIndex();

            // get the colors
            final RGB[] rgbLine = yData.getRgbLine();
            final RGB[] rgbDark = yData.getRgbDark();
            final RGB[] rgbBright = yData.getRgbBright();

            final int devYBottom = drawingData.getDevYBottom();
            //         final int devYBottom = drawingData.devGraphHeight;

            final Rectangle[][] barRectangeleSeries = drawingData.getBarRectangles();

            final int markerWidth = BAR_MARKER_WIDTH;
            final int markerWidth2 = markerWidth / 2;

            // loop: all data series
            for (int serieIndex = 0; serieIndex < barRectangeleSeries.length; serieIndex++) {

                // get hovered rectangle
                final Rectangle hoveredRectangle = barRectangeleSeries[serieIndex][_hoveredBarValueIndex];

                if (hoveredRectangle == null) {
                    continue;
                }

                if (serieIndex != _hoveredBarSerieIndex) {
                    continue;
                }

                final int colorIndex = colorsIndex[serieIndex][_hoveredBarValueIndex];
                final RGB rgbBrightDef = rgbBright[colorIndex];
                final RGB rgbDarkDef = rgbDark[colorIndex];
                final RGB rgbLineDef = rgbLine[colorIndex];

                final Color colorBright = getColor(rgbBrightDef);
                final Color colorDark = getColor(rgbDarkDef);
                final Color colorLine = getColor(rgbLineDef);

                if (serieLayout != ChartDataYSerie.BAR_LAYOUT_STACKED) {

                }

                final Rectangle hoveredBarShape = new Rectangle((hoveredRectangle.x - markerWidth2),
                        (hoveredRectangle.y - markerWidth2), (hoveredRectangle.width + markerWidth),
                        (hoveredRectangle.height + markerWidth));

                // do't write into the x-axis units which also contains the
                // selection marker
                if (hoveredBarShape.y + hoveredBarShape.height > devYBottom) {
                    hoveredBarShape.height = devYBottom - hoveredBarShape.y;
                }

                // fill bar background
                gcOverlay.setForeground(colorDark);
                gcOverlay.setBackground(colorBright);

                gcOverlay.fillGradientRectangle(hoveredBarShape.x + 1, hoveredBarShape.y + 1,
                        hoveredBarShape.width - 1, hoveredBarShape.height - 1, true);

                // draw bar border
                gcOverlay.setForeground(colorLine);
                gcOverlay.drawRoundRectangle(hoveredBarShape.x, hoveredBarShape.y, hoveredBarShape.width,
                        hoveredBarShape.height, 4, 4);
            }
        }

        gcOverlay.setAlpha(0xff);
    }

    private void drawSync_460_HoveredLine(final GC gcOverlay) {

        int graphIndex = 0;

        // draw value point marker
        final int devOffsetFill = _pc.convertHorizontalDLUsToPixels(8); // 10;
        final int devOffsetPoint = _pc.convertHorizontalDLUsToPixels(2);// 3;

        gcOverlay.setAntialias(SWT.ON);

        // loop: all graphs
        for (final GraphDrawingData drawingData : _allGraphDrawingData) {

            // draw only line graphs
            if (_chart.getChartDataModel().getChartType() != ChartType.LINE) {
                continue;
            }

            // get the chart data
            final ChartDataYSerie yData = drawingData.getYData();
            final int[][] colorsIndex = yData.getColorsIndex();
            final int[] lineColorIndex = colorsIndex[0];

            /*
             * get hovered rectangle
             */
            // check bounds
            if (_lineDevPositions.size() - 1 < graphIndex) {
                return;
            }

            // check bounds
            final PointLong[] lineDevPositions = _lineDevPositions.get(graphIndex);
            if (lineDevPositions.length - 1 < graphIndex) {
                return;
            }

            // check color index
            if (_hoveredValuePointIndex > lineColorIndex.length - 1) {
                return;
            }

            final RectangleLong[] lineFocusRectangles = _lineFocusRectangles.get(graphIndex);

            final PointLong devPosition = lineDevPositions[_hoveredValuePointIndex];
            final RectangleLong hoveredRectangle = lineFocusRectangles[_hoveredValuePointIndex];

            // check if hovered line positions are set
            if (hoveredRectangle == null || devPosition == null) {
                continue;
            }

            int devX;
            final int devVisibleChartWidth = getDevVisibleChartWidth();
            Color colorLine;

            /*
             * paint the points which are outside of the visible area at the border with gray color
             */
            if (devPosition.x < 0) {

                devX = 0;
                colorLine = Display.getCurrent().getSystemColor(SWT.COLOR_DARK_GRAY);

            } else if (devPosition.x > devVisibleChartWidth) {

                devX = devVisibleChartWidth;
                colorLine = Display.getCurrent().getSystemColor(SWT.COLOR_DARK_GRAY);

            } else {

                devX = (int) devPosition.x;

                // get the colors
                final RGB[] rgbLine = yData.getRgbLine();
                final int colorIndex = lineColorIndex[_hoveredValuePointIndex];

                final RGB rgbLineDef = rgbLine[colorIndex];
                colorLine = getColor(rgbLineDef);
            }

            gcOverlay.setBackground(colorLine);

            gcOverlay.setAlpha(0x40);
            gcOverlay.fillOval(//
                    devX - devOffsetFill + 1, (int) (devPosition.y - devOffsetFill + 1), devOffsetFill * 2,
                    devOffsetFill * 2);

            gcOverlay.setAlpha(0xff);
            gcOverlay.fillOval(//
                    devX - devOffsetPoint + 1, (int) (devPosition.y - devOffsetPoint + 1), devOffsetPoint * 2,
                    devOffsetPoint * 2);

            //         // debug: draw hovered rectangle
            //         gcOverlay.setForeground(Display.getCurrent().getSystemColor(SWT.COLOR_RED));
            //         gcOverlay.drawRectangle(
            //               (int) hoveredRectangle.x,
            //               (int) hoveredRectangle.y,
            //               (int) hoveredRectangle.width,
            //               (int) hoveredRectangle.height);

            // move to next graph
            graphIndex++;
        }

        gcOverlay.setAntialias(SWT.OFF);
    }

    private void drawSync_462_HoveredSegment(final GC gcOverlay) {

        gcOverlay.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_YELLOW));
        gcOverlay.setAlpha(0x10);

        gcOverlay.fillRectangle(//
                _hoveredTitleSegment.devXSegment, 0, _hoveredTitleSegment.devSegmentWidth,
                _hoveredTitleSegment.devYTitle + _hoveredTitleSegment.titleHeight);

        if (chartTitleSegmentConfig.isMultipleSegments) {

            // show hovered segment only when multiple tours are displayed otherwise it is a bit confusing

            for (final GraphDrawingData graphDrawingData : _allGraphDrawingData) {

                final int devYTop = graphDrawingData.getDevYBottom() - graphDrawingData.devGraphHeight;
                final int devYBottom = graphDrawingData.getDevYBottom();

                gcOverlay.fillRectangle(//
                        _hoveredTitleSegment.devXSegment, devYBottom, _hoveredTitleSegment.devSegmentWidth,
                        devYTop - devYBottom);
            }
        }

        // reset alpha
        gcOverlay.setAlpha(0xff);
    }

    private void drawSync_470_CustomOverlay(final GC gcOverlay, final long eventTime) {

        /*
         * custom overlay must be checked 2x because it is fired 2 times before the photo groups are
         * set correctly
         */

        if (_isCustomOverlayInvalid == 99) {

            _isCustomOverlayInvalid = 1;

            return;
        }

        if (_isCustomOverlayInvalid == 1) {

            _isCustomOverlayInvalid = 0;

            // check if a custom overlay needs to be painted
            //         _isOverlayDirty = _chart.getCustomOverlayState(eventTime, _devXMouseMove, _devYMouseMove);
        }

        if (_isOverlayDirty) {

            for (final GraphDrawingData graphDrawingData : _allGraphDrawingData) {

                final ArrayList<IChartLayer> customFgLayers = graphDrawingData.getYData()
                        .getCustomForegroundLayers();

                /**
                 * Draw overlay only when a graph contains overlay layers to ensure that a overlay
                 * is painted not for the "wrong" graphs.
                 * <p>
                 * This feature is used to optimize painting is currently used for tour markers and
                 * photos.
                 */
                if (customFgLayers.size() == 0) {
                    continue;
                }

                for (final Object item : _chart.getChartOverlays()) {
                    if (item instanceof IChartOverlay) {
                        ((IChartOverlay) item).drawOverlay(gcOverlay, graphDrawingData);
                    }
                }
            }
        }
    }

    private void drawSyncBg_999_ErrorMessage(final GC gc) {

        final String errorMessage = _chartComponents.errorMessage;
        if (errorMessage != null) {
            gc.drawText(errorMessage, 0, 10);
        }
    }

    private int getAlphaFill(final boolean isTopGraph) {

        int graphFillingAlpha = (int) (_chart.graphTransparencyFilling * _chart.graphTransparencyAdjustment);

        if (_canChartBeOverlapped && _isChartOverlapped) {

            // reduce opacity for overlapped graphs

            if (isTopGraph) {

                graphFillingAlpha *= 0.6;

            } else {

                graphFillingAlpha *= 0.2;
            }
        }

        // check ranges
        graphFillingAlpha = graphFillingAlpha < 0 ? 0 : graphFillingAlpha > 255 ? 255 : graphFillingAlpha;

        return graphFillingAlpha;
    }

    private int getAlphaLine() {

        int graphLineAlpha = (int) (_chart.graphTransparencyLine * _chart.graphTransparencyAdjustment);

        // check ranges
        graphLineAlpha = graphLineAlpha < 0 ? 0 : graphLineAlpha > 255 ? 255 : graphLineAlpha;

        return graphLineAlpha;
    }

    /**
     * @param rgb
     * @return Returns the color from the color cache, the color must not be disposed this is done
     *         when the cache is disposed
     */
    private Color getColor(final RGB rgb) {

        // !!! this is a performance bottleneck !!!
        //      final String colorKey = rgb.toString();

        final String colorKey = Integer.toString(rgb.hashCode());
        final Color color = _colorCache.get(colorKey);

        if (color == null) {
            return _colorCache.getColor(colorKey, rgb);
        } else {
            return color;
        }
    }

    /**
     * @return Returns the viewport (visible width) of the chart graph
     */
    int getDevVisibleChartWidth() {
        return _chartComponents.getDevVisibleChartWidth();
    }

    /**
     * @return Returns the visible height of the chart graph
     */
    private int getDevVisibleGraphHeight() {
        return _chartComponents.getDevVisibleChartHeight();
    }

    private PointLong getHoveredValueDevPosition() {

        final PointLong[] lineDevPositions = _lineDevPositions.get(0);
        PointLong lineDevPos = lineDevPositions[_hoveredValuePointIndex];

        boolean isAdjusted = false;

        /*
         * it happened, that lineDevPos was null
         */
        if (lineDevPos == null) {

            int lineDevIndex = _hoveredValuePointIndex;

            // check forward
            while (lineDevIndex < lineDevPositions.length - 1) {

                lineDevPos = lineDevPositions[++lineDevIndex];

                if (lineDevPos != null) {
                    _hoveredValuePointIndex = lineDevIndex;
                    isAdjusted = true;
                    break;
                }
            }

            if (lineDevPos == null) {

                lineDevIndex = _hoveredValuePointIndex;

                // check backward
                while (lineDevIndex > 0) {

                    lineDevPos = lineDevPositions[--lineDevIndex];

                    if (lineDevPos != null) {
                        _hoveredValuePointIndex = lineDevIndex;
                        isAdjusted = true;
                        break;
                    }
                }
            }
        }

        if (isAdjusted) {
            // force repaining
            _isOverlayDirty = true;
        }

        return lineDevPos;
    }

    /**
     * @return Returns the index in the data series which is hovered with the mouse or
     *         <code>-1</code> when a value is not hovered.
     */
    int getHoveredValuePointIndex() {
        return _hoveredValuePointIndex;
    }

    /**
     * @return Returns the left slider
     */
    ChartXSlider getLeftSlider() {

        final long posSliderA = _xSliderA.getXXDevSliderLinePos();
        final long posSliderB = _xSliderB.getXXDevSliderLinePos();

        return posSliderA < posSliderB ? _xSliderA : _xSliderB;
    }

    /**
     * @return Returns the right most slider
     */
    ChartXSlider getRightSlider() {

        final long posSliderA = _xSliderA.getXXDevSliderLinePos();
        final long posSliderB = _xSliderB.getXXDevSliderLinePos();

        return posSliderA < posSliderB ? _xSliderB : _xSliderA;
    }

    ChartXSlider getSelectedSlider() {

        final ChartXSlider slider = _selectedXSlider;

        if (slider == null) {
            return getLeftSlider();
        }
        return slider;
    }

    /**
     * @return Returns the x-Data in the drawing data list
     */
    private ChartDataXSerie getXData() {
        if (_allGraphDrawingData.size() == 0) {
            return null;
        } else {
            return _allGraphDrawingData.get(0).getXData();
        }
    }

    private GraphDrawingData getXDrawingData() {
        return _allGraphDrawingData.get(0);
    }

    /**
     * @return Returns the virtual graph image width, this is the width of the graph image when the
     *         full graph would be displayed
     */
    long getXXDevGraphWidth() {
        return _xxDevGraphWidth;
    }

    /**
     * @return When the graph is zoomed, the chart shows only a part of the whole graph in the
     *         viewport. Returns the left border of the viewport.
     */
    long getXXDevViewPortLeftBorder() {
        return _xxDevViewPortLeftBorder;
    }

    double getZoomRatio() {
        return _graphZoomRatio;
    }

    double getZoomRatioLeftBorder() {
        return _zoomRatioLeftBorder;
    }

    private void handleChartResizeForSliders(final boolean isFireEvent) {

        // update the width in the sliders
        final int visibleGraphHeight = getDevVisibleGraphHeight();

        getLeftSlider().handleChartResize(visibleGraphHeight, isFireEvent);
        getRightSlider().handleChartResize(visibleGraphHeight, isFireEvent);
    }

    /**
     * Mouse event occured in the value point tooltip, move the slider and/or hovered line (value
     * point) accordingly.
     * 
     * @param event
     * @param mouseDisplayPosition
     */
    void handleTooltipMouseEvent(final Event event, final Point mouseDisplayPosition) {

        switch (event.type) {
        case SWT.MouseMove:

            final Point controlPos = toControl(mouseDisplayPosition);
            final Rectangle clientRect = getClientArea();

            if (clientRect.contains(controlPos)) {

                _isAutoScroll = false;

                onMouseMove(event.time & 0xFFFFFFFFL, controlPos.x, controlPos.y);

            } else {
                onMouseMoveAxis(new MouseEvent(event));
            }

            break;

        case SWT.MouseEnter:

            // simulate a mouse move to do autoscrolling
            onMouseMoveAxis(new MouseEvent(event));

            break;

        case SWT.MouseExit:

            break;

        case SWT.MouseVerticalWheel:
            onMouseWheel(event, false, false);
            break;

        default:
            break;
        }
    }

    private void hideTooltip() {

        final IHoveredValueListener hoveredListener = _chart.getHoveredListener();
        if (hoveredListener != null) {

            // hide value point tooltip
            hoveredListener.hideTooltip();
        }
    }

    /**
     * check if mouse has moved over a bar
     * 
     * @param devX
     * @param devY
     */
    private boolean isBarHit(final int devX, final int devY) {

        boolean isBarHit = false;

        // loop: all graphs
        for (final GraphDrawingData drawingData : _allGraphDrawingData) {

            final Rectangle[][] barFocusRectangles = drawingData.getBarFocusRectangles();
            if (barFocusRectangles == null) {
                break;
            }

            final int serieLength = barFocusRectangles.length;

            // find the rectangle which is hovered by the mouse
            for (int serieIndex = 0; serieIndex < serieLength; serieIndex++) {

                final Rectangle[] serieRectangles = barFocusRectangles[serieIndex];

                for (int valueIndex = 0; valueIndex < serieRectangles.length; valueIndex++) {

                    final Rectangle barInfoFocus = serieRectangles[valueIndex];

                    // test if the mouse is within a bar focus rectangle
                    if (barInfoFocus != null && barInfoFocus.contains(devX, devY)) {

                        // keep the hovered bar index
                        _hoveredBarSerieIndex = serieIndex;
                        _hoveredBarValueIndex = valueIndex;

                        _hoveredBarToolTip.toolTip_10_Show(devX, 100, serieIndex, valueIndex);

                        isBarHit = true;
                        break;
                    }
                }
                if (isBarHit) {
                    break;
                }
            }

            if (isBarHit) {
                break;
            }
        }

        if (isBarHit == false) {

            _hoveredBarToolTip.toolTip_20_Hide();

            if (_hoveredBarSerieIndex != -1) {

                /*
                 * hide last hovered bar, because the last hovered bar is visible
                 */

                // set status: no bar is hovered
                _hoveredBarSerieIndex = -1;

                // force redraw
                isBarHit = true;
            }
        }

        return isBarHit;
    }

    private boolean isInXSliderSetArea(final int devYMouse) {

        final int devVisibleChartHeight = _chartComponents.getDevVisibleChartHeight();
        final int devSetArea = (int) Math.min(100, devVisibleChartHeight * 0.3);

        Cursor cursor = null;

        if (devYMouse < devSetArea) {

            cursor = _cursorXSliderLeft;

            _isSetXSliderPositionLeft = true;
            _isSetXSliderPositionRight = false;

        } else if (devYMouse > (devVisibleChartHeight - devSetArea)) {

            cursor = _cursorXSliderRight;

            _isSetXSliderPositionLeft = false;
            _isSetXSliderPositionRight = true;
        }

        if (cursor != null) {

            setCursor(cursor);

            return true;

        } else {

            _isSetXSliderPositionLeft = false;
            _isSetXSliderPositionRight = false;

            return false;
        }
    }

    /**
     * @param devXGraph
     * @return Returns <code>true</code> when the synch marker was hit
     */
    private boolean isSynchMarkerHit(final int devXGraph) {

        final ChartDataXSerie xData = getXData();

        if (xData == null) {
            return false;
        }

        final int synchMarkerStartIndex = xData.getSynchMarkerStartIndex();
        final int synchMarkerEndIndex = xData.getSynchMarkerEndIndex();

        if (synchMarkerStartIndex == -1) {
            // synch marker is not set
            return false;
        }

        final double[] xValues = xData.getHighValuesDouble()[0];
        final double scaleX = getXDrawingData().getScaleX();

        final int devXMarkerStart = (int) (xValues[Math.min(synchMarkerStartIndex, xValues.length - 1)] * scaleX
                - _xxDevViewPortLeftBorder);

        final int devXMarkerEnd = (int) (xValues[Math.min(synchMarkerEndIndex, xValues.length - 1)] * scaleX
                - _xxDevViewPortLeftBorder);

        if (devXGraph >= devXMarkerStart && devXGraph <= devXMarkerEnd) {
            return true;
        }

        return false;
    }

    private ChartXSlider isXSliderHit(final int devXMouse, final int devYMouse) {

        ChartXSlider xSlider = null;

        if (_xSliderA.getHitRectangle().contains(devXMouse, devYMouse)) {
            xSlider = _xSliderA;
        } else if (_xSliderB.getHitRectangle().contains(devXMouse, devYMouse)) {
            xSlider = _xSliderB;
        }

        return xSlider;
    }

    /**
     * check if the mouse hit an y-slider and returns the hit slider
     * 
     * @param graphX
     * @param devY
     * @return
     */
    private ChartYSlider isYSliderHit(final int graphX, final int devY) {

        if (_ySliders == null) {
            return null;
        }

        final boolean isGraphOverlapped = _chartDrawingData.chartDataModel.isGraphOverlapped();
        final boolean isStackedChart = !isGraphOverlapped;

        int graphNo = 0;
        final int lastGraph = _ySliders.size();

        for (final ChartYSlider ySlider : _ySliders) {

            graphNo++;

            // there are 2 y-sliders for each graph
            final boolean isLastGraph = graphNo == lastGraph || graphNo == lastGraph - 1;
            final boolean isLastOverlappedGraph = isGraphOverlapped && isLastGraph;

            final boolean canDoHitChecking = !_canChartBeOverlapped
                    || (_canChartBeOverlapped && (isStackedChart || isLastOverlappedGraph));

            if (canDoHitChecking) {

                if (ySlider.getHitRectangle().contains(graphX, devY)) {

                    _hitYSlider = ySlider;
                    return ySlider;
                }
            }
        }

        // hide previously hitted y-slider
        if (_hitYSlider != null) {

            // redraw the sliders to hide the labels
            _hitYSlider = null;
            _isSliderDirty = true;
            redraw();
        }

        return null;
    }

    /**
     * Move left slider to the mouse down position
     */
    void moveLeftSliderHere() {

        final ChartXSlider leftSlider = getLeftSlider();
        final long xxDevLeftPosition = _xxDevViewPortLeftBorder + _devXMouseDown;

        setXSliderValue_FromHoveredValuePoint(leftSlider);
        leftSlider.moveToXXDevPosition(xxDevLeftPosition, true, true, false);

        setZoomInPosition();

        _isSliderDirty = true;
        redraw();
    }

    /**
     * Move right slider to the mouse down position
     */
    void moveRightSliderHere() {

        final ChartXSlider rightSlider = getRightSlider();
        final long xxDevRightPosition = _xxDevViewPortLeftBorder + _devXMouseDown;

        setXSliderValue_FromHoveredValuePoint(rightSlider);
        rightSlider.moveToXXDevPosition(xxDevRightPosition, true, true, false);

        setZoomInPosition();

        _isSliderDirty = true;
        redraw();
    }

    private void moveSlidersToBorder() {

        if (_canAutoMoveSliders == false) {
            return;
        }

        moveSlidersToBorderWithoutCheck();
    }

    void moveSlidersToBorderWithoutCheck() {

        /*
         * get the sliders first before they are moved
         */
        final ChartXSlider leftSlider = getLeftSlider();
        final ChartXSlider rightSlider = getRightSlider();

        /*
         * adjust left slider
         */
        final long xxDevLeftPosition = _xxDevViewPortLeftBorder + 2;

        setXSliderValue_FromHoveredValuePoint(leftSlider);
        leftSlider.moveToXXDevPosition(xxDevLeftPosition, true, true, false);

        /*
         * adjust right slider
         */
        final long xxDevRightPosition = _xxDevViewPortLeftBorder + getDevVisibleChartWidth() - 2;

        setXSliderValue_FromHoveredValuePoint(rightSlider);
        rightSlider.moveToXXDevPosition(xxDevRightPosition, true, true, false);

        _isSliderDirty = true;
        redraw();
    }

    /**
     * Move slider to the valueIndex position.
     * 
     * @param xSlider
     * @param valueIndex
     * @param isCenterSliderPosition
     * @param isMoveChartToShowSlider
     * @param isSetKeyCenterZoomPosition
     * @param isBorderOffset
     */
    void moveXSlider(final ChartXSlider xSlider, int valueIndex, final boolean isCenterSliderPosition,
            final boolean isMoveChartToShowSlider, final boolean isFireEvent,
            final boolean isSetKeyCenterZoomPosition) {

        final ChartDataXSerie xData = getXData();

        if (xData == null) {
            return;
        }

        final double[] xValues = xData.getHighValuesDouble()[0];
        final int xValueLastIndex = xValues.length - 1;

        // adjust the slider index to the array bounds
        valueIndex = valueIndex < 0 ? 0 //
                : valueIndex > xValueLastIndex ? xValueLastIndex //
                        : valueIndex;

        final double xValue = xValues[valueIndex];
        final double lastXValue = xValues[xValueLastIndex];
        final double xxDevLinePos = _xxDevGraphWidth * xValue / lastXValue;

        xSlider.setValueIndex(valueIndex);
        xSlider.moveToXXDevPosition(xxDevLinePos, true, true, false, isFireEvent);

        _zoomRatioCenterKey = isSetKeyCenterZoomPosition ? xxDevLinePos / _xxDevGraphWidth : 0;

        setChartPosition(xSlider, isCenterSliderPosition, isMoveChartToShowSlider, 15);

        _isSliderDirty = true;
    }

    /**
     * Move the slider to a new position
     * 
     * @param xSlider
     *            Current slider
     * @param xxDevSliderLinePos
     *            x coordinate for the slider line within the graph, this can be outside of the
     *            visible graph
     */
    private void moveXSlider(final ChartXSlider xSlider, final long devXSliderLinePos) {

        long xxDevSliderLinePos = _xxDevViewPortLeftBorder + devXSliderLinePos;

        /*
         * adjust the line position the the min/max width of the graph image
         */
        xxDevSliderLinePos = Math.min(_xxDevGraphWidth, Math.max(0, xxDevSliderLinePos));

        // set new slider line position
        setXSliderValue_FromHoveredValuePoint(xSlider);
        xSlider.moveToXXDevPosition(xxDevSliderLinePos, true, true, false);
    }

    /**
     * Dispose event handler
     */
    private void onDispose() {

        // dispose resources
        _cursorResizeLeftRight = Util.disposeResource(_cursorResizeLeftRight);
        _cursorResizeTopDown = Util.disposeResource(_cursorResizeTopDown);
        _cursorDragged = Util.disposeResource(_cursorDragged);
        _cursorArrow = Util.disposeResource(_cursorArrow);
        _cursorModeSlider = Util.disposeResource(_cursorModeSlider);
        _cursorModeZoom = Util.disposeResource(_cursorModeZoom);
        _cursorModeZoomMove = Util.disposeResource(_cursorModeZoomMove);
        _cursorDragXSlider_ModeZoom = Util.disposeResource(_cursorDragXSlider_ModeZoom);
        _cursorDragXSlider_ModeSlider = Util.disposeResource(_cursorDragXSlider_ModeSlider);
        _cursorHoverXSlider = Util.disposeResource(_cursorHoverXSlider);

        _cursorMove1x = Util.disposeResource(_cursorMove1x);
        _cursorMove2x = Util.disposeResource(_cursorMove2x);
        _cursorMove3x = Util.disposeResource(_cursorMove3x);
        _cursorMove4x = Util.disposeResource(_cursorMove4x);
        _cursorMove5x = Util.disposeResource(_cursorMove5x);

        _cursorXSliderLeft = Util.disposeResource(_cursorXSliderLeft);
        _cursorXSliderRight = Util.disposeResource(_cursorXSliderRight);

        _chartImage_20_Chart = Util.disposeResource(_chartImage_20_Chart);
        _chartImage_10_Graphs = Util.disposeResource(_chartImage_10_Graphs);
        _chartImage_40_Overlay = Util.disposeResource(_chartImage_40_Overlay);
        _chartImage_30_Custom = Util.disposeResource(_chartImage_30_Custom);

        _gridColor = Util.disposeResource(_gridColor);
        _gridColorMajor = Util.disposeResource(_gridColorMajor);

        _hoveredBarToolTip.dispose();

        _colorCache.dispose();
    }

    private void onKeyDown(final Event event) {

        if ((_chart.onExternalKeyDown(event)).isWorked) {

            // nothing more to do

        } else {

            final ChartType chartType = _chart.getChartDataModel().getChartType();

            if (chartType == ChartType.BAR) {

                _chartComponents.selectBarItem(event);

            } else if (chartType == ChartType.LINE) {

                // reduce zoom factor when accelerator key <Alt> is pressed
                double accelerator = 1.0;
                final boolean isAlt = (event.stateMask & SWT.ALT) != 0;
                if (isAlt) {
                    accelerator = 1.01;
                }

                switch (event.character) {
                case '+':

                    // overwrite zoom ratio
                    if (_zoomRatioCenterKey != 0) {
                        _zoomRatioCenter = _zoomRatioCenterKey;
                    }

                    _chart.onExecuteZoomIn(accelerator);

                    break;

                case '-':

                    // overwrite zoom ratio
                    if (_zoomRatioCenterKey != 0) {
                        _zoomRatioCenter = _zoomRatioCenterKey;
                    }
                    _chart.onExecuteZoomOut(true, accelerator);

                    break;

                default:
                    onKeyDown_MoveXSlider(event);
                }
            }
        }
    }

    /**
     * move the x-slider with the keyboard
     * 
     * @param event
     */
    private void onKeyDown_MoveXSlider(final Event event) {

        final int keyCode = event.keyCode;

        /*
         * keyboard events behaves different than the mouse event, shift & ctrl can be set in both
         * event fields
         */
        boolean isShift = (event.stateMask & SWT.SHIFT) != 0 || (keyCode & SWT.SHIFT) != 0;
        boolean isCtrl = (event.stateMask & SWT.CTRL) != 0 || (keyCode & SWT.CTRL) != 0;

        // ensure a slider is selected
        if (_selectedXSlider == null) {
            final ChartXSlider leftSlider = getLeftSlider();
            if (leftSlider != null) {
                // set default slider
                _selectedXSlider = leftSlider;
            } else {
                return;
            }
        }

        // toggle selected slider with the ctrl key, key changed in 15.10 to select multiple segments which shift
        if (isCtrl && isShift == false) {
            _selectedXSlider = _selectedXSlider == _xSliderA ? _xSliderB : _xSliderA;
            _isSliderDirty = true;
            redraw();

            return;
        }

        // accelerate with page up/down
        if (keyCode == SWT.PAGE_UP || keyCode == SWT.PAGE_DOWN) {
            isCtrl = true;
            isShift = true;
        }

        // accelerate slider move speed depending on shift/ctrl key
        int valueIndexDiff = isCtrl ? 10 : 1;
        valueIndexDiff *= isShift ? 10 : 1;

        int valueIndex = _selectedXSlider.getValuesIndex();
        final double[] xValues = getXData().getHighValuesDouble()[0];

        boolean isMoveSlider = false;

        switch (keyCode) {
        case SWT.PAGE_DOWN:
        case SWT.ARROW_RIGHT:

            valueIndex += valueIndexDiff;

            // wrap around
            if (valueIndex >= xValues.length) {
                valueIndex = 0;
            }

            isMoveSlider = true;

            break;

        case SWT.PAGE_UP:
        case SWT.ARROW_LEFT:

            valueIndex -= valueIndexDiff;

            // wrap around
            if (valueIndex < 0) {
                valueIndex = xValues.length - 1;
            }

            isMoveSlider = true;

            break;

        case SWT.HOME:

            valueIndex = 0;

            isMoveSlider = true;

            break;

        case SWT.END:

            valueIndex = xValues.length - 1;

            isMoveSlider = true;

            break;
        }

        if (isMoveSlider) {

            moveXSlider(_selectedXSlider, valueIndex, false, true, true, false);

            redraw();
            setCursorStyle(event.y);
        }
    }

    void onMouseDoubleClick(final MouseEvent e) {

        final long eventTime = e.time & 0xFFFFFFFFL;
        final int devXMouse = e.x;
        final int devYMouse = e.y;

        // stop dragging the x-slider
        _xSliderDragged = null;

        @SuppressWarnings("unused")
        ChartMouseEvent mouseEvent;

        if ((mouseEvent = _chart.onExternalMouseDoubleClick(eventTime, devXMouse, devYMouse)).isWorked) {

            //         setChartCursor(mouseEvent.cursor);

            //         _isOverlayDirty = true;
            //         isRedraw = true;
            //
            //         canShowHoveredTooltip = true;

        } else if (_hoveredBarSerieIndex != -1) {

            /*
             * execute the action which is defined when a bar is selected with the left mouse button
             */

            _chart.fireChartDoubleClick(_hoveredBarSerieIndex, _hoveredBarValueIndex);

        } else {

            if ((e.stateMask & SWT.CONTROL) != 0) {

                // toggle mouse mode

                if (_chart.getMouseMode().equals(Chart.MOUSE_MODE_SLIDER)) {

                    // switch to mouse zoom mode
                    _chart.setMouseMode(false);

                } else {

                    // switch to mouse slider mode
                    _chart.setMouseMode(true);
                }

            } else {

                if (_chart.getMouseMode().equals(Chart.MOUSE_MODE_SLIDER)) {

                    // switch to mouse zoom mode
                    _chart.setMouseMode(false);
                }

                // mouse mode: zoom chart

                /*
                 * set position where the double click occured, this position will be used when the
                 * chart is zoomed
                 */
                final double xxDevMousePosInChart = _xxDevViewPortLeftBorder + e.x;
                _zoomRatioCenter = xxDevMousePosInChart / _xxDevGraphWidth;

                zoomInWithMouse(Integer.MIN_VALUE, 1.0);
            }
        }
    }

    /**
     * Mouse down event handler
     * 
     * @param event
     */
    private void onMouseDown(final MouseEvent event) {

        hideTooltip();

        // zoom out to show the whole chart with the button on the left side
        if (event.button == 4) {
            _chart.onExecuteZoomFitGraph();
            return;
        }

        final int devXMouse = event.x;
        final int devYMouse = event.y;

        final boolean isShift = (event.stateMask & SWT.SHIFT) != 0;
        final boolean isCtrl = (event.stateMask & SWT.CTRL) != 0;

        _devXMouseDown = devXMouse;
        _devYMouseDown = devYMouse;

        // show slider context menu
        if (event.button != 1) {

            // stop dragging the x-slider
            _xSliderDragged = null;

            // prevent that after the context menu is closed with a mouse click, the x-slider starts dragging
            _isSetXSliderPositionLeft = false;
            _isSetXSliderPositionRight = false;

            if (event.button == 3) {

                // right button is pressed

                // this was disabled because it is not working on OSX, partly it works,
                // propably depending on the mouse: OSX mouse or Logitech mouse
                //            computeSliderForContextMenu(devXMouse, devYMouse);
            }
            return;
        }

        // use external mouse event listener
        final ChartMouseEvent externalMouseEvent = _chart.onExternalMouseDown(//
                event.time & 0xFFFFFFFFL, devXMouse, devYMouse, event.stateMask);

        if (externalMouseEvent.isWorked) {

            // stop dragging because x-sliders are selected by selecting a tour segmenter segment
            //         if (externalMouseEvent.isDisableSliderDragging) {
            //            _xSliderDragged = null;
            //         }

            setChartCursor(externalMouseEvent.cursor);
            return;
        }

        if (_xSliderDragged != null) {

            // x-slider is dragged

            // keep x-slider
            final ChartXSlider xSlider = _xSliderDragged;

            // stop dragging the slider
            _xSliderDragged = null;

            // set mouse zoom double click position
            final double xxDevMousePosInChart = _xxDevViewPortLeftBorder + devXMouse;
            _zoomRatioCenter = xxDevMousePosInChart / _xxDevGraphWidth;

            /*
             * make sure that the slider is exactly positioned where the value is displayed in the
             * graph
             */
            moveXSlider(xSlider, _hoveredValuePointIndex, false, true, true, false);

            _isSliderDirty = true;
            redraw();

        } else {

            // check if a x-slider was hit
            _xSliderDragged = null;
            if (_xSliderA.getHitRectangle().contains(devXMouse, devYMouse)) {
                _xSliderDragged = _xSliderA;
            } else if (_xSliderB.getHitRectangle().contains(devXMouse, devYMouse)) {
                _xSliderDragged = _xSliderB;
            }

            if (_xSliderDragged != null) {

                // x-slider is dragged, stop dragging

                _xSliderOnTop = _xSliderDragged;
                _xSliderOnBottom = _xSliderOnTop == _xSliderA ? _xSliderB : _xSliderA;

                // set the hit offset for the mouse click
                _xSliderDragged.setDevXClickOffset(devXMouse - _xxDevViewPortLeftBorder);

                // the hit x-slider is now the selected x-slider
                _selectedXSlider = _xSliderDragged;
                _isSelectionVisible = true;
                _isSliderDirty = true;

                redraw();

            } else if (_ySliderDragged != null) {

                // y-slider is dragged, stop dragging

                adjustYSlider();

            } else if ((_ySliderDragged = isYSliderHit(devXMouse, devYMouse)) != null) {

                // start y-slider dragging

                _ySliderDragged.devYClickOffset = (int) (devYMouse - _ySliderDragged.getHitRectangle().y);

            } else if (_hoveredBarSerieIndex != -1) {

                actionSelectBars();

            } else if (_chart._draggingListenerXMarker != null && isSynchMarkerHit(devXMouse)) {

                /*
                 * start to move the x-marker, when a dragging listener and the x-marker was hit
                 */

                _isXMarkerMoved = getXData().getSynchMarkerStartIndex() != -1;

                if (_isXMarkerMoved) {

                    _devXMarkerDraggedStartPos = devXMouse;
                    _devXMarkerDraggedPos = devXMouse;
                    _xMarkerValueDiff = _chart._draggingListenerXMarker.getXMarkerValueDiff();

                    _isSliderDirty = true;
                    redraw();
                }

            } else if (_isXSliderVisible //
                    //
                    // x-slider is NOT dragged
                    && _xSliderDragged == null
                    //
                    && (isShift || isCtrl || _isSetXSliderPositionLeft || _isSetXSliderPositionRight)) {

                // position the x-slider and start dragging it

                if (_isSetXSliderPositionLeft) {
                    _xSliderDragged = getLeftSlider();
                } else if (_isSetXSliderPositionRight) {
                    _xSliderDragged = getRightSlider();
                } else if (isCtrl) {
                    // ctrl is pressed -> left slider
                    _xSliderDragged = getRightSlider();
                } else {
                    // shift is pressed -> right slider
                    _xSliderDragged = getLeftSlider();
                }

                _xSliderOnTop = _xSliderDragged;
                _xSliderOnBottom = _xSliderOnTop == _xSliderA ? _xSliderB : _xSliderA;

                // the left x-slider is now the selected x-slider
                _selectedXSlider = _xSliderDragged;
                _isSelectionVisible = true;

                /*
                 * move the left slider to the mouse down position
                 */

                _xSliderDragged.setDevXClickOffset(devXMouse - _xxDevViewPortLeftBorder);

                // keep position of the slider line
                final int devXSliderLinePos = devXMouse;
                _devXDraggedXSliderLine = devXSliderLinePos;

                moveXSlider(_xSliderDragged, devXSliderLinePos);

                _isSliderDirty = true;
                redraw();

            } else if (_graphZoomRatio > 1) {

                // start dragging the chart

                /*
                 * to prevent flickering with the double click event, dragged started is used
                 */
                _isChartDraggedStarted = true;

                _draggedChartStartPos = new Point(event.x, event.y);

                /*
                 * set also the move position because when changing the data model, the old position
                 * will be used and the chart is painted on the wrong position on mouse down
                 */
                _draggedChartDraggedPos = _draggedChartStartPos;
            }
        }

        setCursorStyle(devYMouse);
    }

    /**
     * Mouse down event in the x-axis area
     * 
     * @param event
     */
    void onMouseDownAxis(final MouseEvent event) {

        hideTooltip();

        if (_xSliderDragged != null) {

            // stop dragging the slider
            _xSliderDragged = null;

            doAutoZoomToXSliders();
        }
    }

    private void onMouseEnter(final MouseEvent mouseEvent) {

        if (_ySliderDragged != null) {

            _hitYSlider = _ySliderDragged;

            _isSliderDirty = true;
            redraw();
        }

    }

    void onMouseEnterAxis(final MouseEvent event) {

        // simulate a mouse move to do autoscrolling
        onMouseMoveAxis(event);
    }

    /**
     * Mouse exit event handler
     * 
     * @param event
     */
    private void onMouseExit(final MouseEvent event) {

        _chart.onExternalMouseExit(event.time);

        _hoveredBarToolTip.toolTip_20_Hide();

        boolean isRedraw = false;

        if (_isAutoScroll) {

            // stop autoscrolling
            _isAutoScroll = false;

        } else if (_xSliderDragged == null) {

            // hide the y-slider labels
            if (_hitYSlider != null) {
                _hitYSlider = null;

                _isSliderDirty = true;

                isRedraw = true;
            }
        }

        if (_mouseOverXSlider != null) {
            // mouse left the x-slider
            _mouseOverXSlider = null;
            _isSliderDirty = true;

            isRedraw = true;
        }

        setCursorStyle(event.y);

        if (isRedraw) {
            redraw();
        }
    }

    /**
     * @param mouseEvent
     * @return Returns <code>true</code> when the mouse event was handled.
     */
    boolean onMouseExitAxis(final MouseEvent mouseEvent) {

        if (_isAutoScroll) {

            // stop autoscrolling with x-slider
            _isAutoScroll = false;

            // stop autoscrolling without x-slider
            _devXAutoScrollMousePosition = Integer.MIN_VALUE;

            // hide move/scroll cursor
            if (mouseEvent.widget instanceof ChartComponentAxis) {
                ((ChartComponentAxis) mouseEvent.widget).setCursor(null);
            }

            return true;
        }

        return false;
    }

    /**
     * Mouse move event handler
     * 
     * @param eventTime
     * @param eventTime
     */
    private void onMouseMove(final long eventTime, final int devXMouse, final int devYMouse) {

        _devXMouseMove = devXMouse;
        _devYMouseMove = devYMouse;

        boolean isRedraw = false;
        boolean canShowHoveredValueTooltip = false;

        /**
         * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!<br>
         * THE FEATURE onExternalMouseMoveImportant IS NOT YET FULLY IMPLEMENTED
         * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!<br>
         */
        final ChartMouseEvent externalMouseMoveEvent = _chart.onExternalMouseMoveImportant(//
                eventTime, devXMouse, devYMouse);
        if (externalMouseMoveEvent.isWorked) {

            setChartCursor(externalMouseMoveEvent.cursor);

            _isOverlayDirty = true;
            isRedraw = true;

            canShowHoveredValueTooltip = true;
        }

        if (_isXSliderVisible && _xSliderDragged != null) {

            // x-slider is dragged

            canShowHoveredValueTooltip = true;

            // keep position of the slider line
            _devXDraggedXSliderLine = devXMouse;

            /*
             * when the x-slider is outside of the visual graph in horizontal direction, the graph
             * can be scrolled with the mouse
             */
            final int devVisibleChartWidth = getDevVisibleChartWidth();
            if (_devXDraggedXSliderLine > -1 && _devXDraggedXSliderLine < devVisibleChartWidth) {

                // slider is within the visible area, autoscrolling is NOT done

                // autoscroll could be active, disable it
                _isAutoScroll = false;

                moveXSlider(_xSliderDragged, devXMouse);

                _isSliderDirty = true;
                isRedraw = true;

            } else {

                /*
                 * slider is outside the visible area, auto scroll the slider and graph when this is
                 * not yet done
                 */
                if (_isAutoScroll == false) {
                    doAutoScroll(eventTime);
                }
            }

        } else if (_isChartDraggedStarted || _isChartDragged) {

            // chart is dragged with the mouse

            _isChartDraggedStarted = false;
            _isChartDragged = true;

            _draggedChartDraggedPos = new Point(devXMouse, devYMouse);

            isRedraw = true;

        } else if (_isYSliderVisible && _ySliderDragged != null) {

            // y-slider is dragged

            final int devYSliderLine = devYMouse - _ySliderDragged.devYClickOffset
                    + ChartYSlider.halfSliderHitLineHeight;

            _ySliderDragged.setDevYSliderLine(devYSliderLine);
            _ySliderGraphX = devXMouse;

            _isSliderDirty = true;
            isRedraw = true;

        } else if (_isXMarkerMoved) {

            // X-Marker is dragged

            _devXMarkerDraggedPos = devXMouse;

            _isSliderDirty = true;
            isRedraw = true;

        } else {

            ChartXSlider xSlider;

            if (externalMouseMoveEvent.isWorked == false) {

                final ChartMouseEvent externalMouseEvent = _chart.onExternalMouseMove(eventTime, devXMouse,
                        devYMouse);

                if (externalMouseEvent.isWorked) {

                    setChartCursor(externalMouseEvent.cursor);

                    _isOverlayDirty = true;
                    isRedraw = true;

                    canShowHoveredValueTooltip = true;

                } else if (_isXSliderVisible && (xSlider = isXSliderHit(devXMouse, devYMouse)) != null) {

                    // mouse is over an x-slider

                    if (_mouseOverXSlider != xSlider) {

                        // a new x-slider is hovered

                        _mouseOverXSlider = xSlider;

                        // hide the y-slider
                        _hitYSlider = null;

                        _isSliderDirty = true;
                        isRedraw = true;
                    }

                    // set cursor
                    setCursor(_cursorResizeLeftRight);

                    canShowHoveredValueTooltip = true;

                } else if (_mouseOverXSlider != null) {

                    // mouse has left the x-slider

                    _mouseOverXSlider = null;
                    _isSliderDirty = true;
                    isRedraw = true;

                    canShowHoveredValueTooltip = true;

                } else if (_isYSliderVisible && isYSliderHit(devXMouse, devYMouse) != null) {

                    // cursor is within a y-slider

                    setCursor(_cursorResizeTopDown);

                    // show the y-slider labels
                    _ySliderGraphX = devXMouse;

                    _isSliderDirty = true;
                    isRedraw = true;

                    canShowHoveredValueTooltip = true;

                } else if (_chart._draggingListenerXMarker != null && isSynchMarkerHit(devXMouse)) {

                    setCursor(_cursorDragged);

                } else if (_isXSliderVisible && isInXSliderSetArea(devYMouse)) {

                    // cursor is already set

                    canShowHoveredValueTooltip = true;

                } else if (isBarHit(devXMouse, devYMouse)) {

                    _isHoveredBarDirty = true;
                    isRedraw = true;

                    setCursorStyle(devYMouse);

                } else {

                    canShowHoveredValueTooltip = true;

                    setCursorStyle(devYMouse);
                }
            }
        }

        final IHoveredValueListener hoveredListener = _chart._hoveredListener;

        if (_isHoveredLineVisible || hoveredListener != null) {

            setHoveredLineValue();

            if (_hoveredValuePointIndex != -1) {

                final PointLong devHoveredValueDevPosition = getHoveredValueDevPosition();

                if (_isHoveredLineVisible) {

                    if (valuePointToolTip != null) {

                        valuePointToolTip.setValueIndex(_hoveredValuePointIndex, _devXMouseMove, _devYMouseMove,
                                devHoveredValueDevPosition, _graphZoomRatio);
                    }

                    /*
                     * this redraw is necessary otherwise a hovered photo displayed as none hovered
                     * when mouse is not hovering a photo
                     */
                    isRedraw = true;
                }

                if (hoveredListener != null && canShowHoveredValueTooltip) {

                    hoveredListener.hoveredValue(eventTime, _devXMouseMove, _devYMouseMove, _hoveredValuePointIndex,
                            devHoveredValueDevPosition);
                }
            }
        }

        if (isRedraw) {
            redraw();
        }
    }

    /**
     * @param mouseEvent
     * @return Returns <code>true</code> when the mouse event was been handled.
     */
    boolean onMouseMoveAxis(final MouseEvent mouseEvent) {

        ChartComponentAxis axisComponent = null;
        int axisWidth = 0;

        int devXMouse = mouseEvent.x;
        int devYMouse = mouseEvent.y;
        final Widget mouseWidget = mouseEvent.widget;

        final ChartComponentAxis axisLeft = _chartComponents.getAxisLeft();
        final ChartComponentAxis axisRight = _chartComponents.getAxisRight();

        boolean isMouseFromRightToolTip = false;
        boolean isMouseFromLeftToolTip = false;

        if (mouseWidget instanceof ChartComponentAxis) {

            axisComponent = (ChartComponentAxis) mouseWidget;
            axisWidth = axisComponent.getAxisClientArea().width;

        } else if (valuePointToolTip != null) {

            // check if the event widget is from the tooltip

            // get tooltip shell
            final Shell ttShell = valuePointToolTip.getToolTipShell();
            if (ttShell != null) {

                if (mouseWidget instanceof Control) {
                    final Control control = (Control) mouseWidget;

                    if (control.getShell() == ttShell) {

                        /*
                         * this event is from the value point tooltip, the control is the tooltip
                         */

                        final Point screenTTMouse = control.toDisplay(devXMouse, devYMouse);

                        final Point leftAxisScreen = axisLeft.toDisplay(0, 0);
                        final Point leftAxisSize = axisLeft.getSize();

                        final Rectangle leftAxisRect = new Rectangle(leftAxisScreen.x, leftAxisScreen.y,
                                leftAxisSize.x, leftAxisSize.y);

                        if (leftAxisRect.contains(screenTTMouse)) {

                            // mouse is moved above the left axis

                            final Point devLeftAxis = axisLeft.toControl(screenTTMouse);
                            devXMouse = devLeftAxis.x;
                            devYMouse = devLeftAxis.y;
                            axisComponent = axisLeft;
                            axisWidth = leftAxisSize.x;

                            isMouseFromLeftToolTip = true;

                        } else {

                            final Point rightAxisScreen = axisRight.toDisplay(0, 0);
                            final Point rightAxisSize = axisRight.getSize();

                            final Rectangle rightAxisRect = new Rectangle(rightAxisScreen.x, rightAxisScreen.y,
                                    rightAxisSize.x, rightAxisSize.y);

                            if (rightAxisRect.contains(screenTTMouse)) {

                                // mouse is moved above the right axis

                                final Point devRightAxis = axisRight.toControl(screenTTMouse);
                                devXMouse = devRightAxis.x;
                                devYMouse = devRightAxis.y;
                                axisComponent = axisRight;
                                axisWidth = rightAxisSize.x;

                                isMouseFromRightToolTip = true;
                            }
                        }
                    }
                }
            }
        }

        final boolean isLeftAxis = axisComponent == axisLeft;
        final int marginBottom = _chartComponents.getMarginBottomStartingFromTop();

        if ((axisWidth == 0 || axisComponent == null) //

                // chart is not zoomed
                || _graphZoomRatio == 1

                        /*
                         * ensure the mouse is moved from the graph from the tooltip or is moved
                         * over the tooltip when it's above the y-axis
                         */
                        && isMouseFromLeftToolTip == false && isMouseFromRightToolTip == false

                // mouse is above the bottom margin
                || devYMouse < marginBottom
        //
        ) {

            // disable autoscroll
            _isAutoScroll = false;

            if (axisComponent != null) {
                axisComponent.setCursor(null);
            }

            return false;
        }

        final Cursor cursor;

        if (_isXSliderVisible && _xSliderDragged != null) {

            // x-slider is dragged, do autoscroll the graph with the mouse

            if (isLeftAxis) {

                // left x-axis

                _devXDraggedXSliderLine = -axisWidth + devXMouse;

                cursor = //
                        _devXDraggedXSliderLine < _leftAccelerator[0][0] ? _cursorMove5x : //
                                _devXDraggedXSliderLine < _leftAccelerator[1][0] ? _cursorMove4x : //
                                        _devXDraggedXSliderLine < _leftAccelerator[2][0] ? _cursorMove3x : //
                                                _devXDraggedXSliderLine < _leftAccelerator[3][0] ? _cursorMove2x : //
                                                        _cursorMove1x;

            } else {

                // right x-axis

                _devXDraggedXSliderLine = getDevVisibleChartWidth() + devXMouse;

                cursor = //
                        devXMouse < _rightAccelerator[0][0] ? _cursorMove1x : //
                                devXMouse < _rightAccelerator[1][0] ? _cursorMove2x : //
                                        devXMouse < _rightAccelerator[2][0] ? _cursorMove3x : //
                                                devXMouse < _rightAccelerator[3][0] ? _cursorMove4x : //
                                                        _cursorMove5x;
            }

        } else {

            // do autoscroll the graph with the moved mouse

            // set mouse position and do autoscrolling

            if (isLeftAxis) {

                // left x-axis

                _devXAutoScrollMousePosition = -axisWidth + devXMouse;

                cursor = //
                        _devXAutoScrollMousePosition < _leftAccelerator[0][0] ? _cursorMove5x : //
                                _devXAutoScrollMousePosition < _leftAccelerator[1][0] ? _cursorMove4x : //
                                        _devXAutoScrollMousePosition < _leftAccelerator[2][0] ? _cursorMove3x : //
                                                _devXAutoScrollMousePosition < _leftAccelerator[3][0]
                                                        ? _cursorMove2x
                                                        : //
                                                        _cursorMove1x;

            } else {

                // right x-axis

                _devXAutoScrollMousePosition = getDevVisibleChartWidth() + devXMouse;

                cursor = //
                        devXMouse < _rightAccelerator[0][0] ? _cursorMove1x : //
                                devXMouse < _rightAccelerator[1][0] ? _cursorMove2x : //
                                        devXMouse < _rightAccelerator[2][0] ? _cursorMove3x : //
                                                devXMouse < _rightAccelerator[3][0] ? _cursorMove4x : //
                                                        _cursorMove5x;
            }
        }

        axisComponent.setCursor(cursor);

        if (_isAutoScroll == false) {
            // start scrolling when not yet done
            doAutoScroll(mouseEvent.time & 0xFFFFFFFFL);
        }

        return true;
    }

    /**
     * Mouse up event handler
     * 
     * @param event
     */
    private void onMouseUp(final MouseEvent event) {

        final int devXMouse = event.x;
        final int devYMouse = event.y;

        ChartMouseEvent mouseEvent;

        if (_isAutoScroll) {

            // stop auto scolling
            _isAutoScroll = false;

            /*
             * make sure that the sliders are at the border of the visible area are at the border
             */
            if (_devXDraggedXSliderLine < 0) {
                moveXSlider(_xSliderDragged, 0);
            } else {
                final int devVisibleChartWidth = getDevVisibleChartWidth();
                if (_devXDraggedXSliderLine > devVisibleChartWidth - 1) {
                    moveXSlider(_xSliderDragged, devVisibleChartWidth - 1);
                }
            }

            // disable dragging
            _xSliderDragged = null;

            // redraw slider
            _isSliderDirty = true;
            redraw();

        } else if (_ySliderDragged != null) {

            // y-slider is dragged, stop dragging

            adjustYSlider();

        } else if (_isXMarkerMoved) {

            _isXMarkerMoved = false;

            _isSliderDirty = true;
            redraw();

            // call the listener which is registered for dragged x-marker
            if (_chart._draggingListenerXMarker != null) {
                _chart._draggingListenerXMarker.xMarkerMoved(_movedXMarkerStartValueIndex,
                        _movedXMarkerEndValueIndex);
            }

        } else if (_isChartDragged || _isChartDraggedStarted) {

            // chart was moved with the mouse

            _isChartDragged = false;
            _isChartDraggedStarted = false;

            updateDraggedChart(_draggedChartDraggedPos.x - _draggedChartStartPos.x);

        } else if ((mouseEvent = _chart.onExternalMouseUp(event.time & 0xFFFFFFFFL, devXMouse,
                devYMouse)).isWorked) {

            setChartCursor(mouseEvent.cursor);
            return;
        }

        setCursorStyle(devYMouse);
    }

    void onMouseWheel(final Event event, final boolean isEventFromAxis, final boolean isLeftAxis) {

        if (_isGraphVisible == false) {
            return;
        }

        if (_chart.getMouseMode().equals(Chart.MOUSE_MODE_SLIDER)) {

            // mouse mode: move slider

            /**
             * when a slider in a graph is moved with the mouse wheel the direction is the same as
             * when the mouse wheel is scrolling in the tour editor:
             * <p>
             * wheel up -> tour editor up
             */
            if (event.count < 0) {
                event.keyCode |= SWT.ARROW_RIGHT;
            } else {
                event.keyCode |= SWT.ARROW_LEFT;
            }

            /*
             * set focus when the mouse is over the chart and the mousewheel is scrolled, this will
             * also activate the part with the chart component
             */
            if (isFocusControl() == false) {
                forceFocus();
            }

            onKeyDown(event);

            if (_canAutoZoomToSlider) {

                /*
                 * zoom the chart
                 */
                Display.getCurrent().asyncExec(new Runnable() {
                    @Override
                    public void run() {

                        zoomInWithSlider();
                        _chartComponents.onResize();
                    }
                });
            }

        } else {

            // mouse mode: scroll/zoom chart

            final boolean isCtrl = (event.stateMask & SWT.CONTROL) != 0;
            final boolean isShift = (event.stateMask & SWT.SHIFT) != 0;

            if (isCtrl || isShift) {

                // scroll the chart

                int devXDiff = 0;
                if (event.count < 0) {
                    devXDiff = -10;
                } else {
                    devXDiff = 10;
                }

                if (isShift) {

                    if (isCtrl) {
                        devXDiff *= 50;
                    } else {
                        devXDiff *= 10;
                    }
                }

                /*
                 * When these values are not reset then the chart is flickering when scrolled
                 */
                if (_draggedChartDraggedPos != null) {
                    _draggedChartDraggedPos.x = 0;
                    _draggedChartStartPos.x = 0;
                }

                updateDraggedChart(devXDiff);

            } else {

                // zoom the chart

                if (isEventFromAxis) {
                    // set zoom center position to the left or right side, that
                    _zoomRatioCenter = isLeftAxis ? 0.0 : 1.0;
                }

                // reduce zoom factor when accelerator key <Alt> is pressed
                double accelerator = 1.0;
                final boolean isAlt = (event.stateMask & SWT.ALT) != 0;
                if (isAlt) {
                    accelerator = 1.01;
                }

                if (event.count < 0) {
                    zoomOutWithMouse(true, _devXMouseMove, accelerator);
                } else {
                    zoomInWithMouse(_devXMouseMove, accelerator);
                }

                moveSlidersToBorder();
            }
        }

        /*
         * prevent scrolling the scrollbar, scrolling is done by the chart itself
         */
        event.doit = false;
    }

    /**
     * Scroll event handler
     * 
     * @param event
     */
    private void onScroll(final SelectionEvent event) {
        redraw();
    }

    void redrawChart() {

        if (isDisposed()) {
            return;
        }

        _isChartDirty = true;
        redraw();
    }

    void redrawLayer() {

        if (isDisposed()) {
            return;
        }

        _isCustomLayerImageDirty = true;

        redraw();
    }

    /**
     * Make the graph selection dirty and redraw it.
     */
    private void redrawSelection() {

        if (isDisposed()) {
            return;
        }

        _isSelectionDirty = true;
        redraw();
    }

    /**
     * set the slider position when the data model has changed
     */
    void resetSliders() {

        // first get the left/right slider
        final ChartXSlider leftSlider = getLeftSlider();
        final ChartXSlider rightSlider = getRightSlider();

        /*
         * reset the sliders, the temp sliders are used so that the same slider is not reset twice
         */
        leftSlider.reset();
        rightSlider.reset();

        //      _isSliderDirty = true;
        //      redraw();
    }

    /**
     * select the next bar item
     */
    int selectBarItemNext() {

        int selectedIndex = Chart.NO_BAR_SELECTION;

        if (_selectedBarItems == null || _selectedBarItems.length == 0) {
            return selectedIndex;
        }

        // find selected Index and reset last selected bar item(s)
        for (int index = 0; index < _selectedBarItems.length; index++) {
            if (selectedIndex == Chart.NO_BAR_SELECTION && _selectedBarItems[index]) {
                selectedIndex = index;
            }
            _selectedBarItems[index] = false;
        }

        if (selectedIndex == Chart.NO_BAR_SELECTION) {

            // a bar item is not selected, select first
            selectedIndex = 0;

        } else {

            // select next bar item

            if (selectedIndex == _selectedBarItems.length - 1) {
                /*
                 * last bar item is currently selected, select the first bar item
                 */
                selectedIndex = 0;
            } else {
                // select next bar item
                selectedIndex++;
            }
        }

        _selectedBarItems[selectedIndex] = true;

        redrawSelection();

        return selectedIndex;
    }

    /**
     * select the previous bar item
     */
    int selectBarItemPrevious() {

        int selectedIndex = Chart.NO_BAR_SELECTION;

        // make sure that selectable bar items are available
        if (_selectedBarItems == null || _selectedBarItems.length == 0) {
            return selectedIndex;
        }

        // find selected item, reset last selected bar item(s)
        for (int index = 0; index < _selectedBarItems.length; index++) {
            // get the first selected item if there are many selected
            if (selectedIndex == -1 && _selectedBarItems[index]) {
                selectedIndex = index;
            }
            _selectedBarItems[index] = false;
        }

        if (selectedIndex == Chart.NO_BAR_SELECTION) {

            // a bar item is not selected, select first
            selectedIndex = 0;

        } else {

            // select next bar item

            if (selectedIndex == 0) {
                /*
                 * first bar item is currently selected, select the last bar item
                 */
                selectedIndex = _selectedBarItems.length - 1;
            } else {
                // select previous bar item
                selectedIndex = selectedIndex - 1;
            }
        }

        _selectedBarItems[selectedIndex] = true;

        redrawSelection();

        return selectedIndex;
    }

    void setCanAutoMoveSlidersWhenZoomed(final boolean canMoveSlidersWhenZoomed) {
        _canAutoMoveSliders = canMoveSlidersWhenZoomed;
    }

    /**
     * @param canAutoZoomToSlider
     *            the canAutoZoomToSlider to set
     */
    void setCanAutoZoomToSlider(final boolean canAutoZoomToSlider) {

        _canAutoZoomToSlider = canAutoZoomToSlider;
    }

    private void setChartCursor(final ChartCursor cursor) {

        if (cursor == null) {
            return;
        }

        switch (cursor) {
        case Arrow:
            setCursor(_cursorArrow);
            break;

        case Dragged:
            setCursor(_cursorDragged);
            break;

        default:
            setCursor(null);
            break;
        }
    }

    void setChartOverlayDirty() {

        _isOverlayDirty = true;

        redraw();
    }

    /**
     * Move a zoomed chart so that the slider is visible.
     * 
     * @param slider
     * @param isCenterSliderPosition
     * @param isSetSliderVisible
     * @param borderOffset
     *            Set chart with an offset to the chart border, this is helpful when tour segments
     *            are selected that the surrounding area is also visible.
     */
    private void setChartPosition(final ChartXSlider slider, final boolean isCenterSliderPosition,
            final boolean isSetSliderVisible, final int borderOffset) {

        if (_graphZoomRatio == 1) {
            // chart is not zoomed, nothing to do
            return;
        }

        final long xxDevSliderLinePos = slider.getXXDevSliderLinePos();

        final int devXViewPortWidth = getDevVisibleChartWidth();
        final long xxDevCenter = xxDevSliderLinePos - devXViewPortWidth / 2;

        double xxDevOffset = xxDevSliderLinePos;

        if (isCenterSliderPosition) {

            xxDevOffset = xxDevCenter;

        } else {

            /*
             * check if the slider is in the visible area
             */
            if (xxDevSliderLinePos < _xxDevViewPortLeftBorder) {

                xxDevOffset = xxDevSliderLinePos + 1 - borderOffset;

            } else if (xxDevSliderLinePos > _xxDevViewPortLeftBorder + devXViewPortWidth) {

                xxDevOffset = xxDevSliderLinePos - devXViewPortWidth + borderOffset;
            }
        }

        if (xxDevOffset != xxDevSliderLinePos && isSetSliderVisible) {

            /*
             * slider is not visible
             */

            // check left border
            xxDevOffset = Math.max(xxDevOffset, 0);

            // check right border
            xxDevOffset = Math.min(xxDevOffset, _xxDevGraphWidth - devXViewPortWidth);

            _zoomRatioLeftBorder = xxDevOffset / _xxDevGraphWidth;

            /*
             * reposition the mouse zoom position
             */
            final double xOffsetMouse = _xxDevViewPortLeftBorder + devXViewPortWidth / 2;
            _zoomRatioCenter = xOffsetMouse / _xxDevGraphWidth;

            updateVisibleMinMaxValues();

            /*
             * prevent to display the old chart image
             */
            _isChartDirty = true;

            _chartComponents.onResize();
        }

        /*
         * set position where the double click occured, this position will be used when the chart is
         * zoomed
         */
        _zoomRatioCenter = (double) xxDevSliderLinePos / _xxDevGraphWidth;
    }

    /**
     * Move a zoomed chart to a new position
     * 
     * @param xxDevNewPosition
     */
    private void setChartPosition(final long xxDevNewPosition) {

        if (_graphZoomRatio == 1) {
            // chart is not zoomed, nothing to do
            return;
        }

        final int devXViewPortWidth = getDevVisibleChartWidth();
        double xxDevNewPosition2 = xxDevNewPosition;

        if (xxDevNewPosition < _xxDevViewPortLeftBorder) {

            xxDevNewPosition2 = xxDevNewPosition + 1;

        } else if (xxDevNewPosition > _xxDevViewPortLeftBorder + devXViewPortWidth) {

            xxDevNewPosition2 = xxDevNewPosition - devXViewPortWidth;
        }

        // check left border
        xxDevNewPosition2 = Math.max(xxDevNewPosition2, 0);

        // check right border
        xxDevNewPosition2 = Math.min(xxDevNewPosition2, _xxDevGraphWidth - devXViewPortWidth);

        _zoomRatioLeftBorder = xxDevNewPosition2 / _xxDevGraphWidth;

        // reposition the mouse zoom position
        final double xOffsetMouse = _xxDevViewPortLeftBorder + devXViewPortWidth / 2;
        _zoomRatioCenter = xOffsetMouse / _xxDevGraphWidth;

        updateVisibleMinMaxValues();

        /*
         * prevent to display the old chart image
         */
        _isChartDirty = true;

        _chartComponents.onResize();

        /*
         * set position where the double click occured, this position will be used when the chart is
         * zoomed
         */
        _zoomRatioCenter = (double) xxDevNewPosition / _xxDevGraphWidth;
    }

    void setCursorStyle(final int devYMouse) {

        final ChartDataModel chartDataModel = _chart.getChartDataModel();
        if (chartDataModel == null) {
            return;
        }

        final ChartType chartType = chartDataModel.getChartType();

        if (chartType == ChartType.LINE || chartType == ChartType.LINE_WITH_BARS) {

            final boolean isMouseModeSlider = _chart.getMouseMode().equals(Chart.MOUSE_MODE_SLIDER);

            if (_xSliderDragged != null) {

                // x-slider is dragged
                if (isMouseModeSlider) {
                    setCursor(_cursorDragXSlider_ModeSlider);
                } else {
                    setCursor(_cursorDragXSlider_ModeZoom);
                }

            } else if (_ySliderDragged != null) {

                // y-slider is dragged

                setCursor(_cursorResizeTopDown);

            } else if (_isChartDragged || _isChartDraggedStarted) {

                // chart is dragged
                setCursor(_cursorDragged);

            } else {

                // nothing is dragged

                if (isMouseModeSlider) {
                    setCursor(_cursorModeSlider);
                } else {
                    setCursor(_cursorModeZoom);
                }
            }
        } else {
            setCursor(null);
        }
    }

    /**
     * Set a new configuration for the graph, the whole graph will be recreated. This method is
     * called when the chart canvas is resized, chart is zoomed or scrolled which requires that the
     * chart is recreated.
     */
    void setDrawingData(final ChartDrawingData chartDrawingData) {

        _chartDrawingData = chartDrawingData;

        // create empty list if list is not available, so we do not need
        // to check for null and isEmpty
        _allGraphDrawingData = chartDrawingData.graphDrawingData;

        _isGraphVisible = _allGraphDrawingData != null && _allGraphDrawingData.isEmpty() == false;

        _canChartBeOverlapped = canChartBeOverlapped(_allGraphDrawingData);
        _isChartOverlapped = _chartDrawingData.chartDataModel.isGraphOverlapped();

        if (_canChartBeOverlapped && _isChartOverlapped) {

            /*
             * Revert sequence that the top graph is painted as last and not as the first that it
             * will overlap the other graphs.
             */

            _revertedGraphDrawingData = new ArrayList<GraphDrawingData>();

            for (final GraphDrawingData graphDrawingData : _allGraphDrawingData) {
                _revertedGraphDrawingData.add(graphDrawingData);
            }

            Collections.reverse(_revertedGraphDrawingData);
        }

        // force all graphics to be recreated
        _isChartDirty = true;
        _isSliderDirty = true;
        _isCustomLayerImageDirty = true;
        _isSelectionDirty = true;

        if (_isDisableHoveredLineValueIndex) {
            /*
             * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
             * prevent setting new positions until the chart is redrawn otherwise the slider has the
             * value index -1, the chart is flickering when autoscrolling and the map is WRONGLY /
             * UGLY positioned
             * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
             */
            _isDisableHoveredLineValueIndex = false;
        } else {

            // prevent using old value index which can cause bound exceptions
            _hoveredValuePointIndex = -1;
            _lineDevPositions.clear();
            _lineFocusRectangles.clear();
        }

        // hide previous tooltip
        _hoveredBarToolTip.toolTip_20_Hide();

        // force the graph to be repainted
        redraw();
    }

    @Override
    public boolean setFocus() {

        boolean isFocus = false;

        if (setFocusToControl()) {

            // check if the chart has the focus
            if (isFocusControl()) {
                isFocus = true;
            } else {
                if (forceFocus()) {
                    isFocus = true;
                }
            }
        }

        return isFocus;
    }

    /**
     * Set the focus to a control depending on the chart type
     * 
     * @return Returns <code>true</code> when the focus was set
     */
    private boolean setFocusToControl() {

        if (_isGraphVisible == false) {
            // we can't get the focus
            return false;
        }

        boolean isFocus = false;

        final ChartType chartType = _chart.getChartDataModel().getChartType();
        if (chartType == ChartType.LINE) {

            if (_selectedXSlider == null) {

                // set focus to the left slider when x-sliders are visible
                if (_isXSliderVisible) {

                    _selectedXSlider = getLeftSlider();
                    isFocus = true;
                }

            } else if (_selectedXSlider != null) {

                isFocus = true;
            }

        } else if (chartType == ChartType.BAR) {

            if (_selectedBarItems == null || _selectedBarItems.length == 0) {

                setSelectedBars(null);

            } else {

                // set focus to selected x-data

                int selectedIndex = -1;

                // find selected Index, reset last selected bar item(s)
                for (int index = 0; index < _selectedBarItems.length; index++) {
                    if (selectedIndex == -1 && _selectedBarItems[index]) {
                        selectedIndex = index;
                    }
                    _selectedBarItems[index] = false;
                }

                if (selectedIndex == -1) {

                    // a bar item is not selected, select first

                    // disabled, 11.4.2008 wolfgang
                    //               fSelectedBarItems[0] = true;
                    //
                    //               fChart.fireBarSelectionEvent(0, 0);

                } else {

                    // select last selected bar item

                    _selectedBarItems[selectedIndex] = true;
                }

                redrawSelection();
            }
            isFocus = true;
        }

        //      if (isFocus) {
        //         _chart.fireFocusEvent();
        //      }

        return isFocus;
    }

    void setGraphSize(final int xxDevGraphWidth, final int xxDevViewPortOffset, final double graphZoomRatio) {

        _xxDevGraphWidth = xxDevGraphWidth;
        _xxDevViewPortLeftBorder = xxDevViewPortOffset;
        _graphZoomRatio = graphZoomRatio;

        _xSliderA.moveToXXDevPosition(xxDevViewPortOffset, false, true, false);
        _xSliderB.moveToXXDevPosition(xxDevGraphWidth, false, true, false);
    }

    /**
     * Check if mouse has moved over a line value and sets {@link #_hoveredValuePointIndex} to the
     * value index or <code>-1</code> when focus rectangle is not hit.
     */
    private void setHoveredLineValue() {

        if (_lineDevPositions.size() == 0) {
            return;
        }

        RectangleLong lineRect = null;

        for (final RectangleLong[] lineFocusRectangles : _lineFocusRectangles) {

            // find the line rectangle which is hovered by the mouse
            for (int valueIndex = 0; valueIndex < lineFocusRectangles.length; valueIndex++) {

                lineRect = lineFocusRectangles[valueIndex];

                // test if the mouse is within a bar focus rectangle
                if (lineRect != null) {

                    // inline for lineRect.contains
                    if ((_devXMouseMove >= lineRect.x) && (_devYMouseMove >= lineRect.y)
                            && _devXMouseMove < (lineRect.x + lineRect.width)
                            && _devYMouseMove < (lineRect.y + lineRect.height)) {

                        // keep the hovered line index
                        _hoveredValuePointIndex = valueIndex;

                        return;
                    }
                }
            }
        }

        // reset index
        _hoveredValuePointIndex = -1;
    }

    void setHoveredTitleSegment(final ChartTitleSegment chartTitleSegment) {

        _hoveredTitleSegment = chartTitleSegment;

        redraw();
    }

    void setLineSelectionPainter(final ILineSelectionPainter lineSelectionPainter) {
        _lineSelectionPainter = lineSelectionPainter;
    }

    void setSelectedBars(final boolean[] selectedItems) {

        if (selectedItems == null) {

            // set focus to first bar item

            if (_allGraphDrawingData.size() == 0) {
                _selectedBarItems = null;
            } else {

                final GraphDrawingData graphDrawingData = _allGraphDrawingData.get(0);
                final ChartDataXSerie xData = graphDrawingData.getXData();

                _selectedBarItems = new boolean[xData._highValuesDouble[0].length];
            }

        } else {

            _selectedBarItems = selectedItems;
        }

        _isSelectionVisible = true;

        redrawSelection();
    }

    void setSelectedLines(final boolean isSelectionVisible) {

        _isSelectionVisible = isSelectionVisible;

        redrawSelection();
    }

    /**
     * Set the value index in the X-slider for the hovered position.
     * 
     * @param xSlider
     */
    void setXSliderValue_FromHoveredValuePoint(final ChartXSlider xSlider) {

        final ChartDataXSerie xData = getXData();

        if (xData == null) {
            return;
        }

        final double[][] xValueSerie = xData.getHighValuesDouble();

        if (xValueSerie.length == 0) {
            // data are not available
            return;
        }

        final double[] xDataValues = xValueSerie[0];

        if (_hoveredValuePointIndex == -1 || _hoveredValuePointIndex >= xDataValues.length) {

            // this happens when a new tour is displayed

            return;
        }

        xSlider.setValueIndex(_hoveredValuePointIndex);
    }

    /**
     * Set the value index in the X-slider for the current slider position ratio.
     * <p>
     * The distance values (and time values with breaks) are not linear, the value is increasing
     * steadily but with different distance on the x axis. So first we have to find the nearest
     * position in the values array and then interpolite from the found position to the slider
     * position.
     * 
     * @param xSlider
     */
    void setXSliderValue_FromRatio(final ChartXSlider xSlider) {

        final ChartDataXSerie xData = getXData();

        if (xData == null) {
            return;
        }

        final double[][] xValueSerie = xData.getHighValuesDouble();

        if (xValueSerie.length == 0) {
            // data are not available
            return;
        }

        final double[] xDataValues = xValueSerie[0];
        final int serieLength = xDataValues.length;
        final int maxIndex = Math.max(0, serieLength - 1);

        /*
         */

        final double minValue = xData.getOriginalMinValue();
        final double maxValue = xData.getOriginalMaxValue();
        final double valueRange = maxValue > 0 ? (maxValue - minValue) : -(minValue - maxValue);

        final double posRatio = xSlider.getPositionRatio();
        int valueIndex = (int) (posRatio * serieLength);

        // check array bounds
        valueIndex = Math.min(valueIndex, maxIndex);
        valueIndex = Math.max(valueIndex, 0);

        // sliderIndex points into the value array for the current slider position
        double xDataValue = xDataValues[valueIndex];

        // compute the value for the slider on the x-axis
        final double sliderValue = posRatio * valueRange;

        if (xDataValue == sliderValue) {

            // nothing to do

        } else if (sliderValue > xDataValue) {

            /*
             * in the value array move towards the end to find the position where the value of the
             * slider corresponds with the value in the value array
             */

            while (sliderValue > xDataValue) {

                xDataValue = xDataValues[valueIndex++];

                // check if end of the x-data are reached
                if (valueIndex == serieLength) {
                    break;
                }
            }
            valueIndex--;
            xDataValue = xDataValues[valueIndex];

        } else {

            /*
             * xDataValue > sliderValue
             */

            while (sliderValue < xDataValue) {

                // check if beginning of the x-data are reached
                if (valueIndex == 0) {
                    break;
                }

                xDataValue = xDataValues[--valueIndex];
            }
        }

        /*
         * This is a bit of a hack because at some positions the value is too small. Solving the
         * problem in the algorithm would take more time than using this hack.
         */
        if (xDataValue < sliderValue) {
            valueIndex++;
        }

        // check array bounds
        valueIndex = Math.min(valueIndex, maxIndex);
        xDataValue = xDataValues[valueIndex];

        // !!! debug values !!!
        //      xValue = valueIndex * 1000;
        //      xValue = (int) (slider.getPositionRatio() * 1000000000);

        xSlider.setValueIndex(valueIndex);
    }

    /**
     * makes the slider visible, a slider is only drawn into the chart if a slider was created with
     * createSlider
     * 
     * @param isXSliderVisible
     */
    void setXSliderVisible(final boolean isSliderVisible) {
        _isXSliderVisible = isSliderVisible;
    }

    /**
     * Set ratio when the mouse is double clicked, this position is used to zoom the chart with the
     * mouse.
     */
    private void setZoomInPosition() {

        // get left+right slider
        final ChartXSlider leftSlider = getLeftSlider();
        final ChartXSlider rightSlider = getRightSlider();

        final long devLeftVirtualSliderLinePos = leftSlider.getXXDevSliderLinePos();

        final long devZoomInPosInChart = devLeftVirtualSliderLinePos
                + ((rightSlider.getXXDevSliderLinePos() - devLeftVirtualSliderLinePos) / 2);

        _zoomRatioCenter = (double) devZoomInPosInChart / _xxDevGraphWidth;
    }

    /**
     * Set left border ratio for the zoomed chart, zoomed graph width and ratio is already set.
     */
    private void setZoomRatioLeftBorder() {

        final int devViewPortWidth = getDevVisibleChartWidth();

        double xxDevPosition = _zoomRatioCenter * _xxDevGraphWidth;
        xxDevPosition -= devViewPortWidth / 2;

        // ensure left border bounds
        xxDevPosition = Math.max(xxDevPosition, 0);

        // ensure right border by setting the left border value
        final long leftBorder = _xxDevGraphWidth - devViewPortWidth;
        xxDevPosition = Math.min(xxDevPosition, leftBorder);

        _zoomRatioLeftBorder = xxDevPosition / _xxDevGraphWidth;
    }

    /**
     * switch the sliders to the 2nd x-data (switch between time and distance)
     */
    void switchSlidersTo2ndXData() {
        switchSliderTo2ndXData(_xSliderA);
        switchSliderTo2ndXData(_xSliderB);
    }

    /**
     * set the slider to the 2nd x-data and keep the slider on the same xValue position as before,
     * this can cause to the situation, that the right slider gets unvisible/unhitable or the
     * painted graph can have a white space on the right side
     * 
     * @param slider
     *            the slider which gets changed
     */
    private void switchSliderTo2ndXData(final ChartXSlider slider) {

        if (_allGraphDrawingData.size() == 0) {
            return;
        }

        final GraphDrawingData graphDrawingData = _allGraphDrawingData.get(0);
        if (graphDrawingData == null) {
            return;
        }

        final ChartDataXSerie data2nd = graphDrawingData.getXData2nd();

        if (data2nd == null) {
            return;
        }

        final double[] xValues = data2nd.getHighValuesDouble()[0];
        int valueIndex = slider.getValuesIndex();

        if (valueIndex >= xValues.length) {
            valueIndex = xValues.length - 1;
            slider.setValueIndex(valueIndex);
        }

        try {

            final double linePos = _xxDevGraphWidth * (xValues[valueIndex] / xValues[xValues.length - 1]);

            slider.moveToXXDevPosition(linePos, true, true, false);

        } catch (final ArrayIndexOutOfBoundsException e) {
            // ignore
        }
    }

    void updateChartSize() {

        if (valuePointToolTip == null) {
            return;
        }

        final int marginTop = _chartComponents.getDevChartMarginTop();
        final int marginBottom = _chartComponents.getDevChartMarginBottom();

        valuePointToolTip.setChartMargins(marginTop, marginBottom);

        // update custom overlay
        _isOverlayDirty = true;
        _isCustomOverlayInvalid = 99;
    }

    void updateCustomLayers() {

        if (isDisposed()) {
            return;
        }

        _isCustomLayerImageDirty = true;
        _isSliderDirty = true;

        redraw();
    }

    private void updateDraggedChart(final int devXDiff) {

        if (_graphZoomRatio == 1.0) {
            // nothing is zoomed -> nothing can be dragged
            return;
        }

        //      System.out.println((UI.timeStampNano() + " [" + getClass().getSimpleName() + "] ") + ("\tupdateDraggedChart"));
        //      // TODO remove SYSTEM.OUT.PRINTLN

        final int devVisibleChartWidth = getDevVisibleChartWidth();

        double devXOffset = _xxDevViewPortLeftBorder - devXDiff;

        // adjust left border
        devXOffset = Math.max(devXOffset, 0);

        // adjust right border
        devXOffset = Math.min(devXOffset, _xxDevGraphWidth - devVisibleChartWidth);

        _zoomRatioLeftBorder = devXOffset / _xxDevGraphWidth;

        /*
         * reposition the mouse zoom position
         */
        _zoomRatioCenter = ((_zoomRatioCenter * _xxDevGraphWidth) - devXDiff) / _xxDevGraphWidth;

        updateVisibleMinMaxValues();

        /*
         * draw the dragged image until the graph image is recomuted
         */
        _isPaintDraggedImage = true;

        _chartComponents.onResize();

        moveSlidersToBorder();
    }

    void updateGraphSize() {

        final int devVisibleChartWidth = getDevVisibleChartWidth();

        // calculate new virtual graph width
        _xxDevGraphWidth = (long) (_graphZoomRatio * devVisibleChartWidth);

        if (_graphZoomRatio == 1.0) {
            // with the ration 1.0 the graph is not zoomed
            _xxDevViewPortLeftBorder = 0;
        } else {
            // the graph is zoomed, only a part is displayed which starts at
            // the offset for the left slider
            _xxDevViewPortLeftBorder = (long) (_zoomRatioLeftBorder * _xxDevGraphWidth);
        }
    }

    /**
     * Resize the sliders after the graph was resized
     */
    void updateSlidersOnResize() {

        /*
         * update all x-sliders
         */
        final int visibleGraphHeight = getDevVisibleGraphHeight();
        _xSliderA.handleChartResize(visibleGraphHeight, false);
        _xSliderB.handleChartResize(visibleGraphHeight, false);

        /*
         * update all y-sliders
         */
        _ySliders = new ArrayList<ChartYSlider>();

        // loop: get all y-sliders from all graphs
        for (final GraphDrawingData drawingData : _allGraphDrawingData) {

            final ChartDataYSerie yData = drawingData.getYData();

            if (yData.isShowYSlider()) {

                final ChartYSlider sliderTop = yData.getYSlider1();
                final ChartYSlider sliderBottom = yData.getYSlider2();

                sliderTop.handleChartResize(drawingData, ChartYSlider.SLIDER_TYPE_TOP);
                _ySliders.add(sliderTop);

                sliderBottom.handleChartResize(drawingData, ChartYSlider.SLIDER_TYPE_BOTTOM);
                _ySliders.add(sliderBottom);

                _isYSliderVisible = true;
            }
        }
    }

    /**
     * Set min/max values for the x/y-axis that the visible area will be filled with the chart
     */
    void updateVisibleMinMaxValues() {

        final ChartDataModel chartDataModel = _chartComponents.getChartDataModel();
        final ChartDataXSerie xData = chartDataModel.getXData();
        final ArrayList<ChartDataYSerie> yDataList = chartDataModel.getYData();

        if (xData == null) {
            return;
        }

        final double[][] xValueSerie = xData.getHighValuesDouble();

        if (xValueSerie.length == 0) {
            // data are not available
            return;
        }

        final double[] xValues = xValueSerie[0];
        final double lastXValue = xValues[xValues.length - 1];
        final double valueVisibleArea = lastXValue / _graphZoomRatio;

        final double valueLeftBorder = lastXValue * _zoomRatioLeftBorder;
        double valueRightBorder = valueLeftBorder + valueVisibleArea;

        // make sure right is higher than left
        if (valueLeftBorder >= valueRightBorder) {
            valueRightBorder = valueLeftBorder + 1;
        }

        /*
         * get value index for the left and right border of the visible area
         */
        int xValueIndexLeft = 0;
        for (int serieIndex = 0; serieIndex < xValues.length; serieIndex++) {

            final double xValue = xValues[serieIndex];

            if (xValue == valueLeftBorder) {
                xValueIndexLeft = serieIndex;
                break;
            }

            if (xValue > valueLeftBorder) {
                xValueIndexLeft = serieIndex == 0 ? 0

                        // get index from last invisible value
                        : serieIndex - 1;
                break;
            }
        }

        int xValueIndexRight = xValueIndexLeft;
        for (; xValueIndexRight < xValues.length; xValueIndexRight++) {
            if (xValues[xValueIndexRight] > valueRightBorder) {
                break;
            }
        }

        /*
         * get visible min/max value for the x-data serie which fills the visible area in the chart
         */
        // ensure array bounds
        final int xValuesLastIndex = xValues.length - 1;
        xValueIndexLeft = Math.min(xValueIndexLeft, xValuesLastIndex);
        xValueIndexLeft = Math.max(xValueIndexLeft, 0);
        xValueIndexRight = Math.min(xValueIndexRight, xValuesLastIndex);
        xValueIndexRight = Math.max(xValueIndexRight, 0);

        xData.setVisibleMinValue(xValues[xValueIndexLeft]);
        xData.setVisibleMaxValue(xValues[xValueIndexRight]);

        /*
         * get min/max value for each y-data serie to fill the visible area with the chart
         */
        for (final ChartDataYSerie yData : yDataList) {

            final boolean isIgnoreMinMaxZero = yData.isIgnoreMinMaxZero();

            final float[][] yValueSeries = yData.getHighValuesFloat();
            final float yValues[] = yValueSeries[0];

            // ensure array bounds
            final int yValuesLastIndex = yValues.length - 1;
            xValueIndexLeft = Math.min(xValueIndexLeft, yValuesLastIndex);
            xValueIndexLeft = Math.max(xValueIndexLeft, 0);
            xValueIndexRight = Math.min(xValueIndexRight, yValuesLastIndex);
            xValueIndexRight = Math.max(xValueIndexRight, 0);

            // set dummy value
            float dataMinValue = Float.MIN_VALUE;
            float dataMaxValue = Float.MIN_VALUE;

            for (final float[] yValueSerie : yValueSeries) {

                if (yValueSerie == null) {
                    continue;
                }

                for (int valueIndex = xValueIndexLeft; valueIndex <= xValueIndexRight; valueIndex++) {

                    final float yValue = yValueSerie[valueIndex];

                    if (yValue != yValue) {
                        // ignore NaN
                        continue;
                    }

                    if (yValue == Float.POSITIVE_INFINITY) {
                        // ignore infinity
                        continue;
                    }

                    if (isIgnoreMinMaxZero && (yValue > -FLOAT_ZERO && yValue < FLOAT_ZERO)) {
                        // value is zero (almost) -> ignore
                        continue;
                    }

                    if (dataMinValue == Float.MIN_VALUE) {

                        // setup first value
                        dataMinValue = dataMaxValue = yValue;

                    } else {

                        // check subsequent values

                        if (yValue < dataMinValue) {
                            dataMinValue = yValue;
                        }
                        if (yValue > dataMaxValue) {
                            dataMaxValue = yValue;
                        }
                    }
                }
            }

            if (dataMinValue == Float.MIN_VALUE) {

                // a valid value is not found
                dataMinValue = 0;
                dataMaxValue = 1;
            }

            if (yData.isForceMinValue()) {

                /*
                 * Prevent that data values which are small than forced min are not painted but
                 * increase the visible min values when the data values are larger than the forced
                 * min value.
                 */

                final double forcedMinValue = yData.getVisibleMinValueForced();

                if (dataMinValue > forcedMinValue) {

                    // data min is larger than forced min -> use data min value

                    yData.setVisibleMinValue(dataMinValue);

                } else {

                    // set forced min value

                    yData.setVisibleMinValue(forcedMinValue);
                }

            } else if (dataMinValue != 0) {

                // min is not forced

                yData.setVisibleMinValue(dataMinValue);
            }

            if (yData.isForceMaxValue()) {

                /*
                 * Prevent that data values which are larger than forced max are not painted but
                 * reduce the visible max values when the data values are below the forced max
                 * value.
                 */

                final double forcedMaxValue = yData.getVisibleMaxValueForced();

                if (dataMaxValue < forcedMaxValue) {

                    // data max value is below forced max value -> use data max value

                    yData.setVisibleMaxValue(dataMaxValue);

                } else {

                    // set forced max value

                    yData.setVisibleMaxValue(forcedMaxValue);
                }

            } else if (dataMaxValue != 0) {

                // max is not forced

                yData.setVisibleMaxValue(dataMaxValue);
            }
        }
    }

    /**
     * adjust the y-position for the bottom label when the top label is drawn over it
     */
    private void updateXSliderYPosition() {

        int labelIndex = 0;
        final ArrayList<ChartXSliderLabel> onTopLabels = _xSliderOnTop.getLabelList();
        final ArrayList<ChartXSliderLabel> onBotLabels = _xSliderOnBottom.getLabelList();

        for (final ChartXSliderLabel onTopLabel : onTopLabels) {

            final ChartXSliderLabel onBotLabel = onBotLabels.get(labelIndex);

            final int onTopWidth2 = onTopLabel.width / 2;
            final int onTopDevX = onTopLabel.x;
            final int onBotWidth2 = onBotLabel.width / 2;
            final int onBotDevX = onBotLabel.x;

            if (onTopDevX + onTopWidth2 > onBotDevX - onBotWidth2
                    && onTopDevX - onTopWidth2 < onBotDevX + onBotWidth2) {
                onBotLabel.y = onBotLabel.y + onBotLabel.height + 5;
            }
            labelIndex++;
        }
    }

    /**
     * @param devXMousePosition
     *            This relative mouse position is used to keep the position when zoomed in, when set
     *            to {@link Integer#MIN_VALUE} this value is ignored.
     * @param accelerator
     *            The default zoom ratio will be multiplied with this value when this value is not
     *            1.0.
     */
    void zoomInWithMouse(final int devXMousePosition, final double accelerator) {

        if (_xxDevGraphWidth <= ChartComponents.CHART_MAX_WIDTH) {

            // chart can be zoomed in

            final int devViewPortWidth = getDevVisibleChartWidth();
            final double devViewPortWidth2 = (double) devViewPortWidth / 2;

            ZOOM_RATIO_FACTOR = accelerator != 1.0 ? accelerator : ZOOM_RATIO;

            final double newZoomRatio = _graphZoomRatio * ZOOM_RATIO_FACTOR;
            final long xxDevNewGraphWidth = (long) (devViewPortWidth * newZoomRatio);

            if (_xSliderDragged != null) {

                // set zoom center so that the dragged x-slider keeps position when zoomed in

                final double xxDevSlider = _xxDevViewPortLeftBorder + _devXDraggedXSliderLine;
                final double sliderRatio = xxDevSlider / _xxDevGraphWidth;
                final double xxDevNewSlider = sliderRatio * xxDevNewGraphWidth;
                final double xxDevNewZoomRatioCenter = xxDevNewSlider - _devXDraggedXSliderLine + devViewPortWidth2;

                _zoomRatioCenter = xxDevNewZoomRatioCenter / xxDevNewGraphWidth;

            } else if (devXMousePosition != Integer.MIN_VALUE) {

                //            if (_zoomRatioCenter == 1.0) {
                //
                //               // keep ratio, this ratio is used when mouse is in the right axis component and is zooming
                //
                //            } else {
                //            }

                final double xxDevSlider = _xxDevViewPortLeftBorder + devXMousePosition;
                final double sliderRatio = xxDevSlider / _xxDevGraphWidth;
                final double xxDevNewSlider = sliderRatio * xxDevNewGraphWidth;
                final double xxDevNewZoomRatioCenter = xxDevNewSlider - devXMousePosition + devViewPortWidth2;

                _zoomRatioCenter = xxDevNewZoomRatioCenter / xxDevNewGraphWidth;
            }

            if (xxDevNewGraphWidth > ChartComponents.CHART_MAX_WIDTH) {

                // ensure max size
                _graphZoomRatio = (double) ChartComponents.CHART_MAX_WIDTH / devViewPortWidth;
                _xxDevGraphWidth = ChartComponents.CHART_MAX_WIDTH;

            } else {

                _graphZoomRatio = newZoomRatio;
                _xxDevGraphWidth = xxDevNewGraphWidth;
            }

            setZoomRatioLeftBorder();
            handleChartResizeForSliders(false);

            updateVisibleMinMaxValues();
            moveSlidersToBorder();

            _chartComponents.onResize();
        }

        _chart.enableActions();
    }

    /**
     * Zoom into the graph with the ratio {@link #ZOOM_RATIO_FACTOR}
     */
    void zoomInWithoutSlider() {

        final int visibleGraphWidth = getDevVisibleChartWidth();
        final long graphImageWidth = _xxDevGraphWidth;

        final long maxChartWidth = ChartComponents.CHART_MAX_WIDTH;

        if (graphImageWidth <= maxChartWidth) {

            // chart is within the range which can be zoomed in

            if (graphImageWidth * ZOOM_RATIO_FACTOR > maxChartWidth) {

                /*
                 * the double zoomed graph would be wider than the max width, reduce it to the max
                 * width
                 */
                _graphZoomRatio = (double) maxChartWidth / visibleGraphWidth;
                _xxDevGraphWidth = maxChartWidth;

            } else {

                _graphZoomRatio = _graphZoomRatio * ZOOM_RATIO_FACTOR;
                _xxDevGraphWidth = (long) (graphImageWidth * ZOOM_RATIO_FACTOR);
            }

            _chart.enableActions();
        }
    }

    /**
     * Zoom into the graph to the left and right slider
     */
    void zoomInWithSlider() {

        final int devVisibleChartWidth = getDevVisibleChartWidth();

        /*
         * offset for the left slider
         */
        final ChartXSlider leftSlider = getLeftSlider();
        final ChartXSlider rightSlider = getRightSlider();

        // get position of the sliders within the graph
        final long devVirtualLeftSliderPos = leftSlider.getXXDevSliderLinePos();
        final long devVirtualRightSliderPos = rightSlider.getXXDevSliderLinePos();

        // difference between left and right slider
        final double devSliderDiff = devVirtualRightSliderPos - devVirtualLeftSliderPos - 0;

        if (devSliderDiff == 0) {

            // no difference between the slider
            _graphZoomRatio = 1;
            _xxDevGraphWidth = devVisibleChartWidth;

        } else {

            /*
             * the graph image can't be scrolled, show only the zoomed part which is defined between
             * the two sliders
             */

            // calculate new graph ratio
            _graphZoomRatio = (_xxDevGraphWidth) / (devSliderDiff);

            // adjust rounding problems
            _graphZoomRatio = (_graphZoomRatio * devVisibleChartWidth) / devVisibleChartWidth;

            // set the position (ratio) at which the zoomed chart starts
            _zoomRatioLeftBorder = getLeftSlider().getPositionRatio();

            // set the center of the chart for the position when zooming with the mouse
            final double devVirtualWidth = _graphZoomRatio * devVisibleChartWidth;
            final double devXOffset = _zoomRatioLeftBorder * devVirtualWidth;
            final double devCenterPos = devXOffset + devVisibleChartWidth / 2;
            _zoomRatioCenter = devCenterPos / devVirtualWidth;
        }

        handleChartResizeForSliders(false);

        updateVisibleMinMaxValues();

        _chart.enableActions();
    }

    /**
     * Zooms out of the graph
     */
    void zoomOutFitGraph() {

        // reset the data which influence the computed graph image width
        _graphZoomRatio = 1;
        _zoomRatioLeftBorder = 0;

        _xxDevGraphWidth = getDevVisibleChartWidth();
        _xxDevViewPortLeftBorder = 0;

        // reposition the sliders
        final int visibleGraphHeight = getDevVisibleGraphHeight();
        _xSliderA.handleChartResize(visibleGraphHeight, false);
        _xSliderB.handleChartResize(visibleGraphHeight, false);

        _chart.enableActions();

        _chartComponents.onResize();
    }

    /**
     * Zooms out of the graph
     * 
     * @param isUpdateChart
     * @param devXMousePosition
     *            This relative mouse position is used to keep the position when zoomed in, when set
     *            to {@link Integer#MIN_VALUE} this value is ignored.
     * @param accelerator
     *            The default zoom ratio will be multiplied with this value when this value is not
     *            1.0.
     */
    void zoomOutWithMouse(final boolean isUpdateChart, final int devXMousePosition, final double accelerator) {

        final int devViewPortWidth = getDevVisibleChartWidth();
        final double devViewPortWidth2 = (double) devViewPortWidth / 2;

        ZOOM_RATIO_FACTOR = accelerator != 1.0 ? accelerator : ZOOM_RATIO;

        if (_graphZoomRatio > ZOOM_RATIO_FACTOR) {

            // graph is zoomed

            final double newZoomRatio = _graphZoomRatio / ZOOM_RATIO_FACTOR;
            final long xxDevNewGraphWidth = (long) (newZoomRatio * devViewPortWidth);

            if (_xSliderDragged != null) {

                /**
                 * Set zoom center so that the dragged x-slider keeps position when zoomed out.
                 * <p>
                 * This formula preserves the slider ratio for a resized graph but is using the
                 * zoomed center to preserve the x-slider position
                 * <p>
                 * very complicated and it took me some time to get this formula
                 */

                // get slider ratio before the graph width is resized
                final double xxDevSlider = _xxDevViewPortLeftBorder + _devXDraggedXSliderLine;
                final double sliderRatio = xxDevSlider / _xxDevGraphWidth;
                final double xxDevNewSlider = sliderRatio * xxDevNewGraphWidth;
                final double xxDevNewZoomRatioCenter = xxDevNewSlider - _devXDraggedXSliderLine + devViewPortWidth2;

                _zoomRatioCenter = xxDevNewZoomRatioCenter / xxDevNewGraphWidth;

            } else if (devXMousePosition != Integer.MIN_VALUE) {

                final double xxDevSlider = _xxDevViewPortLeftBorder + devXMousePosition;
                final double sliderRatio = xxDevSlider / _xxDevGraphWidth;
                final double xxDevNewSlider = sliderRatio * xxDevNewGraphWidth;
                final double xxDevNewZoomRatioCenter = xxDevNewSlider - devXMousePosition + devViewPortWidth2;

                _zoomRatioCenter = xxDevNewZoomRatioCenter / xxDevNewGraphWidth;
            }

            _graphZoomRatio = newZoomRatio;
            _xxDevGraphWidth = xxDevNewGraphWidth;

            setZoomRatioLeftBorder();

            handleChartResizeForSliders(false);
            updateVisibleMinMaxValues();

            if (isUpdateChart) {
                _chartComponents.onResize();
            }

        } else {

            if (_graphZoomRatio != 1) {

                // show whole graph in the chart

                _graphZoomRatio = 1;
                _xxDevGraphWidth = devViewPortWidth;

                setZoomRatioLeftBorder();

                handleChartResizeForSliders(false);
                updateVisibleMinMaxValues();

                if (isUpdateChart) {
                    _chartComponents.onResize();
                }
            }
        }

        moveSlidersToBorder();

        _chart.enableActions();
    }

}