import { LasalRuntimeSigElement, SigPolymer } from '../../sigmatek/sig-element/sig-element.js';
import { DA_DataManager } from '../sig-control-da-datamanager/sig-control-da-datamanager.js';

export class LasalRuntimeSigControlDAGraphs extends LasalRuntimeSigElement {

    /***************************************************************************************************************************
     * Defines the custom tag name. Must match the dom module id
     ***************************************************************************************************************************/
    static get is() {
        return 'sig-control-da-graphview';
    }

    /***************************************************************************************************************************
     * When the element is loaded, importMeta() assigns reference to the path from 
     * which an element was imported to its importPath property. 
     * Relative URLs in styles are automatically re-written to be relative to the importPath property. 
     * You can use [[importPath]] in your component to load url resources
     ***************************************************************************************************************************/
    static get importMeta() { return import.meta; }

    /***************************************************************************************************************************
     * Return the component style and template as a template literal
     ***************************************************************************************************************************/    
    static get template() {
        return SigPolymer.html`

        <style include='sig-element-css'>
            :host {
                /* Basic styling of the component */
                color: var(--theme-sig-control-da-graphview-color, inherit);
                background-color: var(--theme-sig-control-da-graphview-background-color, transparent);

                border-width: var(--theme-sig-control-da-graphview-border-width, inherit);
                border-color: var(--theme-sig-control-da-graphview-border-color, inherit);
                border-style: var(--theme-sig-control-da-graphview-border-style, none);
                border-radius: var(--theme-sig-control-da-graphview-border-radius, 0);

                /* Basic box model settings */
                display: block;
                position: absolute;
                box-sizing: border-box;
                padding: 2px;
            }

            :host * {
                /* Reset margin and padding for child elements and turn off text selection */
                @apply --notextselect;
                margin: 0;
                padding: 0;
            }

            #yAxisArea1 {
                float: left;
                width: 0%;
                height: 100%;
            }

            #stage {
                float: left;
                width: 100%;
                height: 100%;
            }

            #drawingArea {
                width: 100%;
                height: 90%;
            }

            #yAxisArea2 {
                float: left;
                width: 0%;
                height: 100%;
            }

            #xAxisArea {
                width: 100%;
                height: 10%;
            }

            /* if x axis shouldn't be shown */
            :host([hidexscale="1"]) #xAxisArea {
                display: none;
            } 

            /* in osci mode we also do not show x axis */
            :host([mode='osci']) #xAxisArea {
                display: none;            
            }

            :host([mode='osci']) #drawingArea {
                height: 100%;            
            }            

            #gridCanvas {
                position: absolute;
                background-color: transparent;                
            }

            #graphsCanvas {
                position: absolute;
                background-color: transparent;                
            }                        

            #cursorCanvas {
                position: absolute;
                background-color: transparent;                
            }   

            #helperCanvas {
                display: none;
            }
            
            #graphsContainer {
                width: 100%;
                height: 100%;
            }
        </style>

        <div id="graphsContainer">
            <div id="yAxisArea1">
                <canvas id="yAxis1Canvas"></canvas>
            </div>
            <div id="stage">
                <div id="unitPlaceHolder"></div>
                <div id="drawingArea" on-track="_track" on-pinchzoom="_zoomView" on-dblclick="_doubleClicked">
                    <canvas id="gridCanvas"></canvas>
                    <canvas id="graphsCanvas"></canvas>
                    <canvas id="cursorCanvas"></canvas>               
                </div>
                <div id="xAxisArea">
                    <canvas id="xAxisCanvas"></canvas>
                </div>             
            </div>
            
            <div id="yAxisArea2">
                <canvas id="yAxis2Canvas"></canvas>
            </div>   
            <canvas id="helperCanvas"></canvas>            
        </div>
        `;
    }

    static get properties() {
        return {
            graphinstanceid: {
                type: Number,
                value: undefined,
                reflectToAttribute: true
            },
            xdivisions: {
                type: Number,
                value: 10,
                observer: '_xDivisionsChanged'
            },
            ydivisions: {
                type: Number,
                value: 10,
                observer: '_yDivisionsChanged'
            },
            gridlinestyle: {
                type: String,
                value: 'dashed',
                observer: '_gridLineStyleChanged'
            },
            gridcolor: {
                type: String,
                value: 'rgba(160,160,160,1)',
                observer: '_gridColorChanged'
            },
            hidexscale: {
                type: Number,
                value: 0,
                observer: '_hideXScaleChanged',
                reflectToAttribute: true
            },
            textcolor: {
                type: String,
                value: "rgba(255,255,255,1)",
                observer: '_textColorChanged'
            },
            timedivision: {
                type: Number,
                value: 0,
                observer: '_timeDivisionChanged'
            },
            timedivisionunit: {
                type: Number,
                value: 0,
                observer: '_timeDivisionUnitChanged'
            },
            dateformat: {
                type: Number,
                value: 0,
                observer: '_dateTimeFormatChanged'
            },
            timeformat: {
                type: Number,
                value: 0,
                observer: '_dateTimeFormatChanged'
            },
            scrollvalue: {
                type: Number,
                value: 0,
                observer: '_scrollValueChanged',
                notify: true
            },
            x1cursoractive: {
                type: Number,
                value: 0,
                observer: '_cursorChanged'
            },
            x1cursorvalue: {
                type: Number,
                value: 0,
                observer: '_cursorChanged',
                notify: true
            },
            x2cursoractive: {
                type: Number,
                value: 0,
                observer: '_cursorChanged'
            },
            x2cursorvalue: {
                type: Number,
                value: 0,
                observer: '_cursorChanged',
                notify: true
            },
            y1cursoractive: {
                type: Number,
                value: 0,
                observer: '_cursorChanged'
            },
            y1cursorvalue: {
                type: Number,
                value: 0,
                observer: '_cursorChanged',
                notify: true
            },
            y2cursoractive: {
                type: Number,
                value: 0,
                observer: '_cursorChanged'
            },
            y2cursorvalue: {
                type: Number,
                value: 0,
                observer: '_cursorChanged',
                notify: true
            },
            noofscales: {
                type: Number,
                value: undefined,
                observer: '_noOfScalesChanged'
            },
            firstscaleid: {
                type: Number,
                value: 0,
                observer: '_firstScaleChanged',
                notify: true
            },
            mode: {
                type: String,
                value: 'trend',
                observer: '_modeChanged',
                reflectToAttribute: true
            },
            horizontalpos: {
                type: Number,
                value: 0,
                observer: '_horizontalChanged',
                notify: true
            },
            triggerlevel: {
                type: Number,
                value: 0,
                observer: '_triggerLevelChanged',
                notify: true
            },
            triggerchannel: {
                type: Number,
                value: 0,
                observer: '_triggerChannelChanged'
            },
            zoomfactorx: {
                type: Number,
                value: 1,
                observer: '_zoomFactorXChanged',
                notify: true
            },
            zoomfactory: {
                type: Number,
                value: 1,
                observer: '_zoomFactorYChanged',
                notify: true
            },            
            scrollable: {
                type: Number,
                value: undefined,
                notify: true
            },            
            invalidpathcolor: {
                type: String,
                value: 'rgba(160,160,160,1)',
                observer: '_invalidPathColorChanged'
            },
            displayedTimeRange: {
                type: Object,
                value: undefined,
                observer: '_displayedTimeRangeChanged'
            },
            timeRange: {
                type: Object,
                value: undefined,
                observer: '_timeRangeChanged'
            },
            triggercolor: {
                type: String,
                value: 'rgba(222,247,85,1)',
                observer: '_triggerColorChanged'
            }                                                                         
        };
    }

    /***************************************************************************************************************************
     * Called when the element has been created,
     * but before property values are set and local DOM is initialized.
     ***************************************************************************************************************************/
    constructor() {
        super();

        //init variables which are used as constants so there is only one value if it should be changed
        this._initVariables();     

        //only do if not in designmode
        if (!this.isdesignmode) {
            //  Create shortcuts to the common apis makes the code more readable
            if (window.sigApi) {
                this.propApi = window.sigApi.properties;
                this.constApi = window.sigApi.SIG_CONST;
                this.datapointApi = window.sigApi.datapointManager;
                this.vmApi = window.sigApi.vm;
                this.unitApi = window.sigApi.unitManager;
                this.evtMediaApi = window.sigApi.eventMediator;
                this.evtApi = window.sigApi.events;
            } else {
                this._log('constructor(): API not ready!', 'ERROR');
            }

            this.sigAddRequiredProperty('graphInstanceID');
            this.sigAddRequiredProperty('mode');
            this.sigAddRequiredProperty('xdivisions');
            this.sigAddRequiredProperty('ydivisions');
            this.sigAddRequiredProperty('group');
            this.sigAddRequiredProperty('gridcolor');
            this.sigAddRequiredProperty('gridlinestyle');
            this.sigAddRequiredProperty('textcolor'); 
            this.sigAddRequiredProperty('scrollValue');
            this.sigAddRequiredProperty('timedivision');
            this.sigAddRequiredProperty('timedivisionunit'); 
            this.sigAddRequiredProperty('oscitrigger');      
            
            //register for incoming events
            this._registerEvents();

            //request instance of datamanager
            this.DA_DataManager = DA_DataManager.requestInstance();
        }
    }

    /**
     * init variables which are used as constants so there is only one value if it should be changed
     */
    _initVariables() {
        //define size of separation lines
        this.sepLineSize = 5;
        //define offset to top and bottom of label
        this.labelOffset = 2;
        //triangle std size
        this.triangleSize = 10;  
        //offset to border of drawing area -> so curves won't get cut off -> only used for autoset
        this.autosetOffset = 2;
        //offset of base line of axis to drawing area
        this.baseLineOffset = 3;
        //corridor to track cursors in px
        this.cursorTrackOffset = 10;
        //init zoommode
        this.zoommode = 1;

        //init splitmode
        this.splitmode = 0;

        //event ids
        this.majorID = 0x8003;
        Object.freeze(this.majorID);
        
        this.autoset = {
            minorID: 0x08
        }
        Object.freeze(this.autoset); 
        
        //define triggermodes
        this.triggerModes = {
            all: 0,
            single: 1,
            group: 2
        }
        Object.freeze(this.triggerModes); 

        //define datatypes
        this.dataTypes = {
            void: 0,
            bool: 1,
            int8: 2,
            int16: 3,
            int32: 4,
            int64: 5,
            uint8: 6,
            uint16: 7,
            uint32: 8,
            uint64: 9,
            float32: 10,
            float64: 11
        }
        Object.freeze(this.dataTypes);  
        
        //define zoom options
        this.zoomOptions = {
            none: 0,
            both: 1,
            onlyX: 2,
            onlyY: 3
        }
    }

    /**
     * register for incoming events
     */
    _registerEvents() {
        //*******************************************************************************************************
        //** new channel logged in so get channels and draw them
        //*******************************************************************************************************
        this.sigAddEventListener('_DA_NEW_CHANNEL_LOGGED_IN', (e) => {
            if (e.detail !== undefined) {
                //check if component already rendered
                if (this.rendered) {                
                    //check if channel ment for graph instance id
                    if (this.graphinstanceid === e.detail.instID) {
                        //check if channel is active and 
                        if (e.detail.channelActive === 1) {
                            //redraw all
                            this._redrawAll();                              
                        } 
                          
                    }                                             
                }
                
            }
        }, 'jsEvent', window);

        //*******************************************************************************************************
        //** we logged into a new manager
        //*******************************************************************************************************
        this.sigAddEventListener('_DA_LOGGED_IN', (e) => {
            if (e.detail !== undefined) {
                //check if component already rendered
                if (this.rendered) { 
                    //redraw all
                    this._redrawAll();                               
                }                                                
            }
        }, 'jsEvent', window);

        //*******************************************************************************************************
        //** buffer data have changed -> check if channel is displayed in this graph
        //*******************************************************************************************************
        this.sigAddEventListener('_DA_CHANNEL_BUFFER_CHANGED', (e) => {
            if (e.detail !== undefined) {
                //check if component already rendered
                if (this.rendered) {                 
                    //check if datamanager was initialized
                    if (this.DA_DataManager !== undefined) {
                        //get visuchannels
                        const visuChls = this.DA_DataManager.getVisuChannels(this.graphinstanceid);
                        if ((visuChls !== undefined) && (visuChls.size > 0)) {
                            for (let [visuChlID, channel] of visuChls) {
                                if (channel.chlReference !== undefined) {
                                    //check if it is about this channel
                                    if ((channel.chlReference.stationNumber === e.detail.stationNumber) && (channel.chlReference.chlID === e.detail.chlID)) {                               
                                        //check if channel active
                                        if (channel.channelActive.value === 1) {                                        
                                            //draw curves
                                            this._requestAnimation();
                                        }
                                    }
                                }    
                                else {
                                    this._log('_registerEvents(): invalid channel reference', 'ERROR');
                                }                                                            
                            }
                        }
                    } 
                }                
            }
        }, 'jsEvent', window);

        //*******************************************************************************************************
        //** buffer was resetted
        //*******************************************************************************************************
        this.sigAddEventListener('_DA_BUFFER_RESETTED', (e) => {
            if (e.detail !== undefined) {
                if (this.mode === 'osci') {
                    //check if trigger infos are available
                    if (this.triggerInfos !== undefined) {
                        //check if it is the channel the oscitrigger is configured
                        if ((this.triggerInfos.stationNumber === e.detail.stationNumber) && (this.triggerchannel === e.detail.chlID)) {
                            //save recordedPresamples for drawing correct horizontal position
                            this.recordedPresamples = this.preSamples;
                        }    
                    }    
                }                    
            }
        }, 'jsEvent', window);

        //*******************************************************************************************************
        //** buffer was reallocated
        //*******************************************************************************************************
        this.sigAddEventListener('_DA_BUFFER_REALLOC', (e) => {
            if (e.detail !== undefined) {
                //check if osci mode
                if (this.mode === 'osci') {
                    //check if already rendered and trigger infos are available
                    if ((this.rendered) && (this.triggerInfos !== undefined)) {
                        //check if it is the channel the oscitrigger is configured
                        if ((this.triggerInfos.stationNumber === e.detail.stationNumber) && (this.triggerchannel === e.detail.chlID)) {
                            //update pre and postsamples
                            this._calcPrePostSamples();
                        }
                    }
                }
            }
        }, 'jsEvent', window);

        //*******************************************************************************************************
        //** buffer was cleared -> check if channel is displayed in this graph
        //*******************************************************************************************************
        this.sigAddEventListener('_DA_CLEAR_DATA', (e) => {
            if (e.detail !== undefined) {
                //check if component already rendered
                if (this.rendered) {                    
                    //check if datamanager was initialized
                    if (this.DA_DataManager !== undefined) {
                        //get visuchannels
                        const visuChls = this.DA_DataManager.getVisuChannels(this.graphinstanceid);
                        if ((visuChls !== undefined) && (visuChls.size > 0)) {
                            for (let [visuChlID, channel] of visuChls) {
                                //check if channel active
                                if (channel.channelActive.value === 1) {
                                    if (channel.chlReference !== undefined) {
                                        //check if it is about this channel
                                        if ((channel.chlReference.stationNumber === e.detail.stationNumber) && (channel.chlReference.chlID === e.detail.chlID)) {
                                            //draw curves
                                            this._requestAnimation();
                                        }
                                    }
                                    else {
                                        this._log('_registerEvents(): invalid channel reference', 'ERROR');
                                    }                                
                                }
                            }
                        }
                    } 
                }                
            }
        }, 'jsEvent', window);

        //*******************************************************************************************************
        //** unit has changed
        //*******************************************************************************************************
        this.sigAddEventListener('_DA_UNIT_CHANGED', (e) => {
            if (e.detail !== undefined) {
                //check if component already rendered
                if (this.rendered) {
                    //check if datamanager was initialized
                    if (this.DA_DataManager !== undefined) {
                        //get visuchannels
                        const visuChls = this.DA_DataManager.getVisuChannels(this.graphinstanceid);
                        if ((visuChls !== undefined) && (visuChls.size > 0)) {
                            for (let [visuChlID, channel] of visuChls) {
                                //check if channel active and scale of channel is shown
                                if ((channel.channelActive.value === 1) && ((channel.scaleType.value === -1) ||(channel.scaleType.value === visuChlID))) {
                                    if (channel.chlReference !== undefined) {
                                        //check if it is about this channel
                                        if ((channel.chlReference.stationNumber === e.detail.stationNumber) && (channel.chlReference.chlID === e.detail.chlID)) {
                                            //redraw everything cause size may change
                                            this._redrawAll();
                                        }
                                    }
                                    else {
                                        this._log('_registerEvents(): invalid channel reference (unit change)', 'ERROR');
                                    }                                
                                }
                            }
                        }
                    }
                }                 
            }
        }, 'jsEvent', window); 
        
        //*******************************************************************************************************
        //** server of channel changed -> check if we need to update
        //*******************************************************************************************************
        this.sigAddEventListener('_DA_SERVER_CHANGED', (e) => {
            if (e.detail !== undefined) {
                //check if component already rendered
                if (this.rendered) {
                    //check if datamanager was initialized
                    if (this.DA_DataManager !== undefined) {
                        //get visuchannels
                        const visuChls = this.DA_DataManager.getVisuChannels(this.graphinstanceid);
                        if ((visuChls !== undefined) && (visuChls.size > 0)) {
                            for (let [visuChlID, channel] of visuChls) {
                                //check if channel active
                                if (channel.channelActive.value === 1) {
                                    if (channel.chlReference !== undefined) {
                                        //check if it is about this channel
                                        if ((channel.chlReference.stationNumber === e.detail.stationNumber) && (channel.chlReference.chlID === e.detail.chlID)) {
                                            //redraw everything cause size may change
                                            this._redrawAll();
                                            break;
                                        }
                                    }
                                    else {
                                        this._log('_registerEvents(): invalid channel reference (server change)', 'ERROR');
                                    }                                
                                }
                            }
                            //check if osci mode
                            if (this.mode === 'osci') {
                                //check if trigger infos are available
                                if (this.triggerInfos !== undefined) {
                                    //check if it is the channel the oscitrigger is configured
                                    if ((this.triggerInfos.stationNumber === e.detail.stationNumber) && (this.triggerchannel === e.detail.chlID)) {
                                        //update threshold
                                        this._calcThreshold();
                                    }
                                }
                            }
                        }
                    } 
                }                
            }
        }, 'jsEvent', window);       

        //*******************************************************************************************************
        //** response to get pre and postsamples
        //*******************************************************************************************************
        this.sigAddEventListener('_DA_UPDATE_PREPOST', (e) => {
            if (e.detail !== undefined) {
                //check if in osci mode
                if (this.mode === 'osci') {
                    //check if trigger infos available
                    if (this.triggerInfos !== undefined) {
                        //check if pre and postsamples of our oscitrigger
                        if (this.triggerInfos.CRC === e.detail.triggerCRC) {
                            //set pre and postsamples
                            this.preSamples = e.detail.preSamples;
                            this.postSamples = e.detail.postSamples;
                            //check if already rendered to calculate horizontal position
                            if (this.rendered) {
                                //get width of drawing area
                                const drawWidth = this.$.drawingArea.offsetWidth;
                                //get trigger channel to calculate presamples
                                const triggerChannel = this.DA_DataManager.getChannelByID(this.triggerInfos.stationNumber, this.triggerchannel);
                                if (triggerChannel !== undefined) {
                                    //calculate recording time
                                    const recordingTime = (triggerChannel.maxNoOfValues - 1) * triggerChannel.sampleInterval;
                                    //avoid observer
                                    if (this.horizontalpos !== Math.round((this.preSamples - (recordingTime/2)) * (drawWidth/this.viewTimeRange))) {
                                        this.avoidHorizontalObserver = true;
                                        //calculate pre and post samples back to horizontal position
                                        this.horizontalpos = Math.round((this.preSamples - (recordingTime/2)) * (drawWidth/this.viewTimeRange));   
                                    } 
                                }
                                else {
                                    this._log('_registerEvents(): failed to get triggerchannel', 'ERROR');
                                }                                            
                            }                    
                        }                          
                    }                
                }            
            }
        }, 'jsEvent', window);         

        //*******************************************************************************************************
        //** response to get threshold
        //*******************************************************************************************************
        this.sigAddEventListener('_DA_UPDATE_THRESHOLD', (e) => {
            if (e.detail !== undefined) {
                //check if in osci mode
                if (this.mode === 'osci') {  
                    //check if trigger infos available
                    if (this.triggerInfos !== undefined) {              
                        //check if threshold of our oscitrigger
                        if (this.triggerInfos.CRC === e.detail.triggerCRC) {
                            //get trigger channel
                            const triggerChannel = this.DA_DataManager.getChannelByID(this.triggerInfos.stationNumber, this.triggerchannel);
                            if (triggerChannel !== undefined) {
                                //set threshold
                                this.threshold = this._getCastedThreshold(triggerChannel, e.detail.threshold);
                                //check if already rendered to calculate trigger level
                                if (this.rendered) {
                                    this._calcTriggerLevel();    
                                }                                   
                            }              
                        }
                    }     
                }                          
            }
        }, 'jsEvent', window); 

        //*******************************************************************************************************
        //** graph got zoomed so check if group is the same and zoom too
        //*******************************************************************************************************
        this.sigAddEventListener('_DA_ZOOM_GRAPHVIEW', (e) => {
            if (e.detail !== undefined) {
                //only zoom if event wasn't received from ourselve and group is the same
                if ((e.detail.graphInstID !== this.graphinstanceid) && (this.group !== undefined) && (e.detail.group === this.group)) {
                    //only evaluate zoom in trend cause zooming is done with valueperdivision in osci mode
                    if (this.mode === 'trend') {                    
                        //zoom but without triggering again
                        this.zoom(e.detail.centerPos, e.detail.zoomFactorX, e.detail.zoomFactorY, true);  
                    }                      
                }                 
            }
        }, 'jsEvent', window); 

        //*******************************************************************************************************
        //** get timeranges
        //*******************************************************************************************************
        this.sigAddEventListener('_DA_GET_TIMERANGES', (e) => {
            if (e.detail !== undefined) {             
                //check if channel ment for graph instance id
                if (this.graphinstanceid === e.detail.graphInstID) {
                    //check if displayed timerange available 
                    if (this.displayedTimeRange !== undefined) {
                        //send displayed timerange
                        this._displayedTimeRangeChanged();                             
                    }      
                    //check if timerange available 
                    if (this.timeRange !== undefined) {
                        //send displayed timerange
                        this._timeRangeChanged();                             
                    }                       
                }                                             
            }
        }, 'jsEvent', window);

        //*******************************************************************************************************
        //** update of scroll position by navigation bar
        //*******************************************************************************************************
        this.sigAddEventListener('_DA_UPDATE_SCROLL_POSITION', (e) => {
            if (e.detail !== undefined) {             
                //check if channel ment for graph instance id
                if (this.graphinstanceid === e.detail.graphInstID) {
                    //set scrollposition
                    this.scrollvalue = e.detail.scrollPosition;                     
                }                                             
            }
        }, 'jsEvent', window); 

        //*******************************************************************************************************
        //** register for pointer up event to reset pinch zoom state
        //*******************************************************************************************************
        this.sigAddEventListener('up', (e) => {
            //reset pinch zoom state
            this.pinchZoomCoords = undefined;
        }, 'polymerEvent');        

        if ((this.evtMediaApi !== undefined) && (this.evtApi !== undefined)) {
            //-----------------------------------------------------------------------------------------------------
            //-- subscribe for autoset -> triggered from lvd
            //-----------------------------------------------------------------------------------------------------  
            this.autosetHandler = this.evtMediaApi.subscribeWithId(this.majorID, this.autoset.minorID, (ma, mi, data) => {
                //handle autoset function
                this._autoset(data);
            });

            //*******************************************************************************************************
            //** register for language change event
            //*******************************************************************************************************
            this.sigAddEventListener('LANGUAGE_CHG', () => {
                //check if component already rendered
                if (this.rendered) {
                    //redraw all cause at least y axis has to be redrawn which may have effect on size of drawing area
                    this._redrawAll();
                }    
            }, 'runtimeEvent', this);
        }   
        else {
            this._log('_registerEvents(): event apis not ready', 'ERROR');
        }  


        //register for visu channel property events
        this._registerVisuChlEvents();

    }

    /**
     * method is used to register for events if properties of visu channel changes
     */
    _registerVisuChlEvents() {

        //*******************************************************************************************************
        //** selected channel changed
        //*******************************************************************************************************
        this.sigAddEventListener('_DA_CHANNEL_REFERENCE_CHANGED', (e) => {
            if (e.detail !== undefined) {
                //check if already rendered
                if (this.rendered === true) {                
                    //check if ment for graph
                    if (e.detail.graphInstID === this.graphinstanceid) {
                        //autoset to center new channels data
                        this._autoset([e.detail.graphInstID, e.detail.visuChlID]);                     
                    }
                }             
            }
        }, 'jsEvent', window);        

        //*******************************************************************************************************
        //** channel active changed
        //*******************************************************************************************************
        this.sigAddEventListener('_DA_CHANNEL_ACTIVE_CHANGED', (e) => {
            if (e.detail !== undefined) {
                //check if already rendered
                if (this.rendered === true) {                
                    //check if ment for graph
                    if (e.detail.instID === this.graphinstanceid) {
                        //redraw curves
                        this._redrawAll();                        
                    }
                }             
            }
        }, 'jsEvent', window); 

        //*******************************************************************************************************
        //** position of scale changed
        //*******************************************************************************************************
        this.sigAddEventListener('_DA_SCALE_POSITION_CHANGED', (e) => {
            if (e.detail !== undefined) {
                //check if already rendered
                if (this.rendered === true) {                
                    //check if ment for graph
                    if (e.detail.instID === this.graphinstanceid) {
                        //redraw curves
                        this._redrawAll();                        
                    }
                }             
            }
        }, 'jsEvent', window); 
        
        //*******************************************************************************************************
        //** split view changed
        //*******************************************************************************************************
        this.sigAddEventListener('_DA_SPLIT_VIEW_CHANGED', (e) => {
            if (e.detail !== undefined) {
                //check if already rendered
                if (this.rendered === true) {                
                    //check if ment for graph
                    if (e.detail.instID === this.graphinstanceid) {
                        //redraw curves
                        this._requestAnimation();                        
                    }
                }             
            }
        }, 'jsEvent', window);

        //*******************************************************************************************************
        //** position of unit changed
        //*******************************************************************************************************
        this.sigAddEventListener('_DA_UNIT_POSITION_CHANGED', (e) => {
            if (e.detail !== undefined) {
                //check if already rendered
                if (this.rendered === true) {                
                    //check if ment for graph
                    if (e.detail.instID === this.graphinstanceid) {
                        //redraw curves
                        this._redrawAll();                        
                    }
                }             
            }
        }, 'jsEvent', window); 
        
        //*******************************************************************************************************
        //** scale type changed
        //*******************************************************************************************************
        this.sigAddEventListener('_DA_SCALE_TYPE_CHANGED', (e) => {
            if (e.detail !== undefined) {
                //check if already rendered
                if (this.rendered === true) {                
                    //check if ment for graph
                    if (e.detail.instID === this.graphinstanceid) {
                        //in osci mode we have to check if triggerlevel needs to be calculated
                        if (this.mode === 'osci') {
                            if (this.DA_DataManager !== undefined) {
                                const chlReference = this.DA_DataManager.getChannelReference(e.detail.instID, e.detail.chlID);
                                if (chlReference !== undefined) {
                                    if ((chlReference.stationNumber === this.triggerInfos.stationNumber) && (chlReference.chlID === this.triggerchannel)) {
                                        //recalculate triggerlevel
                                        this._calcTriggerLevel();
                                    }
                                }
                            }
                            else {
                                this._log('_registerVisuChlEvents(): invalid datamanager instance (min changed)', 'ERROR');
                            }
                        }                        
                        //redraw curves
                        this._redrawAll();                        
                    }
                }             
            }
        }, 'jsEvent', window);
        
        //*******************************************************************************************************
        //** min value of scale changed
        //*******************************************************************************************************
        this.sigAddEventListener('_DA_MIN_SCALE_CHANGED', (e) => {
            if (e.detail !== undefined) {
                //check if already rendered
                if (this.rendered === true) {                
                    //check if ment for graph
                    if (e.detail.instID === this.graphinstanceid) {
                        //in osci mode we have to check if triggerlevel needs to be calculated
                        if (this.mode === 'osci') {
                            if (this.DA_DataManager !== undefined) {
                                const chlReference = this.DA_DataManager.getChannelReference(e.detail.instID, e.detail.chlID);
                                if (chlReference !== undefined) {
                                    if ((chlReference.stationNumber === this.triggerInfos.stationNumber) && (chlReference.chlID === this.triggerchannel)) {
                                        //recalculate triggerlevel
                                        this._calcTriggerLevel();
                                    }
                                }
                            }
                            else {
                                this._log('_registerVisuChlEvents(): invalid datamanager instance (min changed)', 'ERROR');
                            }
                        }                        
                        //redraw curves
                        this._redrawAll();                        
                    }
                }             
            }
        }, 'jsEvent', window); 

        //*******************************************************************************************************
        //** max value of scale changed
        //*******************************************************************************************************
        this.sigAddEventListener('_DA_MAX_SCALE_CHANGED', (e) => {
            if (e.detail !== undefined) {
                //check if already rendered
                if (this.rendered === true) {                
                    //check if ment for graph
                    if (e.detail.instID === this.graphinstanceid) {
                        //in osci mode we have to check if triggerlevel needs to be calculated
                        if (this.mode === 'osci') {
                            if (this.DA_DataManager !== undefined) {
                                const chlReference = this.DA_DataManager.getChannelReference(e.detail.instID, e.detail.chlID);
                                if (chlReference !== undefined) {
                                    if ((chlReference.stationNumber === this.triggerInfos.stationNumber) && (chlReference.chlID === this.triggerchannel)) {
                                        //recalculate triggerlevel
                                        this._calcTriggerLevel();
                                    }
                                }
                            }
                            else {
                                this._log('_registerVisuChlEvents(): invalid datamanager instance (max changed)', 'ERROR');
                            }
                        }
                        //redraw curves
                        this._redrawAll();                        
                    }
                }             
            }
        }, 'jsEvent', window);
        
        //*******************************************************************************************************
        //** selected color changed
        //*******************************************************************************************************
        this.sigAddEventListener('_DA_SELECTED_COLOR_CHANGED', async (e) => {
            if (e.detail !== undefined) {
                //check if already rendered
                if (this.rendered === true) {                
                    //check if ment for graph
                    if (e.detail.instID === this.graphinstanceid) {
                        //no y axis needed in osci mode
                        if (this.mode === 'trend') {
                            //redraw axis and curves
                            await this._drawYAxis();                             
                        }                         
                        this._requestAnimation();                       
                    }
                }             
            }
        }, 'jsEvent', window);
        
        //*******************************************************************************************************
        //** line width changed
        //*******************************************************************************************************
        this.sigAddEventListener('_DA_LINE_WIDTH_CHANGED', (e) => {
            if (e.detail !== undefined) {
                //check if already rendered
                if (this.rendered === true) {                
                    //check if ment for graph
                    if (e.detail.instID === this.graphinstanceid) {
                        //redraw curves
                        this._redrawAll();                        
                    }
                }             
            }
        }, 'jsEvent', window);        

        //*******************************************************************************************************
        //** line style changed
        //*******************************************************************************************************
        this.sigAddEventListener('_DA_LINE_STYLE_CHANGED', (e) => {
            if (e.detail !== undefined) {
                //check if already rendered
                if (this.rendered === true) {                
                    //check if ment for graph
                    if (e.detail.instID === this.graphinstanceid) {
                        //redraw curves
                        this._redrawAll();                        
                    }
                }             
            }
        }, 'jsEvent', window); 

        //*******************************************************************************************************
        //** 0 line changed
        //*******************************************************************************************************
        this.sigAddEventListener('_DA_ZERO_LINE_ACTIVE_CHANGED', (e) => {
            if (e.detail !== undefined) {
                //check if already rendered
                if (this.rendered === true) {                
                    //check if ment for graph
                    if (e.detail.instID === this.graphinstanceid) {
                        //redraw curves
                        this._redrawAll();                        
                    }
                }             
            }
        }, 'jsEvent', window);      

        //*******************************************************************************************************
        //** 0 line offset changed
        //*******************************************************************************************************
        this.sigAddEventListener('_DA_ZERO_LINE_OFFSET_CHANGED', (e) => {
            if (e.detail !== undefined) {
                //check if already rendered
                if (this.rendered === true) {                
                    //check if ment for graph
                    if (e.detail.instID === this.graphinstanceid) {
                        //in osci mode we have to check if triggerlevel needs to be calculated
                        if (this.mode === 'osci') {
                            if (this.DA_DataManager !== undefined) {
                                const chlReference = this.DA_DataManager.getChannelReference(e.detail.instID, e.detail.chlID);
                                if (chlReference !== undefined) {
                                    if ((chlReference.stationNumber === this.triggerInfos.stationNumber) && (chlReference.chlID === this.triggerchannel)) {
                                        //recalculate triggerlevel
                                        this._calcTriggerLevel();
                                    }
                                }
                            }
                            else {
                                this._log('_registerVisuChlEvents(): invalid datamanager instance (max changed)', 'ERROR');
                            }
                        }                        
                        //redraw curves
                        this._redrawAll();                        
                    }
                }             
            }
        }, 'jsEvent', window);  
        
        //*******************************************************************************************************
        //** value per division changed
        //*******************************************************************************************************
        this.sigAddEventListener('_DA_VALUE_PER_DIVISION_CHANGED', (e) => {
            if (e.detail !== undefined) {
                //only needed in osci mode
                if (this.mode === 'osci') {
                    //check if already rendered
                    if (this.rendered === true) {                
                        //check if ment for graph
                        if (e.detail.instID === this.graphinstanceid) {
                            //redraw curves
                            this._redrawAll();                        
                        }                        
                    }
                }             
            }
        }, 'jsEvent', window);          
    }

    /**
     * required properties are ready so login to datamanager
     */
    sigOnRequiredPropertiesReady() {
        super.sigOnRequiredPropertiesReady();

        //login to datamanager cause some information need to be dispatched just like mode and divisions
        dispatchEvent(new CustomEvent('_DA_LOGIN_GRAPH_VIEW', {
            detail: {
                graphInstID: this.graphinstanceid,
                graphViewInfos: {
                    mode: this.mode,
                    xDivisions: this.xdivisions,
                    yDivisions: this.ydivisions                    
                }
            }
        }));

        if (this.mode === 'osci') {
            //get trigger infos
            this._getTriggerInfos().then((infos) => {
                this.triggerInfos = Object.assign(new Object(), infos);
                //get the trigger threshold
                this._getThreshold(this.triggerInfos.CRC);
                //set pre and post samples
                if (this.rendered) {
                    this._calcPrePostSamples();
                }
            },(error) => this._log('sigOnRequiredPropertiesReady(): failed to get trigger infos -> ' + error));
        }
        //calculate viewable timerange
        this.viewTimeRange = this.xdivisions * this._getµsPerDivision();
        //add rendercallback
        this._addRenderCB();         
    }

    /**
     * 
     */
    ready() {
       super.ready();
       //as onRequiredPropertiesReady won't work in designer we use the ready interface to trigger drawing
       if (this.isdesignmode) {
           SigPolymer.afterNextRender(this, () => {
               this._rendered();
           });
       } 
    }
    
    /**
     * render callback
     */
    _rendered() {
        //set info that control was rendered
        this.rendered = true;
        //allocate buffers for oscimode
        if ((!this.isdesignmode) && (this.mode === 'osci')) {
            //check if trigger infos available
            if (this.triggerInfos !== undefined) {
                //set pre and post sampples
                this._calcPrePostSamples();                    
            }
            //check if threshold available to calculate trigger level
            if (this.threshold !== undefined) {
                //calculate triggerlevel
                this._calcTriggerLevel();
            }
        }
        this._redrawAll();
    }

    /**
     * refreshes all axes and curves
     */
    async _redrawAll() {
        //check if redraw all active
        if (this.redrawAllActive !== true) {
            this.redrawAllActive = true;
            //no y or x axis needed in osci mode
            if (this.mode === 'trend') {                
                if (!this.isdesignmode) {
                    //predraw x axis to define height of x axis area which has effects on size of y axis
                    this._drawXAxis(true);  
                    try {                       
                        //draw y axis -> needs to be done first cause it might change the size of the x axis
                        await this._drawYAxis();  
                    }
                    catch (error) {
                        this._log('_rendered(): drawing x or y axis failed ' + error, 'ERROR');
                        return;
                    }                                                    
                }
                //draw x axis
                this._drawXAxis(false);                                               

            }                    
            //draw grid
            this._drawGrid();
            //request animation frame for drawing curves
            this._requestAnimation(); 
            this.redrawAllActive = false;
            //check if redrawing should be done again cause it was requested during redraw
            if (this.redrawAgain) {
                this.redrawAgain = false;
                this._redrawAll();
            }           
        }
        else {
            //set flag so redraw gets started new if finished
            this.redrawAgain = true;
        }
    }

    /**
     * render callback error
     */
     _renderCBError() {
        //error occured -> try again
        this._addRenderCB();
        this._log("_renderCBError(): error render callback", "ERROR");
    }

    /**
     * helper method to do not need to code this multiple times
     */
    _addRenderCB() {
        //check if vm api ready
        if (this.vmApi !== undefined) {
            //if properties are ready add rendercallback
            this.vmApi.addRenderCallback(this, () => {
                this._rendered();
            }, () => {
                this._renderCBError();
            });            
        }
        else {
            this._log('_addRenderCB(): vm api not ready', 'ERROR');
        }   
    }

    /**
     * draw gridlines
     */
    _drawGrid() {
        //check fi already rendered
        if (this.rendered === true) {
            //check if gridlines need to be drawn
            if (this.gridlinestyle !== 'none') {
                //get canvas context
                let context = this.$.gridCanvas.getContext('2d');
                //get dimensions
                const gridWidth = this.$.drawingArea.offsetWidth;
                const gridHeight = this.$.drawingArea.offsetHeight;
                //clear old data
                context.canvas.width = gridWidth;
                context.canvas.height = gridHeight;

                context.moveTo(0, 0);
                context.beginPath();

                //check for valid x divisions
                if (this.xdivisions > 0) {
                    let x = 0;
                    let limitOffset = 0;
                    for (let i = 0; i <= this.xdivisions; i++) {
                        if (i === 0) {
                            limitOffset = 1;
                        }
                        else if (i === this.xdivisions) {
                            limitOffset = -1;
                        }
                        else {
                            limitOffset = 0;
                        }
                        context.moveTo(x + limitOffset, 0);
                        context.lineTo(x + limitOffset, gridHeight);
                        x += gridWidth / this.xdivisions;
                    }            
                }

                //check for valid y divisions
                if (this.ydivisions > 0) {
                    let y = 0;
                    let limitOffset = 0;
                    for (let i = 0; i <= this.ydivisions; i++) {
                        if (i === 0) {
                            limitOffset = 1;
                        }
                        else if (i === this.xdivisions) {
                            limitOffset = -1;
                        }
                        else {
                            limitOffset = 0;
                        }                        
                        context.moveTo(0, y + limitOffset);
                        context.lineTo(gridWidth, y + limitOffset);
                        y += gridHeight / this.ydivisions;
                    }            
                }

                //set line style
                context.setLineDash(this._getGridLineDash(this.gridlinestyle));

                //set line color
                context.strokeStyle = this._getGridColor();
                context.stroke();
                context.closePath();             
            }          
        }
    }

    /**
     * method is used to return the color of the grid
     * @returns 
     */
    _getGridColor() {
        return this.gridcolor;
    }

    /**
     * method is used to return the dash of the lines for grid
     * @param {*} gridLineStyle - line style which was set as property of graphs control
     */
    _getGridLineDash(gridLineStyle) {
        //check linestyle
        switch (gridLineStyle) {
            
            //******************************************************************************************************************
            //** solid -> no space
            //******************************************************************************************************************
            case 'solid':
                return [];

            //******************************************************************************************************************
            //** dotted -> same distance of space and line
            //******************************************************************************************************************
            case 'dotted':
                return [1, 1];             

            //******************************************************************************************************************
            //** dashed -> longer lines bigger spaces
            //******************************************************************************************************************
            case 'dashed':
                return [10, 5]; 
        }
    }

    /**
     * number of x divisions changed -> redraw
     */
    _xDivisionsChanged() {
        //can only be changed in designmode so avoid calling in normal mode
        if (this.isdesignmode) {
            //no y or x axis needed in osci mode
            if (this.mode === 'trend') { 
                //also x axis has to been drawn new
                this._drawXAxis();
            }                          
            this._drawGrid();
        }
    }

    /**
     * number of y divisions changed -> redraw
     */
     _yDivisionsChanged() {
        //can only be changed in designmode so avoid calling in normal mode
        if (this.isdesignmode) {         
            this._drawGrid();
        }    
    }

    /**
     * line style of gridlines changed -> redraw grid
     */
     _gridLineStyleChanged() {
        //can only be changed in designmode so avoid calling in normal mode
        if (this.isdesignmode) {         
            this._drawGrid();
        }    
    }    

    /**
     * color of gridlines changed -> redraw grid
     */
     _gridColorChanged() {
        //draw grid
        this._drawGrid();
        if (!this.isdesignmode) {
            //redraw all cause when grid gets redrawn curves will get to background which doesn't look very nice
            this._requestAnimation(); 
        }
    } 

    /**
     * color for invalid values was changed
     */
    _invalidPathColorChanged() {
        if (!this.isdesignmode) {
            //redraw curves
            this._requestAnimation();
        }
    }

    /**
     * control was resized in designer
     */
    designerOnBoundsChanged() {
        SigPolymer.afterNextRender(this, () => {
            //no y or x axis needed in osci mode
            if (this.mode === 'trend') { 
                //also x axis has to been drawn new
                this._drawXAxis();
            }           
            this._drawGrid();
        });
    }    

    /**
     * draw x scale
     * @param {*} preDraw - false .. draw x axis, true .. only set size of x axis
     */
    _drawXAxis(preDraw = false) {
        //check if already rendered
        if (this.rendered === true) {
            //check if x axis should be drawn
            if (this.hidexscale !== 1) {
                //get context
                let context = this.$.xAxisCanvas.getContext('2d');
                //first of all get all texts so we can calculate the size of the canvas
                //check for valid x divisions
                if (this.xdivisions > 0) {            
                    //do one loop to get all texts and width of texts
                    //the number of labels which are done depends on the text widths so we cannot do everything in one loop
                    let texts = [];
                    let textWidths = 0;
                    //get width of drawing area
                    const drawAreaWidth = this.$.drawingArea.offsetWidth;                    
                    //calculate step width of divisions
                    const step = drawAreaWidth / this.xdivisions;

                    //set font property cause it is needed for measuring text correctly
                    //wrapper method for overwriting
                    const font = this._getXAxisFont();
                    if (font !== undefined) {
                        context.font = font.fontSize + " " + font.fontFamily;

                        //get latest displayed time
                        let actTime;
                        if (this.displayedTimeRange !== undefined) {
                            actTime = this.displayedTimeRange.start;
                        }
                        else {
                            let date = new Date();
                            actTime = Math.floor((date.getTime() - (date.getTimezoneOffset() * 60000))*1000);                        
                        }
                        let latestTime;
                        if (this.timeRange !== undefined) {
                            latestTime = this.timeRange.maxTime;
                        }
                        let maxHeight = 0;              
                        for (let i=0;i<=this.xdivisions;i++) {
                            //get text -> switch order cause displaying from right to left
                            texts[i] = this._getXAxisText(context, this.xdivisions - i, step, actTime, latestTime);
                            //check for multiple line text
                            if (texts[i].length > 0) {
                                let maxWidth = 0;
                                let actHeight = 0; 
                                for (let j=0;j<texts[i].length;j++) {
                                    //get maximum width of texts
                                    if (texts[i][j].textSize.width > maxWidth) {
                                        maxWidth = texts[i][j].textSize.width;
                                    }
                                    //get height of both labels
                                    actHeight += texts[i][j].textSize.actualBoundingBoxAscent + texts[i][j].textSize.actualBoundingBoxDescent;
                                    //if it is more than one text we have to add a offset between the texts
                                    if (j > 0) {
                                        actHeight += this.labelOffset;
                                    }
                                }
                                //increase text width
                                textWidths += maxWidth; 
                                //check for maximum height overall
                                if (actHeight > maxHeight) {
                                    maxHeight = actHeight;
                                }                                                       
                            }
                            else {
                                this._log('_drawXAxis(): invalid text', 'ERROR');                            
                            }
                        }

                        //only set height in predraw -> height is checked and manipulated in drawYaxis so do not overwrite if really drawing
                        if (preDraw === true) {
                            //calculate maximum height of canvas
                            const xAxisHeight = this.baseLineOffset + this.sepLineSize + maxHeight + (2 * this.labelOffset);
                            this.$.xAxisArea.style.height = xAxisHeight + 'px';                        
                        }
                        else {
                            //reset old data by setting size of canvas
                            context.canvas.width = this.$.xAxisArea.offsetWidth;
                            context.canvas.height = this.$.xAxisArea.offsetHeight;             

                            //get color used for x axis
                            const color = this._getXAxisColor();
                            if (color !== undefined) {
                                //set props for canvas after setting size cause this resets everything       
                                context.font = font.fontSize + " " + font.fontFamily;
                                context.fillStyle = color;
                                context.textAlign = "center";  

                                //draw base line of x axis
                                context.beginPath();
                                context.moveTo(0, this.baseLineOffset);
                                context.lineTo(drawAreaWidth, this.baseLineOffset);
                                context.strokeStyle = color;
                                context.stroke();
                                context.closePath();

                                //now we have to check how many labels can be written to keep the same distance between those
                                const avgTextWidth = textWidths / (this.xdivisions + 1);
                                //calculate minimum number of steps between labels
                                //multiplied by 2 cause first and last texts are shifted
                                let minSteps = Math.ceil(avgTextWidth * 2/ step);
                                let labelStep;
                                //now we have to check for a possible number of steps to keep the same distance
                                while (minSteps <= this.xdivisions/2) {
                                    if ((this.xdivisions % minSteps) === 0) {
                                        //found yeepee
                                        labelStep = minSteps;
                                        break;
                                    } 
                                    minSteps ++;
                                }
                                //check if labelstep was found
                                if (labelStep === undefined) {
                                    labelStep = this.xdivisions;
                                }
                                context.beginPath();
                                let x = 0;
                                let limitOffset = 0;
                                for (let i=0;i<=this.xdivisions;i++) {
                                    if (i === 0) {
                                        limitOffset = 1;
                                    }
                                    else if (i === this.xdivisions) {
                                        limitOffset = -1;
                                    }
                                    else {
                                        limitOffset = 0;
                                    }
                                    //draw line
                                    context.moveTo(x + limitOffset, this.baseLineOffset);
                                    context.lineTo(x + limitOffset, this.baseLineOffset + this.sepLineSize);
                                    context.strokeStyle = this.textcolor;
                                    context.stroke();
                                    context.closePath();

                                    //draw text
                                    if ((i % labelStep) === 0) {
                                        let labelOffset = 0;
                                        let labelYPos = 0;
                                        //multiline texts possible
                                        if (texts[i].length > 0) {
                                            //set offset for first text
                                            labelYPos = this.baseLineOffset + this.sepLineSize + this.labelOffset;
                                            for (let j=0;j<texts[i].length;j++) {
                                                if (i == 0) {
                                                    labelOffset = (texts[i][j].textSize.width / 2) + 3;
                                                }
                                                if (i == parseInt(this.xdivisions)) {
                                                    labelOffset = -(texts[i][j].textSize.width / 2)  - 3;
                                                }
                                                labelYPos += texts[i][j].textSize.actualBoundingBoxAscent
                                                context.fillText(texts[i][j].text, x + labelOffset, labelYPos);
                                                //set offset for next text
                                                labelYPos += this.labelOffset + texts[i][j].textSize.actualBoundingBoxDescent;                                      
                                            }
                                        }
                                    }

                                    //get to next postion
                                    x += step;                     
                                }                                 
                            }
                            else {
                                this._log('_drawXAxis(): get color failed', 'ERROR');
                            }                        
                        }
                    }            
                    else {
                        this._log('_drawXAxis(): get font failed', 'ERROR');
                    }
                }                
            }            
        }
    }

    /**
     * method is used to return the font used for x axis texts
     */
     _getXAxisFont() {
        const compStyle = getComputedStyle(this);
        return {
            fontFamily: compStyle.fontFamily,
            fontSize: compStyle.fontSize
        }
    }

    /**
     * method is used to return the color used for x axis
     */
    _getXAxisColor() {
        return this.textcolor;
    }    

    /**
     * property hide x scale changed
     */
    async _hideXScaleChanged() {
        if (this.rendered) {
            //no y or x axis needed in osci mode
            if (this.mode === 'trend') {              
                if (!this.isdesignmode) {
                    //predraw x axis to define height of x axis area which has effects on size of y axis
                    this._drawXAxis(true);  
                    try { 
                        //draw y axis -> needs to be done first cause it might change the size of the x axis
                        await this._drawYAxis();
                    }
                    catch (error) {
                        this._log('_hideXScaleChanged(): drawing x or y axis failed ' + error, 'ERROR');
                        return;
                    }                              
                }
                //draw x axis
                this._drawXAxis(false);                                               
            } 
            this._drawGrid();            
        }
    }

    /**
     * text color changed so redraw axis
     */
    _textColorChanged() {
        //no y or x axis needed in osci mode
        if (this.mode === 'trend') {          
            this._drawXAxis();
        }    
    }

    /**
     * build text for x axis seperations
     * @param {*} context - context of canvas
     * @param {*} division - division
     * @param {*} step - offset each division
     * @param {*} actTime - timestamp of the first division
     * @param {*} latestTime - timestamp of the latest record
     * @returns 
     */
    _getXAxisText(context, division, step, actTime, latestTime) {
        //check for valid inputs
        if ((context !== undefined) && (division !== undefined) && (step !== undefined)) {
            //return texts
            let texts = [];
            //init object
            let text = {
                text: undefined,
                textSize: undefined
            }
            //check if in design mode
            if (this.isdesignmode) {
                text.text = 'x' + division.toString();
                //get size of text
                text.textSize = context.measureText(text.text); 
                texts.push(Object.assign(new Object(), text));               
            }
            else {
                //check for valid acttime
                if (actTime !== undefined) {
                    //calc time offset to actual division
                    let timeOffset = (division * this._getµsPerDivision());
                    if ((this.zoomfactorx !== undefined) && (parseFloat(this.zoomfactorx) > 0)) {
                        timeOffset /= this.zoomfactorx;
                    }
                    //evaluate format
                    if ((this.timeformat !== 0) || (this.dateformat !== 0)) {
                        //sub time per division to get date time for entry
                        const newTime = actTime - timeOffset
                        const dateTime = new Date(Math.floor(newTime / 1000));
                        const microOffset = newTime % 1000;
                        //first check if date should be written -> date always on top of label
                        if (this.dateformat !== 0) {
                            text.text = this._getDate(this.dateformat, dateTime);
                            text.textSize = context.measureText(text.text);
                            texts.push(Object.assign(new Object(), text));
                        }
                        //check if time should be written
                        if (this.timeformat !== 0) {
                            text.text = this._getTime(this.timeformat, dateTime);
                            text.textSize = context.measureText(text.text);
                            texts.push(Object.assign(new Object(), text));                       
                        }                        
                    }
                    else {
                        //get unit text
                        let unitText = this._getUnitText();
                        let time;
                        if (latestTime !== undefined) {
                            time = -(latestTime - (actTime - timeOffset));
                            //convert time to correct unit
                            time = this._convertTime(time);
                        }
                        else {
                            time = -(division * this.timedivision);
                        }
                        text.text = time.toFixed(2) + unitText;
                        //get size of text
                        text.textSize = context.measureText(text.text); 
                        texts.push(Object.assign(new Object(), text));
                    }
                }
                else {
                    this._log('_getXAxisText(): invalid acttime', 'ERROR');
                }
            }  
            return texts;                     
        }
        else {
            this._log('_getXAxisText(): invalid inputs', 'ERROR');
        }
    }

    /**
     * method is used to convert a time in microseconds into used format
     * @param {*} time 
     */
    _convertTime(time) {
        if (time !== undefined) {
            //check for unit
            switch (this.timedivisionunit) {
            
                //*****************************************************************************
                //** time is given in µs
                //*****************************************************************************
                case 0:
                    return time;
    
                //*****************************************************************************
                //** time is given in ms
                //*****************************************************************************
                case 1:
                    return time / 1000;                
    
                //*****************************************************************************
                //** time is given in sec
                //*****************************************************************************
                case 2:
                    return time / 1000000;
    
                //*****************************************************************************
                //** time is given in min
                //*****************************************************************************
                case 3:
                    return time / 60000000;  
    
                //*****************************************************************************
                //** time is given in hours
                //*****************************************************************************
                case 4:
                    return time / 3600000000;
    
                //*****************************************************************************
                //** time is given in days
                //*****************************************************************************
                case 5:
                    return time / 86400000000;                  
            }            
        }
        else {
            this._log('_getXAxisText(): invalid time', 'ERROR');
        }
    }

    /**
     * get the date in correct format
     * @param {*} dateFormat - date formate
     * @param {*} latestTime - unix timestamp which should be converted to date
     */
    _getDate(dateFormat, latestTime) {
        //check for valid inputs
        if ((latestTime !== undefined) && (dateFormat !== undefined)) {
            //build date
            const date = new Date(latestTime);
            switch (dateFormat) {

                //**************************************************************************************
                //** 1 -> DD.MM
                //**************************************************************************************
                case 1:
                    return String(date.getUTCDate()).padStart(2, '0') +
                           '.' +
                           String(date.getUTCMonth()+1).padStart(2, '0');
                        
                //**************************************************************************************
                //** 2 -> DD.MM.YY
                //**************************************************************************************
                case 2:
                    return String(date.getUTCDate()).padStart(2, '0') +
                           '.' +
                           String(date.getUTCMonth()+1).padStart(2, '0') + 
                           '.' +
                           String(date.getUTCFullYear()).slice(-2);                           

                //**************************************************************************************
                //** 3 -> DD.MM.YYYY
                //**************************************************************************************
                case 3:
                    return String(date.getUTCDate()).padStart(2, '0') +
                           '.' +
                           String(date.getUTCMonth()+1).padStart(2, '0') + 
                           '.' +
                           String(date.getUTCFullYear()); 

                //**************************************************************************************
                //** 4 -> YYYY-MM-DD
                //**************************************************************************************
                case 4:
                    return String(date.getUTCFullYear()) +
                           '-' +
                           String(date.getUTCMonth()+1).padStart(2, '0') +
                           '-' +
                           String(date.getUTCDate()).padStart(2, '0');
                        
                //**************************************************************************************
                //** 5 -> YY-MM-DD
                //**************************************************************************************
                case 5:
                    return String(date.getUTCFullYear()).slice(-2) +
                           '-' +
                           String(date.getUTCMonth()+1).padStart(2, '0') +
                           '-' +
                           String(date.getUTCDate()).padStart(2, '0');                        

                //**************************************************************************************
                //** 6 -> YY-MM
                //**************************************************************************************
                case 6:
                    return String(date.getUTCFullYear()).slice(-2) +
                           '-' +
                           String(date.getUTCMonth()+1).padStart(2, '0');                  
            } 
        }
        else {
            this._log('_getDate(): invalid inputs', 'ERROR');
        }       
    }       

    /**
     * get the time in correct format
     * @param {*} timeFormat - time formate
     * @param {*} latestTime - unix timestamp which should be converted to date
     */
     _getTime(timeFormat, latestTime) {
        //check for valid inputs
        if ((latestTime !== undefined) && (timeFormat !== undefined)) {
            //build date
            const date = new Date(latestTime);
            switch (timeFormat) {

                //**************************************************************************************
                //** 1 -> HH:MM
                //**************************************************************************************
                case 1:
                    return String(date.getUTCHours()).padStart(2, '0') +
                           ':' +
                           String(date.getUTCMinutes()).padStart(2, '0');
                        
                //**************************************************************************************
                //** 2 -> HH:MM:SS
                //**************************************************************************************
                case 2:
                    return String(date.getUTCHours()).padStart(2, '0') +
                           ':' +
                           String(date.getUTCMinutes()).padStart(2, '0') +
                           ':' +
                           String(date.getUTCSeconds()).padStart(2, '0');                   

                //**************************************************************************************
                //** 3 -> MM:SS
                //**************************************************************************************
                case 3:
                    return String(date.getUTCMinutes()).padStart(2, '0') +
                           ':' +
                            String(date.getUTCSeconds()).padStart(2, '0');                 
            } 
        }
        else {
            this._log('_getDate(): invalid inputs', 'ERROR');
        }       
    }  

    /**
     * gets the text for the x axis unit
     */
    _getUnitText() {
        switch (this.timedivisionunit) {
            
            //*****************************************************************************
            //** time is given in µs
            //*****************************************************************************
            case 0:
                return 'µs';

            //*****************************************************************************
            //** time is given in ms
            //*****************************************************************************
            case 1:
                return 'ms';                

            //*****************************************************************************
            //** time is given in sec
            //*****************************************************************************
            case 2:
                return 'sec';

            //*****************************************************************************
            //** time is given in min
            //*****************************************************************************
            case 3:
                return 'min';  

            //*****************************************************************************
            //** time is given in hours
            //*****************************************************************************
            case 4:
                return 'h';

            //*****************************************************************************
            //** time is given in days
            //*****************************************************************************
            case 5:
                return 'day';                  
        }
    }

    /**
     * get channel data and draw them
     */
    async _drawYAxis() {
        //return promise
        return (new Promise(async (resolve, reject) => {
            //check if already rendered
            if (this.rendered === true) {
                //no need to draw y axis in designmode
                if (!this.isdesignmode) {
                    //get visu channels
                    if (this.DA_DataManager !== undefined) {            
                        //check if draw y axis active to avoid multiple call
                        if (this.drawYAxisActive !== true) {
                            //set info that drawing y axis is active now
                            this.drawYAxisActive = true;
                            let channels = this.DA_DataManager.getVisuChannels(this.graphinstanceid);
                            if (channels !== undefined) {
                                //check how many channels available
                                if (channels.size > 0) {
                                    //get height of container
                                    const containerHeight = this.$.graphsContainer.offsetHeight;
                                    const containerWidth = this.$.graphsContainer.offsetWidth;
                                    //variables for size of canvas
                                    let sizeAxis1 = 0;
                                    let sizeAxis2 = 0;
                                    let offset;
                                    //get canvas context of both y axis areas
                                    let contextArea1 = this.$.yAxis1Canvas.getContext('2d');
                                    let contextArea2 = this.$.yAxis2Canvas.getContext('2d');
                                    //initial set canvas to maximum size of 1/2 drawing area, everything else is not sensefull
                                    //we have to assume size cause we don't know yet
                                    contextArea1.canvas.height = containerHeight;
                                    contextArea1.canvas.width = containerWidth/2;
                                    contextArea2.canvas.height = containerHeight;
                                    contextArea2.canvas.width = containerWidth/2;
                                    //set values for canvas which will not change
                                    //wrapper function to create possibility for overwriting
                                    let font = this._getYAxisFont();
                                    if (font !== undefined) {
                                        contextArea1.textBaseline = 'center';
                                        contextArea1.font = font.fontSize + " " + font.fontFamily;
                                        contextArea1.textAlign = "center"; 
                                        contextArea2.textBaseline = 'center';
                                        contextArea2.font = font.fontSize + " " + font.fontFamily;
                                        contextArea2.textAlign = "center";
                                        //first of all we have to get unit texts which should be displayed to define height of y axis
                                        const unitTextInfos = await this._getYScaleUnitTexts(contextArea1, channels); 
                                        if (unitTextInfos !== undefined) {
                                            //set size of placeholder for unit at top position
                                            this.$.unitPlaceHolder.style.height = unitTextInfos.topHeight + 'px';
                                            //check if size of x axis needs to be corrected
                                            let xAxisHeight = this.$.xAxisArea.offsetHeight;
                                            if (xAxisHeight < unitTextInfos.bottomHeight) {
                                                xAxisHeight = unitTextInfos.bottomHeight;
                                                this.$.xAxisArea.style.height = xAxisHeight + 'px';
                                            }
                                            //calculate height of drawing area for axis
                                            const scaleDrawingHeight = containerHeight - (unitTextInfos.topHeight + xAxisHeight);
                                            //set drawingHeight
                                            this.$.drawingArea.style.height = scaleDrawingHeight + 'px';
                                            //get step width of divisions
                                            const step = scaleDrawingHeight / this.ydivisions;
                                            let noOfScales = 0;
                                            //sort channels by visuchlid starting with first scale id
                                            const sortedChannels = this._sortChannels(channels);
                                            //go through channels and draw y axis
                                            for (let [visuChlID, channel] of sortedChannels) {
                                                //get real channel
                                                if (channel.chlReference !== undefined) {
                                                    const realChl = this.DA_DataManager.getChannelByID(channel.chlReference.stationNumber, channel.chlReference.chlID);
                                                    if (realChl !== undefined) {
                                                        //get datapoint of recorded server
                                                        const dp = this.datapointApi.getDatapoint(realChl.srcVarID);
                                                        if (dp !== null) {
                                                            //get floating point info
                                                            const isFloatingPoint = dp.isFloatingPoint();
                                                            //check if axis should be drawn -> scaleType -1 = own axis
                                                            //also if referencing to our own axis it should be drawn
                                                            if ((channel.scaleType.value === -1) || (channel.scaleType.value === visuChlID)) {
                                                                //check if number of scales already reached
                                                                if ((this.noofscales === undefined) || (noOfScales < this.noofscales)) {
                                                                    let context;
                                                                    //check which side should be used
                                                                    if (channel.scalePos.value === 0) {
                                                                        //draw scale on right side
                                                                        context = contextArea2;
                                                                        //set offset to last size of canvas
                                                                        offset = sizeAxis2;
                                                                    }
                                                                    else {
                                                                        //draw scale on left side
                                                                        context = contextArea1;
                                                                        //set offset to last size of canvas
                                                                        offset = sizeAxis1;                                        
                                                                    }
                                                                    //go through channels and check if linked to this scale
                                                                    for (let [linkedVisuChlID, linkedChannel] of channels) {
                                                                        if ((linkedChannel.channelActive.value === 1) && (linkedChannel.scaleType.value === visuChlID)) {
                                                                            //channel scale is linked to this scale so draw base line with seperators
                                                                            const color = this._getYAxisColor(linkedChannel);
                                                                            if (color !== undefined) {
                                                                                //stroke style will not change anymore so directly set in context as well as the fillstyle
                                                                                context.strokeStyle = color;
                                                                                context.fillStyle = color;
                                                                                //draw the base line of the yaxis -> return is the new size of the canvas
                                                                                offset = this._drawBaseLineAxis(context, scaleDrawingHeight, offset, unitTextInfos.topHeight);
                                                                                let stepOffset = unitTextInfos.topHeight;
                                                                                let limitOffset = 0;
                                                                                for (let j=0;j<=this.ydivisions;j++) {
                                                                                    //offset in case of seperation lines at limit
                                                                                    if (j === 0) {
                                                                                        limitOffset = 1;
                                                                                    }
                                                                                    else if (j === this.ydivisions) {
                                                                                        limitOffset = -1;
                                                                                    }
                                                                                    else {
                                                                                        limitOffset = 0;
                                                                                    }
                                                                                    //draw seperation line
                                                                                    this._drawSeperationLineAxis(context, stepOffset + limitOffset, offset);
                                                                                    //get to next division
                                                                                    stepOffset += step;                                                
                                                                                } 
                                                                                //increase offset
                                                                                offset += this.sepLineSize;                                                                                
                                                                            }
                                                                            else {
                                                                                this._log('_drawYAxis(): failed to get y axis color of linked channel', 'ERROR');
                                                                            }                                                                            
                                                                        }
                                                                    }
                                                                    //wrapper method to get color used for y axis
                                                                    const color = this._getYAxisColor(channel);
                                                                    if (color !== undefined) {
                                                                        //stroke style will not change anymore so directly set in context as well as the fillstyle
                                                                        context.strokeStyle = color;
                                                                        context.fillStyle = color;
                                                                        //draw the base line of the yaxis -> return is the new size of the canvas
                                                                        offset = this._drawBaseLineAxis(context, scaleDrawingHeight, offset, unitTextInfos.topHeight);
                                                                        
                                                                        //draw unit texts
                                                                        let unitTextOffset = offset;
                                                                        let unitText = unitTextInfos.unitTexts.get(visuChlID);
                                                                        if (unitText !== undefined) {
                                                                            let yPos = 0;
                                                                            let height = unitTextInfos.topHeight;
                                                                            if (channel.unitPos.value === 2) {
                                                                                //set offset to bottom
                                                                                yPos = unitTextInfos.topHeight + scaleDrawingHeight;
                                                                                height = unitTextInfos.bottomHeight;
                                                                            }
                                                                            unitTextOffset = this._drawUnitText(channel, context, offset, yPos, height, unitText);
                                                                        }    
                                                                        //go on drawing seperation lines and texts
                                                                        let maxOffset = 0;
                                                                        let stepOffset = unitTextInfos.topHeight;
                                                                        let actOffset;
                                                                        let sepLineOffset;
                                                                        //get unit info of channel source to format scale values correctly
                                                                        const unitInfo = this._getUnitInfo(channel);
                                                                        //check if zoomfactor already defined
                                                                        if ((this.zoomfactory === undefined) || (parseFloat(this.zoomfactory) == 0)) {
                                                                            this.zoomfactory = 1;
                                                                        }
                                                                        //check if yscrollvalue already defined
                                                                        if (this.yScrollValue === undefined) {
                                                                            this.yScrollValue = 0;
                                                                        }
                                                                        //get scale values
                                                                        const scaleValues = this._getScaleValues(visuChlID, channel, sortedChannels, scaleDrawingHeight);
                                                                        if (scaleValues !== undefined) {
                                                                            //calculate value range we are able to display
                                                                            const valueRange = ((scaleValues.maxValue - scaleValues.minValue) / this.zoomfactory);
                                                                            //calculate value step
                                                                            const valueStep = valueRange/this.ydivisions;
                                                                            //calc axis factor
                                                                            const yAxisFactor = (valueRange / scaleDrawingHeight);
                                                                            //calculate maximum value of scale
                                                                            const maxValue = ((this.yScrollValue * yAxisFactor) + scaleValues.minValue) + (this.ydivisions * valueStep);
                                                                            let limitOffset = 0;
                                                                            for (let j=0;j<=this.ydivisions;j++) {
                                                                                //offset in case of seperation lines at limit
                                                                                if (j === 0) {
                                                                                    limitOffset = 1;
                                                                                }
                                                                                else if (j === this.ydivisions) {
                                                                                    limitOffset = -1;
                                                                                }
                                                                                else {
                                                                                    limitOffset = 0;
                                                                                }
                                                                                //draw seperation line
                                                                                sepLineOffset = this._drawSeperationLineAxis(context, stepOffset + limitOffset, offset);
                                                                                try {
                                                                                    //draw texts
                                                                                    actOffset = await this._drawSeperationTexts(context, channel, unitInfo, j, stepOffset, sepLineOffset, maxValue, valueStep, isFloatingPoint);
                                                                                    //check for maximum offset of texts
                                                                                    if (actOffset > maxOffset) {
                                                                                        maxOffset = actOffset;
                                                                                    }
                                                                                    //get to next division
                                                                                    stepOffset += step;                                                
                                                                                }
                                                                                catch (error) {
                                                                                    this._log('_drawYAxis(): draw seperationtext failed ' + error, 'ERROR');
                                                                                    //finished so check if it should be drawn new
                                                                                    this._drawYAxisFinished();
                                                                                    return;
                                                                                }
                                                                            }

                                                                            //check if unittext is bigger than rest
                                                                            if (unitTextOffset > maxOffset) {
                                                                                maxOffset = unitTextOffset;
                                                                            }

                                                                            //set new offset
                                                                            offset = maxOffset;

                                                                            //set new size of canvas
                                                                            if (channel.scalePos.value === 0) {
                                                                                sizeAxis2 = offset;
                                                                            }    
                                                                            else {
                                                                                sizeAxis1 = offset;
                                                                            }

                                                                            //increase number of drawn scales
                                                                            noOfScales += 1;                                                            
                                                                        }
                                                                        else {
                                                                            this._log('_drawYAxis(): failed to get scale values', 'ERROR');
                                                                        }
                                                                    }
                                                                    else {
                                                                        this._log('_drawYAxis(): failed to get y axis color', 'ERROR');
                                                                    }
                                                                }  
                                                                else {
                                                                    //maximum number of scale reached so quit here
                                                                    break;
                                                                }
                                                            }  

                                                        }
                                                        else {
                                                            this._log('_drawYAxis(): failed to get datapoint', 'ERROR');
                                                        }
                                                    }
                                                    else {
                                                        this._log('_drawYAxis(): failed to get real channel', 'ERROR');
                                                    }
                                                }
                                                else {
                                                    this._log('_drawYAxis(): invalid real channel reference', 'ERROR');
                                                }                                             
                                            }                         
                                            //now set widths of areas
                                            this.$.yAxisArea1.style.width = sizeAxis1 + 'px';
                                            this.$.yAxisArea2.style.width = sizeAxis2 + 'px';
                                            this.$.stage.style.width = (containerWidth - sizeAxis1 - sizeAxis2) + 'px';
                                            //save content before setting size cause it will get cleared if setting the size
                                            let contextHelper = this.$.helperCanvas.getContext('2d');
                                            //check if yaxis area 1 should be drawn
                                            if (sizeAxis1 > 0) {
                                                //clear helper canvas by setting size
                                                contextHelper.canvas.width = sizeAxis1;
                                                contextHelper.canvas.height = containerHeight;
                                                contextHelper.drawImage(contextArea1.canvas, 0, 0); 
                                                //set sizes of canvas
                                                contextArea1.canvas.width = sizeAxis1;
                                                contextArea1.canvas.height = containerHeight;  
                                                //this area needs to be mirrored to be displayed correctly
                                                contextArea1.translate(sizeAxis1, 0);
                                                contextArea1.scale(-1, 1);
                                                //restore helper canvas 
                                                contextArea1.drawImage(contextHelper.canvas, 0, 0);          
                                            }

                                            //check if yaxis area 2 should be drawn
                                            if (sizeAxis2 > 0) {
                                                //clear helper canvas by setting size
                                                contextHelper.canvas.width = sizeAxis2;
                                                contextHelper.canvas.height = containerHeight;                        
                                                contextHelper.drawImage(contextArea2.canvas, 0, 0);
                                                contextArea2.canvas.width = sizeAxis2;
                                                contextArea2.canvas.height = containerHeight;
                                                //restore helper canvas
                                                contextArea2.drawImage(contextHelper.canvas, 0, 0);
                                            } 
                                        }
                                        else {
                                            this._log('_drawYAxis(): failed to get unit text info', 'ERROR');
                                        }  
                                    }
                                    else {
                                        this._log('_drawYAxis(): get font failed', 'ERROR');
                                    }                                                  
                                }
                                else {
                                    this._log('_drawYAxis(): invalid channels size', 'ERROR');
                                }
                            }
                            else {    
                                //channels not ready yet -> we will get updated if new available
                            }    
                            //resolve actual promise
                            resolve();
                            //finished so check if it should be drawn new
                            this._drawYAxisFinished();
                        }
                        else {
                            //check if array already available
                            if (this.drawYResolveFnctQueue === undefined) {
                                this.drawYResolveFnctQueue = [];
                            }
                            //save resolve callback so it can be finished later
                            this.drawYResolveFnctQueue.push(resolve);                            
                        }        
                    } 
                    else {
                        this._log('_drawYAxis(): invalid datamanager reference', 'ERROR');
                    }                                   
                }                            
            }  
        }));   
    }

    /**
     * method is used to return the font used for y axis texts
     */
    _getYAxisFont() {
        const compStyle = getComputedStyle(this);
        return {
            fontFamily: compStyle.fontFamily,
            fontSize: compStyle.fontSize
        }
    }

    /**
     * method is used to return the color used for y axis
     * @param {*} channel - channel for which the color should be fetched
     */
    _getYAxisColor(channel) {
        return this._getChannelColor(channel);
    }    

    /**
     * get unit texts of channels and measure it's size
     * @param {*} context - canvas context used to measure text
     * @param {*} visuChls - map of visuchannels
     */
    async _getYScaleUnitTexts(context, visuChls) {
        //check for valid input
        if ((context !== undefined) && (visuChls !== undefined) && (visuChls.size > 0)) {
            //check if properties ready to get unit
            if ((this.propApi !== undefined) && (this.unitApi !== undefined) && (this.constApi !== undefined)) {
                let textInfo = {
                    topHeight: 0,
                    bottomHeight: 0,
                    unitTexts: new Map()
                } 
                for (let [visuChlID, channel] of visuChls) {
                    //check if channel active
                    if (channel.channelActive.value) {
                        //check if unit should be shown
                        if (channel.unitPos.value !== 0) {
                            //check if axis should be drawn -> scaleType -1 = own axis
                            //also if referencing to our own axis it should be drawn
                            if ((channel.scaleType.value === -1) || (channel.scaleType.value === visuChlID)) {
                                //get the unit info
                                const unitInfo = this._getUnitInfo(channel);
                                let unit;
                                if (unitInfo !== undefined) {
                                    if (unitInfo.src === this.constApi.PROP_SRC_UNIT) {
                                        unit = this.unitApi.getUnit(unitInfo.value);
                                    }
                                    else if (unitInfo.src === this.constApi.PROP_SRC_SCHEME) {
                                        try {
                                            const schemeVals = unitInfo.value.split(':');
                                            unit = await this.propApi.doScheme(schemeVals[1], schemeVals[0], false);                                            
                                        }
                                        catch (error) {
                                            this._log('_getYScaleUnitTexts(): failed to get unit -> ' + error, 'ERROR');
                                        }
                                    }
                                    else if (unitInfo.src === this.constApi.PROP_SRC_SWITCH_PROP) {
                                        //scheme and unit configured
                                        if ((unitInfo.children !== undefined) && (unitInfo.children.length > 0)) {
                                            let unitScheme;
                                            for (let i=0;i<unitInfo.children.length;i++) {
                                                //check for unit
                                                if (unitInfo.children[i].src === this.constApi.PROP_SRC_UNIT) {
                                                    unit = this.unitApi.getUnit(unitInfo.children[i].value);
                                                }
                                                else if (unitInfo.children[i].src === this.constApi.PROP_SRC_SCHEME) {
                                                    unitScheme = unitInfo.children[i].value;
                                                }
                                            }
                                            if (unitScheme !== undefined) {
                                                try {
                                                    const schemeVals = unitScheme.split(':');
                                                    const schemeUnit = await this.propApi.doScheme(schemeVals[1], schemeVals[0], false); 
                                                    //handle fallback
                                                    if (schemeUnit) unit = schemeUnit;                                                    
                                                }
                                                catch (error) {
                                                    this._log('_getYScaleUnitTexts(): failed to get unit -> ' + error, 'ERROR');
                                                }                                               
                                            }                           
                                        }
                                        else {
                                            this._log('_getYScaleUnitTexts(): invalid constellation', 'ERROR');
                                        }
                                    }
                                }    
                                let unitText = ' - ';
                                let textHeight = 5 * this.labelOffset;
                                //check if unit was found
                                if (unit !== undefined) {
                                    //get unit text
                                    unitText = unit.getUnitText();
                                    //measure text height
                                    let textSize = context.measureText(unitText);
                                    //calculate text height
                                    textHeight = textSize.actualBoundingBoxAscent + textSize.actualBoundingBoxDescent + (2 * this.labelOffset);                                    
                                }
                                textInfo.unitTexts.set(visuChlID, unitText);

                                //check where text belongs
                                if (channel.unitPos.value === 1) {
                                    //top
                                    if (textInfo.topHeight < textHeight) {
                                        textInfo.topHeight = textHeight;
                                    }
                                } 
                                else {
                                    //bottom
                                    if (textInfo.bottomHeight < textHeight) {
                                        textInfo.bottomHeight = textHeight;
                                    }                                            
                                }
                            }                          
                        }
                    }
                }
                return textInfo;
            }
            else {
                this._log('_getYScaleUnitTexts(): apis not ready', 'ERROR');
            }                  
        }
        else {
            this._log('_getYScaleUnitTexts(): invalid input', 'ERROR');
        }
    }

    /**
     * draws the unit text at the desired position
     * @param {*} channel - channel for which the unit text should be drawn
     * @param {*} context - drawing context of canvas
     * @param {*} xOffset - x offset where text should be drawn
     * @param {*} yOffset - y offset where text should be drawn
     * @param {*} unitText - unit text which should be drawn
     */
    _drawUnitText(channel, context, xOffset, yOffset, height, unitText) {
        //check for valid inputs
        if ((channel !== undefined) && (context !== undefined) && (xOffset !== undefined) && 
            (yOffset !== undefined) && (height !== undefined) && (unitText !== undefined)) {
            //measure text
            const textSize = context.measureText(unitText);
            //calculate position of text
            let textXPos = xOffset + (textSize.width / 2);
            //calculate text y position
            const textYPos = yOffset + height/2 + (textSize.actualBoundingBoxAscent - textSize.actualBoundingBoxDescent)/2
            //save state of canvas
            context.save();            
            //check if value should be displayed on left side -> in this case value has to be mirrored
            if (channel.scalePos.value === 1) {
                //mirror
                context.translate(textXPos, 0);
                //as we set the origin to middle of text we have to draw at 0
                textXPos = 0;
                context.scale(-1,1);    
            }              
            context.fillText(unitText, textXPos, textYPos);
            context.restore();
            //return size of text + space to next axis
            return xOffset + textSize.width + this.labelOffset;           
        }
        else {
            this._log('_drawUnitText(): invalid input', 'ERROR');
        }        
    }

    /**
     * gets called if drawing y axis has finished
     */
    _drawYAxisFinished() {
        //finish actual worked promises
        if ((this.drawYResolveFnctAct !== undefined) && (this.drawYResolveFnctAct.length > 0)) {
            for (let i=0;i<this.drawYResolveFnctAct.length;i++) {
                //call resolves
                this.drawYResolveFnctAct[0]();
            }
        }        
        //reset info that drawing y axis is active
        this.drawYAxisActive = false;
        //check if y axis drawing was requested in the meantime
        if ((this.drawYResolveFnctQueue !== undefined) && (this.drawYResolveFnctQueue.length > 0)) {
            //copy to actual worked
            this.drawYResolveFnctAct = [...this.drawYResolveFnctQueue];
            //reset queue
            this.drawYResolveFnctQueue = [];
            this._drawYAxis();
        }
    }

    /**
     * draws the line of the y axis
     * @param {*} context - context of canvas where line should be drawn
     * @param {*} height - height of drawing area
     * @param {*} xOffset - x offset where line should be drawn
     * @param {*} yOffset - y offset where line should be drawn
     */
    _drawBaseLineAxis(context, height, xOffset, yOffset) {
        //check for valid inputs
        if ((context !== undefined) && (height !== undefined) && (xOffset !== undefined) && (yOffset !== undefined)) {
            context.beginPath();
            //so full line is visible
            xOffset += this.baseLineOffset;
            context.moveTo(xOffset, yOffset);
            context.lineTo(xOffset, yOffset + height);
            context.stroke();
            context.closePath();
            //add offset for seperation lines
            return xOffset;
        }
        else {
            this._log('_drawBaseLineAxis(): invalid inputs', 'ERROR');
        }
    }

    /**
     * draws the seperation lines
     * @param {*} context - canvas context
     * @param {*} stepOffset - vertical offset of axis
     * @param {*} offset - horizontal offset
     */
    _drawSeperationLineAxis(context, stepOffset, offset) {
        //check for valid inputs
        if ((context !== undefined) && (stepOffset !== undefined) && (offset !== undefined)) {
            context.beginPath();
            context.moveTo(offset, stepOffset);
            context.lineTo(offset + this.sepLineSize, stepOffset);
            context.stroke();
            context.closePath();
            //add offset of seperation lines + space to text
            return offset + this.sepLineSize + 2;
        }
        else {
            this._log('_drawSeperationLineAxis(): invalid inputs', 'ERROR');
        }
    }

    /**
     * method is used to get the unit information for formating values
     * @param {*} channel - visu channel reference
     * @returns 
     */
    _getUnitInfo(channel) {
        //check for valid channel
        if (channel !== undefined) {
            //we need to get the real channel for formating the value in the correct unit
            if (channel.chlReference !== undefined) {
                //check if datamanager available
                if (this.DA_DataManager !== undefined) {
                    //check if datapoint api ready cause we need to get the source datapoint of the channel for the unit
                    if (this.datapointApi !== undefined) {
                        let realChl = this.DA_DataManager.getChannelByID(channel.chlReference.stationNumber, channel.chlReference.chlID);
                        if (realChl !== undefined) {
                            //get datapoint
                            let datapoint = this.datapointApi.getDatapoint(realChl.srcVarID);
                            if (datapoint !== null) {
                                //now that datapoint is available we can get the unit
                                return datapoint.getProperties().unit;
                            }
                            else {
                                this._log('_getUnitInfo(): get datapoint failed', 'ERROR');
                            }                           
                        }
                        else {
                            this._log('_getUnitInfo(): get real channel failed', 'ERROR');
                        }                           
                    }
                    else {
                        this._log('_getUnitInfo(): datapoint api not ready', 'ERROR');
                    }                  
                }
                else {
                    this._log('_getUnitInfo(): invalid datamanager reference', 'ERROR');
                } 
            }
            else {
                this._log('_getUnitInfo(): invalid channel reference', 'ERROR');
            } 
        }
        else {
            this._log('_getUnitInfo(): invalid channel', 'ERROR');
        }         
    }

    /**
     * draws the values to the seperation lines
     * @param {*} context - canvas context
     * @param {*} channel - visu channel needed for getting min/max , left/right and so on
     * @param {*} unitInfo - information of unit to foramt values
     * @param {*} division - index of division
     * @param {*} stepOffset - vertical offset of axis
     * @param {*} offset - horizonatl offset
     * @param {*} maxValue - maximum value of scale
     * @param {*} valueStep - value step each division
     * @param {*} isFloatingPoint - value is evaluated as floating point if true
     */   
    async _drawSeperationTexts(context, channel, unitInfo, division, stepOffset, offset, maxValue, valueStep, isFloatingPoint=false) {
        //check for valid inputs
        if ((context !== undefined) && (division !== undefined) && (channel !== undefined) && 
            (stepOffset !== undefined) && (offset !== undefined) && (maxValue !== undefined) && (valueStep !== undefined)) {
            //calculate value
            let value = maxValue - (division * valueStep);
            //format value by unit information
            try {
                value = await this._formatValue(value, unitInfo, isFloatingPoint);

                //get seperation text
                let text = value;
                //get size of text
                const textSize = context.measureText(text);
                //check if it is first or last seperation
                if (division === 0) {
                    //first division so display text lower
                    stepOffset += textSize.actualBoundingBoxAscent + this.labelOffset/2;
                }
                else if (division === this.ydivisions) {
                    //last division so display text higher
                    stepOffset -= textSize.actualBoundingBoxDescent + this.labelOffset/2;
                }
                else {
                    //texts need to be centered
                    stepOffset += (textSize.actualBoundingBoxAscent - textSize.actualBoundingBoxDescent)/2;
                }
                let textXPos = offset + (textSize.width / 2);
                //save state of canvas
                context.save();            
                //check if value should be displayed on left side -> in this case value has to be mirrored
                if (channel.scalePos.value === 1) {
                    //mirror
                    context.translate(textXPos, 0);
                    //as we set the origin to middle of text we have to draw at 0
                    textXPos = 0;
                    context.scale(-1,1);    
                }              
                context.fillText(text, textXPos, stepOffset);
                context.restore(); 

                //return size of text + space to next axis
                return offset + textSize.width + this.labelOffset; 
            }
            catch (error) {
                this._log('_drawSeperationTexts(): failed to format value ' + error, 'ERROR');
            }   
        }
        else {
            this._log('_drawSeperationTexts(): invalid inputs', 'ERROR');
        }
    }

    /**
     * method is used to format a value by information of unit
     * @param {*} value - value which should be converted
     * @param {*} unitInfo - unit property of the datapoint
     * @param {*} isFloatingPoint - info if value is evaluated as floating point
     */
    async _formatValue(value, unitInfo, isFloatingPoint) {
        //check for valid value -> as unitinfo is also valid if undefined in case of no unit configured, we don't question that
        if (value !== undefined) {
            //check if value needs to be formatted
            if (unitInfo !== undefined) {
                //check if apis ready for formating values
                if ((this.propApi !== undefined) && (this.constApi !== undefined)) {
                    if (unitInfo.src === this.constApi.PROP_SRC_UNIT) {
                        return await this.propApi.formatValueWithUnit(unitInfo.value, value, isFloatingPoint, true, 0);
                    }
                    else if (unitInfo.src === this.constApi.PROP_SRC_SCHEME) {
                        const schemeVals = unitInfo.value.split(':');
                        return await this.propApi.formatValueWithUnitscheme(schemeVals[1], schemeVals[0], value, 0, isFloatingPoint, true, 0, false);
                    }
                    else if (unitInfo.src === this.constApi.PROP_SRC_SWITCH_PROP) {
                        //scheme and unit configured
                        if ((unitInfo.children !== undefined) && (unitInfo.children.length > 0)) {
                            let unit = 0;
                            let unitScheme = 0;
                            for (let i=0;i<unitInfo.children.length;i++) {
                                //check for unit
                                if (unitInfo.children[i].src === this.constApi.PROP_SRC_UNIT) {
                                    unit = unitInfo.children[i].value;
                                }
                                else if (unitInfo.children[i].src === this.constApi.PROP_SRC_SCHEME) {
                                    unitScheme = unitInfo.children[i].value;
                                }
                            }
                            const schemeVals = unitScheme.split(':');
                            return await this.propApi.formatValueWithUnitscheme(schemeVals[1], schemeVals[0], value, unit, isFloatingPoint, true, 0, false);                            
                        }
                        else {
                            this._log('_formatValue(): invalid constellation', 'ERROR');
                        }
                    }
                }
                else {
                    this._log('_formatValue(): api not ready', 'ERROR');
                }                
            }
            else {
                //value doesn't need to be formatted
                return value.toFixed(2);
            }
        }
        else {
            this._log('_formatValue(): invalid value', 'ERROR');
        }
    }

    /**
     * time per division has changed so redraw axis and graph
     */
    _timeDivisionChanged() {
        //only redraw if not in designmode
        if (!this.isdesignmode) {
            //calculate timerange
            this.viewTimeRange = this.xdivisions * this._getµsPerDivision();
            //check if already rendered
            if (this.rendered) {
                if (this.mode === 'trend') {  
                    this._drawXAxis();
                }
                //redraw
                this._requestAnimation();                
            }
        }      
    }

    /**
     * time per division has changed so redraw axis and graph
     * @param {*} newValue - new unit
     * @param {*} oldValue - old unit
     */
    _timeDivisionUnitChanged(newValue, oldValue) {
        //only redraw if not in designmode
        if (!this.isdesignmode) {
            this._timeDivisionChanged();
        }      
    }



    /**
     * method gets called if time or dateformat changed
     */
    _dateTimeFormatChanged() {
        if (!this.isdesignmode) {
            //check if already rendered
            if (this.rendered) {
                if (this.mode === 'trend') {  
                    //redraw all cause height of x axis might changes and therefore all others might change in size
                    this._redrawAll();
                } 
                else {
                    this._requestAnimation();
                }                  
            } 
        }
    }

    /**
     * method can be called if new animation should be done
     */
    _requestAnimation() {
        //check if animation already requested
        if (this.animationRequested !== true) {
            //draw curves
            window.requestAnimationFrame(() => this._onAnimation());
            //set animation requested
            this.animationRequested = true;
        }
    }

    /**
     * callback function of animation frame
     */
    async _onAnimation() {
        //function gets called if requested animation frame is available
        //reset request of animation frame
        this.animationRequested = false;             
        if (!this.isdesignmode) {
            this._drawCurves();  
            //do not draw axis texts in oszi mode
            if (this.mode === 'trend') {
                //update x scale values
                this._updateXAxisTexts();
                //update scrollable state
                this._setScrollable();                
            }
            //draw cursors cause size of display area might changed
            this._drawCursor();
            //evaluate cursors
            this._evaluateCursors();                  
        }  
    }

    /**
     * draw curves of channels
     */
    _drawCurves() {
        //check if datamanager was initialized
        if (this.DA_DataManager !== undefined) {
            //get visuchannels
            const visuChls = this.DA_DataManager.getVisuChannels(this.graphinstanceid);
            if ((visuChls !== undefined) && (visuChls.size > 0)) {
                //get context of osci canvas
                let context = this.$.graphsCanvas.getContext('2d');
                //get size of drawing area
                const drawWidth = this.$.drawingArea.offsetWidth;
                const drawHeight = this.$.drawingArea.offsetHeight;
                let actViewTimeRange = this.viewTimeRange;
                //calculate zoomfactor in
                if ((this.zoomfactorx !== undefined) && (parseFloat(this.zoomfactorx) > 0)) {
                    actViewTimeRange /= this.zoomfactorx;
                }
                const xAxisFactor = actViewTimeRange / drawWidth;
                //reset canvas
                if ((drawWidth !== context.canvas.width) || (drawHeight !== context.canvas.height)) {
                    context.canvas.width = drawWidth;
                    context.canvas.height = drawHeight; 
                }
                else {
                    //simply clear drawing area
                    context.clearRect(0, 0, drawWidth, drawHeight);
                }
                //get time range of records
                this.timeRange = this._getTimeRange(visuChls);
                if (this.timeRange !== undefined) {
                    //check if displayed time range exists
                    if (this.displayedTimeRange === undefined) {
                        //calculate displayed time range
                        this.displayedTimeRange = {
                            start: undefined,
                            end: undefined
                        }                          
                    }
                    if (this.mode === 'osci') {
                        this.displayedTimeRange.end = this.timeRange.minTime + ((this.timeRange.maxTime - this.timeRange.minTime)/2) - (actViewTimeRange/2);
                        this.displayedTimeRange.start = this.displayedTimeRange.end + actViewTimeRange;
                        //check iftrigger infos available
                        if (this.triggerInfos !== undefined) {
                            //get trigger channel to calculate presamples
                            const triggerChannel = this.DA_DataManager.getChannelByID(this.triggerInfos.stationNumber, this.triggerchannel);
                            if (triggerChannel !== undefined) {
                                //calculate recording time
                                const recordingTime = (triggerChannel.maxNoOfValues - 1) * triggerChannel.sampleInterval;
                                //manipulate start time cause of horizontal position  
                                this.displayedTimeRange.start -= (this.horizontalpos * xAxisFactor) + (recordingTime/2 - this.recordedPresamples); 
                            }
                            else {
                                this._log('_drawCurves(): failed to get trigger channel');
                            }                           
                        }    
                    }
                    else {
                        this.displayedTimeRange.start = this.timeRange.maxTime;
                    }
                    //check if scroll value is valid
                    if (this.scrollvalue !== undefined) {
                        //calculate scrollable time range
                        let scrollTimeRange = (this.timeRange.maxTime - this.timeRange.minTime) - actViewTimeRange;
                        //check if scrolling is possible
                        if (scrollTimeRange > 0) {
                            //calculate scroll position in time -> scrollValue is given in permille
                            this.displayedTimeRange.start -= ((scrollTimeRange * this.scrollvalue)/1000);                                                      
                        }
                        else {
                            //reset scroll value
                            this.scrollvalue = 0;
                        }                       
                    }                        
                    //calculate end time of displayed time range
                    this.displayedTimeRange.end = this.displayedTimeRange.start - actViewTimeRange;                        
                    for (let [visuChlID, channel] of visuChls) {
                        //check if channel should be drawn
                        if (channel.channelActive.value === 1) {
                            if (channel.chlReference !== undefined) {
                                //get real channel data
                                let realChl = this.DA_DataManager.getChannelByID(channel.chlReference.stationNumber, channel.chlReference.chlID);
                                if (realChl !== undefined) {                                                          
                                    //check if entries available
                                    if (realChl.noOfEntries > 0) {
                                        //set number of entries -> gets manipulated later
                                        let noOfEntries = realChl.noOfEntries; 
                                        //get act index
                                        let actIndex = realChl.actIndex;                                                                     
                                        //check if channel can be displayed
                                        let latestEntry = (realChl.timeFirstEntry + ((realChl.noOfEntries - 1) * realChl.sampleInterval));
                                        //save original latest entry
                                        let origLatestEntry = latestEntry;
                                        //set time of first entry
                                        let firstEntry = realChl.timeFirstEntry;
                                        let oldDataPixelEnd;
                                        let firstIndex;
                                        //in osci mode the first entry is the entry at pos 0
                                        if ((this.mode === 'osci') && (realChl.actIndex > 0) && (realChl.actIndex < realChl.noOfEntries)) {
                                            //assume first entry is the one at index 0
                                            firstEntry = latestEntry - (realChl.actIndex * realChl.sampleInterval);
                                            //manipulate latest entry cause old data are drawn too
                                            latestEntry = firstEntry + (realChl.maxNoOfValues * realChl.sampleInterval);
                                            //calculate number of values before overflow
                                            const noOfOldValues = realChl.noOfEntries - realChl.actIndex;
                                            //set first index -> info is needed to skip to latest data when reaching the first one
                                            firstIndex = realChl.maxNoOfValues - noOfOldValues;
                                            //save pixel of last old data -> this will be missed to see the refresh of the data
                                            oldDataPixelEnd = ~~((this.displayedTimeRange.start - (latestEntry - (noOfOldValues * realChl.sampleInterval))) / xAxisFactor);
                                            //sub one to see the refresh point
                                            oldDataPixelEnd -= 1;
                                            //in this case we asume act index is last one
                                            actIndex = 0;
                                        }                                          
                                        //check if channel can be displayed
                                        if ((firstEntry <= this.displayedTimeRange.start) && (latestEntry >= this.displayedTimeRange.end)) {
                                            //init start entry with latest entry
                                            let startEntry = latestEntry;
                                            //check if latest entry is newer than displayed area -> in this case we draw at least one pixel outside
                                            if (latestEntry > (this.displayedTimeRange.start + xAxisFactor)) {
                                                //calculate entry to start with
                                                const timeDiffToStart = latestEntry - (this.displayedTimeRange.start + xAxisFactor);
                                                //time diff can be evaluated as entries that are not needed to be drawn
                                                //round down cause we may need one entry more otherwise we are not able to draw that extra pixel
                                                const entriesToStart = Math.trunc(timeDiffToStart / realChl.sampleInterval);
                                                startEntry -= (entriesToStart * realChl.sampleInterval);
                                                //correct index to start with
                                                actIndex -= entriesToStart;
                                                //handle overflow
                                                if (actIndex < 0) {
                                                    actIndex += realChl.maxNoOfValues;
                                                }
                                                //also sub from number of entries to draw -> saves time
                                                noOfEntries -= entriesToStart;
                                            }
                                            //calculate absolute time offset to start time of displayed area
                                            let absTimeOffset = this.displayedTimeRange.start - startEntry;
                                            let minValue;
                                            let maxValue;
                                            //get min and max scale value-> possible that channel is referencing to scale of other channel
                                            const scaleValues = this._getScaleValues(visuChlID, channel, visuChls, drawHeight);                                                
                                            if (scaleValues !== undefined) {
                                                //get scale range
                                                let scaleRange = (scaleValues.maxValue - scaleValues.minValue);
                                                //add zoomfactor
                                                if ((this.zoomfactory !== undefined) && (parseFloat(this.zoomfactory) > 0)) {
                                                    scaleRange /= this.zoomfactory;
                                                }
                                                //calculate factor for y axis
                                                const yAxisFactor = scaleRange / drawHeight;
                                                //check if scroll value available
                                                if (this.yScrollValue === undefined) {
                                                    this.yScrollValue = 0;
                                                }
                                                //information if value exceeds canvas bound
                                                let borderExceed = 'none';
                                                let lastBorderExceed = 'none';
                                                //position variables
                                                let xPos, yPosValue, yPosMinValue, yPosMaxValue, xPosOffset, oldXPosOffset, lastDrawnIndex;
                                                let minBorderExceed = 'none';
                                                let maxBorderExceed = 'none';
                                                let breakLine = false;
                                                let invalidPathActive = false;
                                                let xPosInvalidStart;
                                                //get channel color for drawing paths later on
                                                const channelColor = this._getChannelColor(channel);
                                                context.beginPath();
                                                //get through entries and draw them
                                                for (let i=0;i<noOfEntries;i++) {
                                                    //decrease to index to draw next older value
                                                    if (actIndex > 0) {
                                                        actIndex -= 1;          
                                                    }
                                                    else {
                                                        actIndex = realChl.maxNoOfValues - 1;                                                          
                                                    }
                                                    //check if we reach the actual index which means we are drawing new data now -> do not connect old and new
                                                    //data with line
                                                    if (this.mode === 'osci') {
                                                        //check if first index was reached -> in this case we have to skip to act index
                                                        if ((firstIndex !== undefined) && (actIndex === firstIndex)) {
                                                            actIndex = (realChl.actIndex - 1);
                                                            //recalculate absTimeOffset
                                                            absTimeOffset = this.displayedTimeRange.start - origLatestEntry;
                                                        }
                                                        if ((realChl.actIndex > 0) && (actIndex === (realChl.actIndex - 1))) {
                                                            //break line cause old data are drawn seperatly
                                                            breakLine = true;                                                            
                                                        }
                                                    }                                                      
                                                    xPosOffset = ~~(absTimeOffset/xAxisFactor);
                                                    //check if it is the first or last value to draw or simply if next value to draw
                                                    if (((i === 0) || (i === (noOfEntries - 1)) || (xPosOffset !== oldXPosOffset)) && 
                                                        ((oldDataPixelEnd === undefined) || (xPosOffset < oldDataPixelEnd) || (actIndex < realChl.actIndex))) {

                                                        //draw min and max first cause they belong to the old x position
                                                        //check if min and max value available to draw
                                                        if ((minValue !== undefined) && (maxValue !== undefined) &&  (xPos >= 0) && (xPos <= drawWidth)) {

                                                            //***********************************************************************************************
                                                            //** move to minvalue and check if border is exceeded
                                                            //***********************************************************************************************   
                                                            
                                                            //calc min value
                                                            yPosMinValue = ((minValue - scaleValues.minValue)/yAxisFactor) - this.yScrollValue;
                                                            //invert for drawing
                                                            yPosMinValue = (drawHeight - yPosMinValue);                                                          
                                                            //reset border exceed info
                                                            minBorderExceed = 'none';
                                                            //limit positions to border and check if it should be drawn
                                                            if (yPosMinValue > drawHeight) {
                                                                minBorderExceed = 'top';
                                                            }
                                                            else if (yPosMinValue < 0) {
                                                                minBorderExceed = 'bottom';
                                                            }                                                   
                                                            //as we do not need to draw from actual to min simply move to the point
                                                            context.lineTo(xPos, yPosMinValue);                                                                

                                                            //***********************************************************************************************
                                                            //** draw line to max value if it does not exceed the border
                                                            //***********************************************************************************************

                                                            //calc max value
                                                            yPosMaxValue = ((maxValue - scaleValues.minValue)/yAxisFactor) - this.yScrollValue;
                                                            //invert for drawing
                                                            yPosMaxValue = (drawHeight - yPosMaxValue);                                                                
                                                            //reset border exceed info
                                                            maxBorderExceed = 'none';
                                                            //limit positions to border and check if it should be drawn
                                                            if (yPosMaxValue > drawHeight) {
                                                                maxBorderExceed = 'top';
                                                            }
                                                            else if (yPosMaxValue < 0) {
                                                                maxBorderExceed = 'bottom';
                                                            }                                                     
                                                            //check if we should draw a line
                                                            if ((yPosMaxValue !== yPosMinValue) &&
                                                            ((maxBorderExceed === 'none') || (maxBorderExceed !== minBorderExceed))) {
                                                                context.lineTo(xPos, yPosMaxValue);
                                                            }
                                                        }

                                                        if (yPosValue !== undefined) {
                                                            //move to actual value to do not draw the line to next point from min or max pos
                                                            context.moveTo(xPos, yPosValue);                                                          
                                                        }

                                                        //check for invalid value
                                                        if (realChl.buffer[actIndex] !== realChl.invalidValue) {
                                                            let invalidMoveToStart = true;
                                                            //if a valid y pos value was found yet we get to this point
                                                            if ((invalidPathActive) && (yPosValue !== undefined)) {
                                                                //move to last valid point
                                                                context.moveTo(xPosInvalidStart, yPosValue);
                                                                invalidMoveToStart = false;
                                                            }

                                                            //***********************************************************************************************
                                                            //** draw actual value at new position
                                                            //***********************************************************************************************

                                                            //calculate xPos -> float values not good for performance of drawing cause subpixel need to be rendered
                                                            xPos = drawWidth - xPosOffset;                                                        

                                                            //reset information if value exceeds canvas bound
                                                            borderExceed = 'none';
                                                            //calc position of actual value
                                                            yPosValue = ((realChl.buffer[actIndex] - scaleValues.minValue)/yAxisFactor) - this.yScrollValue;
                                                            //invert for drawing
                                                            yPosValue = (drawHeight - yPosValue);
                                                            //limit positions to border
                                                            if (yPosValue > drawHeight) {
                                                                //set border exceed info
                                                                borderExceed = 'top';
                                                            }
                                                            else if (yPosValue < 0) {
                                                                //set border exceed info
                                                                borderExceed = 'bottom';                                                    
                                                            }  
                                                            
                                                            //check if we have to move to start position first
                                                            if ((invalidPathActive) && (invalidMoveToStart)) {
                                                                //move to start pos
                                                                context.moveTo(xPosInvalidStart, yPosValue);
                                                                invalidMoveToStart = false;                                                                
                                                            }

                                                            //do not draw line along border so check if last value exceeded the same border
                                                            //if oldxposoffset differs from new one more than 1 pixel we have invalid data in beetween
                                                            //which means we have to start new with line
                                                            if (((i > 0) && (breakLine === false)) && 
                                                                ((borderExceed !== lastBorderExceed) ||(borderExceed === 'none'))) {
                                                                context.lineTo(xPos, yPosValue);
                                                            }    
                                                            else {
                                                                //do not draw line along border so check if last pos was valid otherwise just move to next point
                                                                context.moveTo(xPos, yPosValue); 
                                                            }

                                                            //check if path for invalid values is active and needs to be stroked
                                                            if (invalidPathActive) {
                                                                this._finishPathDrawing(context, this.invalidpathcolor, channel.lineWidth.value);
                                                                //begin original path again
                                                                context.beginPath();
                                                                //move to valid value
                                                                context.moveTo(xPos, yPosValue);
                                                                invalidPathActive = false;
                                                            }

                                                            //save info if last pos exceeded border
                                                            lastBorderExceed = borderExceed; 
                                                            oldXPosOffset = xPosOffset; 
                                                            lastDrawnIndex = i;    
                                                        }
                                                        else {
                                                            if (invalidPathActive === false) {
                                                                //invalid value recognized so finish normal path and start invalid path
                                                                this._finishPathDrawing(context, channelColor, channel.lineWidth.value);
                                                                //begin new path for drawing "grey" line during invalid data
                                                                context.beginPath();
                                                                //if xpos is not set yet
                                                                if (xPos === undefined) {
                                                                    xPosInvalidStart = drawWidth - xPosOffset;
                                                                }
                                                                else {
                                                                    xPosInvalidStart = xPos;
                                                                }
                                                                //set info that invalid path is active
                                                                invalidPathActive = true;
                                                            }
                                                        }

                                                        //reset min and max values set to undefined cause 0 is a valid value
                                                        minValue = undefined;
                                                        maxValue = undefined;
                                                        
                                                        //check if we finished -> in osci old data need to be drawn
                                                        if ((this.mode !== 'osci') && (xPosOffset > drawWidth)) {
                                                            break;
                                                        }  
                                                        //reset break info
                                                        breakLine = false;                                                                                                                                                                         
                                                    } 
                                                    //if no point was drawn we get max and min
                                                    else {
                                                        //in osci mode we only evaluate min and max if we are not missing the last pixel with old data
                                                        //or if we have reached the new data
                                                        //oldDataPixelEnd is only defined if we have to check for new data
                                                        if ((oldDataPixelEnd === undefined) || (xPosOffset < oldDataPixelEnd) || (actIndex < realChl.actIndex)) {
                                                            //check for invalid value
                                                            if (realChl.buffer[actIndex] !== realChl.invalidValue) {
                                                                //save min and max value to draw it later on
                                                                if ( (realChl.buffer[actIndex] < minValue) || (minValue === undefined)) {
                                                                    minValue = realChl.buffer[actIndex];
                                                                }
                                                                if ((realChl.buffer[actIndex] > maxValue) || (maxValue === undefined)) {
                                                                    maxValue = realChl.buffer[actIndex];
                                                                }                                                                  
                                                            } 
                                                        }                                   
                                                    } 
                                                    //increase time by sampletime
                                                    absTimeOffset += realChl.sampleInterval;                                                                                                                
                                                }
                                                //check if it was the invalid path we have to finish of
                                                if (invalidPathActive) {
                                                    //if invalid path is still active we finish
                                                    xPos = drawWidth - xPosOffset;
                                                    context.lineTo(xPos, yPosValue);
                                                    this._finishPathDrawing(context, this.invalidpathcolor, channel.lineWidth.value);
                                                }
                                                else {
                                                    //finish path drawing
                                                    this._finishPathDrawing(context, channelColor, channel.lineWidth.value);                                                    
                                                }

                                                
                                                //finally draw zero line and triangle for marking it's position
                                                if (channel.zeroLineActive.value === 1) {   
                                                    let zeroValue = 0;
                                                    if (realChl.unitInfos !== undefined) {
                                                        zeroValue = realChl.unitInfos.zeroValue;
                                                    }                                                                                            
                                                    //calculate y position of zero line
                                                    let zeroYPos = ((zeroValue - scaleValues.minValue)/yAxisFactor) - this.yScrollValue;
                                                    //invert for drawing
                                                    zeroYPos = (drawHeight - zeroYPos);
                                                    //limit positions to border
                                                    if (zeroYPos > drawHeight) {
                                                        //draw triangle targeting downwards
                                                        context.beginPath();
                                                        context.moveTo(drawWidth - (this.triangleSize/2), drawHeight);
                                                        context.lineTo(drawWidth - this.triangleSize, drawHeight - this.triangleSize);
                                                        context.lineTo(drawWidth, drawHeight - this.triangleSize);
                                                        context.lineTo(drawWidth - (this.triangleSize/2), drawHeight);
                                                        context.fillStyle = channelColor;
                                                        context.fill();
                                                        context.closePath();                                                               
                                                    }
                                                    else if (zeroYPos < 0) {
                                                        //draw triangle targeting upwards
                                                        context.beginPath();
                                                        context.moveTo(drawWidth - (this.triangleSize/2), 0);
                                                        context.lineTo(drawWidth - this.triangleSize, 0 + this.triangleSize);
                                                        context.lineTo(drawWidth, 0 + this.triangleSize);
                                                        context.lineTo(drawWidth - (this.triangleSize/2), 0);
                                                        context.fillStyle = channelColor;
                                                        context.fill();
                                                        context.closePath();                                                            
                                                    }
                                                    else {
                                                        //draw zero line
                                                        context.beginPath();
                                                        context.moveTo(0, zeroYPos);
                                                        context.lineTo(drawWidth, zeroYPos);
                                                        context.strokeStyle = channelColor;
                                                        let lineWidth = channel.lineWidth.value;
                                                        //minimum linewidth is 1, 0 is not applied to context.lineWidth
                                                        if (lineWidth === 0) {
                                                            lineWidth = 1;
                                                        }
                                                        context.lineWidth = lineWidth;
                                                        //check linestyle
                                                        switch (channel.lineStyle.value) {
                                                            
                                                            //******************************************************************************************************************
                                                            //** solid -> no space
                                                            //******************************************************************************************************************
                                                            case 0:
                                                                context.setLineDash([]);
                                                                break;
                                            
                                                            //******************************************************************************************************************
                                                            //** dotted -> same distance of space and line
                                                            //******************************************************************************************************************
                                                            case 1:
                                                                context.setLineDash([1, 1]);
                                                                break;                    
                                            
                                                            //******************************************************************************************************************
                                                            //** dashed -> longer lines bigger spaces
                                                            //******************************************************************************************************************
                                                            case 2:
                                                                context.setLineDash([10, 5]);
                                                                break;   
                                                        }  
                                                        context.stroke();
                                                        context.closePath();
                                                        //draw triangle
                                                        context.beginPath();
                                                        let xPos = drawWidth - this.triangleSize;
                                                        context.moveTo(xPos, zeroYPos);
                                                        context.lineTo(drawWidth, zeroYPos + (this.triangleSize/2));
                                                        context.lineTo(drawWidth, zeroYPos - (this.triangleSize/2));
                                                        context.lineTo(xPos, zeroYPos);
                                                        context.fillStyle = channelColor;
                                                        context.fill();
                                                        context.closePath();  
                                                    }  
                                                }
                                            }
                                            else {
                                                this._log('_drawCurves(): get scale values failed', 'ERROR');
                                            } 
                                        }
                                    }                                         
                                }
                                else {
                                    this._log('_drawCurves(): get real channel failed', 'ERROR');
                                }                            
                            }
                            else {
                                this._log('_drawCurves(): invalid channel reference', 'ERROR');
                            }
                        }
                    }

                    //***********************************************************************************************************************
                    //** draw trigger line
                    //***********************************************************************************************************************  
                    
                    //only draw trigger in trend cause in osci mode the triggerline is always displayed
                    if (this.mode === 'trend') {
                        //get active triggers
                        let actTriggers = this.DA_DataManager.getActiveTriggers();
                        if ((actTriggers !== undefined) && (actTriggers.size > 0)) {
                            //go through stations
                            for (let [stationNumber, triggers] of actTriggers) {
                                //check if triggers available in station
                                if (triggers.length > 0) {
                                    for (let i=0;i<triggers.length;i++) {
                                        //calculate abs time in microseconds
                                        const triggerTime = (triggers[i].unixTime * 1000000) + triggers[i].microSec;                                    
                                        //check if trigger is within timerange of records
                                        if ((triggerTime >= this.timeRange.minTime) && (triggerTime <= this.timeRange.maxTime)) {
                                            //check if we need to draw trigger
                                            let triggerColor = this._checkForTrigger(visuChls, stationNumber, triggers[i]);
                                            //if triggerColor !== undefined we should draw trigger
                                            if (triggerColor !== undefined) {

                                                //calculate trigger position
                                                const xPosOffset = ~~((this.displayedTimeRange.start - triggerTime)/xAxisFactor);
                                                const xPos = drawWidth - xPosOffset;
                                                //check if trigger within drawing range
                                                if ((xPos >= 0) && (xPos <= drawWidth)) {                                        
                                                    //draw triangle
                                                    context.beginPath();
                                                    context.moveTo(xPos, 0);
                                                    context.lineTo(xPos, drawHeight);
                                                    context.strokeStyle = triggerColor;
                                                    context.lineWidth = 1;
                                                    context.setLineDash([]);  
                                                    context.stroke();
                                                    context.closePath();
                                                } 
                                                else {
                                                    //draw triangle pointing in direction trigger is located
                                                    if (xPos > drawWidth) {
                                                        //draw triangle targeting right
                                                        context.beginPath();
                                                        context.moveTo(drawWidth, 0 + (this.triangleSize/2));
                                                        context.lineTo(drawWidth - this.triangleSize, 0 + this.triangleSize);
                                                        context.lineTo(drawWidth - this.triangleSize, 0);
                                                        context.lineTo(drawWidth, 0 + (this.triangleSize/2));
                                                        context.fillStyle = triggerColor;
                                                        context.fill();
                                                        context.closePath();                                                               
                                                    }
                                                    else {
                                                        //draw triangle targeting left
                                                        context.beginPath();
                                                        context.moveTo(0, 0 + (this.triangleSize/2));
                                                        context.lineTo(0 + this.triangleSize, 0 + this.triangleSize);
                                                        context.lineTo(0 + this.triangleSize, 0);
                                                        context.lineTo(0, 0 + (this.triangleSize/2));
                                                        context.fillStyle = triggerColor;
                                                        context.fill();
                                                        context.closePath();                                                          
                                                    }                                            
                                                }                                            
                                            }                                        
                                        }
                                    }
                                }
                            }
                        }
                    }                    
                }
                //draw horizontal and trigger level anytime
                if (this.mode === 'osci') {
                    //horizontalpos is offset to middle
                    const horizontalPos = (drawWidth/2) + this.horizontalpos;
                    //draw horizontal position
                    if ((horizontalPos >= 0) && (horizontalPos <= drawWidth)) { 
                        //draw triangle at position
                        context.beginPath();
                        context.moveTo(horizontalPos, this.triangleSize);
                        context.lineTo(horizontalPos - (this.triangleSize/2), 0);
                        context.lineTo(horizontalPos + this.triangleSize/2, 0);
                        context.lineTo(horizontalPos, this.triangleSize);
                        context.fillStyle = this.triggercolor;
                        context.fill();
                        context.closePath();                              
                        //check if line should be drawn fully
                        if (this.horizontalTimeout !== undefined) {
                            //draw line
                            context.beginPath();
                            context.moveTo(horizontalPos, 0);
                            context.lineTo(horizontalPos, drawHeight);
                            context.strokeStyle = this.triggercolor;
                            context.lineWidth = 1;
                            context.setLineDash([]);  
                            context.stroke();
                            context.closePath();
                        }                                   
                    } 
                    else {
                        //draw triangle pointing in direction trigger is located
                        if (horizontalPos > drawWidth) {
                            //draw triangle targeting right
                            context.beginPath();
                            context.moveTo(drawWidth, 0 + (this.triangleSize/2));
                            context.lineTo(drawWidth - this.triangleSize, 0 + this.triangleSize);
                            context.lineTo(drawWidth - this.triangleSize, 0);
                            context.lineTo(drawWidth, 0 + (this.triangleSize/2));
                            context.fillStyle = this.triggercolor;
                            context.fill();
                            context.closePath();                                                               
                        }
                        else {
                            //draw triangle targeting left
                            context.beginPath();
                            context.moveTo(0, 0 + (this.triangleSize/2));
                            context.lineTo(0 + this.triangleSize, 0 + this.triangleSize);
                            context.lineTo(0 + this.triangleSize, 0);
                            context.lineTo(0, 0 + (this.triangleSize/2));
                            context.fillStyle = this.triggercolor;
                            context.fill();
                            context.closePath();                                                          
                        }                                            
                    }
                    
                    //draw trigger level
                    const triggerLevel = drawHeight/2 - this.triggerlevel;
                    if ((triggerLevel >= 0) && (triggerLevel <= drawHeight)) { 
                        //draw triangle at position
                        context.beginPath();
                        context.moveTo(this.triangleSize, triggerLevel);
                        context.lineTo(0, triggerLevel - (this.triangleSize/2));
                        context.lineTo(0, triggerLevel + (this.triangleSize/2));
                        context.lineTo(this.triangleSize, triggerLevel);
                        context.fillStyle = this.triggercolor;
                        context.fill();
                        context.closePath();                              
                        //check if line should be drawn fully
                        if (this.triggerLevelTimeout !== undefined) {
                            //draw line
                            context.beginPath();
                            context.moveTo(0, triggerLevel);
                            context.lineTo(drawWidth, triggerLevel);
                            context.strokeStyle = this.triggercolor;
                            context.lineWidth = 1;
                            context.setLineDash([]);  
                            context.stroke();
                            context.closePath();
                        }                                   
                    } 
                    else {
                        //draw triangle pointing in direction trigger is located
                        if (triggerLevel > drawHeight) {
                            //draw triangle targeting down
                            context.beginPath();
                            context.moveTo(0 + (this.triangleSize/2), drawHeight);
                            context.lineTo(0 + this.triangleSize, drawHeight - this.triangleSize);
                            context.lineTo(0, drawHeight - this.triangleSize);
                            context.lineTo(0 + (this.triangleSize/2), drawHeight);
                            context.fillStyle = this.triggercolor;
                            context.fill();
                            context.closePath();                                                               
                        }
                        else {
                            //draw triangle targeting up
                            context.beginPath();
                            context.moveTo(0 + (this.triangleSize/2), 0);
                            context.lineTo(0 + this.triangleSize, 0 + this.triangleSize);
                            context.lineTo(0, 0 + this.triangleSize);
                            context.lineTo(0 + (this.triangleSize/2), 0);
                            context.fillStyle = this.triggercolor;
                            context.fill();
                            context.closePath();                                                          
                        }                                            
                    } 
                }                
            }
        }         
    }

    _setScrollable() {
        //check if timerange available
        let scrollAble = 0;
        if (this.timeRange !== undefined) {
            //calculate view time range
            let actViewTimeRange = this.viewTimeRange;
            //calculate zoomfactor in
            if ((this.zoomfactorx !== undefined) && (parseFloat(this.zoomfactorx) > 0)) {
                actViewTimeRange /= this.zoomfactorx;
            }            
            //check if timerange of data bigger displayable timerange
            if ((this.timeRange.maxTime - this.timeRange.minTime) > actViewTimeRange) {
                scrollAble = 1;
            }
        }
        this.scrollable = scrollAble;
    }

    /**
     * method is used to stroke and close path with given color 
     * @param {*} context - canvas context for drawing
     * @param {*} color - color the path should be stroked with
     * @param {*} lineWidth - line width the path should be stroked with
     */
    _finishPathDrawing(context, color, lineWidth) {
        if ((context !== undefined) && (color !== undefined) && (lineWidth !== undefined)) {
            //set stroke style
            context.strokeStyle = color;
            //minimum linewidth is 1, 0 is not applied to context.lineWidth
            if (lineWidth === 0) {
                lineWidth = 1;
            }
            context.lineWidth = lineWidth;
            //we have to set line to solid -> line styles not useful for lines cause there are drawn multiple points each pixelpos
            context.setLineDash([]);                                                 
            context.stroke();
            context.closePath();
        }
        else {
            this._log('_finishPathDrawing(): invalid inputs', 'ERROR');
        }
    }

    /**
     * checks if scale references to other channel and gets min and maxvalues of it
     * @param {*} visuChlID - id of the channel for which the scale values should be fetched
     * @param {*} channel - channel for which the scale values should be fetched
     * @param {*} visuChannels - map of visuchannels
     */
    _getScaleValues(visuChlID, channel, visuChannels, drawHeight) {
        //check for valid input
        if ((visuChlID !== undefined) && (channel !== undefined) && (visuChannels !== undefined) & (drawHeight !== undefined)) {
            if (this.DA_DataManager !== undefined) {
                //get real chl
                if (channel.chlReference !== undefined) {
                    const realChl = this.DA_DataManager.getChannelByID(channel.chlReference.stationNumber, channel.chlReference.chlID);
                    if (realChl !== undefined) {                
                        //in osci mode we use value per div info
                        if (this.mode === 'osci') {
                            let zeroValue = 0;
                            if (realChl.unitInfos !== undefined) {
                                zeroValue = realChl.unitInfos.zeroValue;
                            }
                            //get value per division casted to correct datatype
                            let value = this.DA_DataManager.getCastedValue(realChl, channel.valuePerDivision);
                            //calculate value per division -> has to be the absolute difference of zero to value per division cause in case of offsets in unit the 
                            //value per division can be negative
                            const absValuePerDiv = Math.abs(value - zeroValue);
                            let scaleValues = {
                                maxValue: zeroValue + (absValuePerDiv * (this.ydivisions / 2)),
                                minValue: zeroValue - (absValuePerDiv * (this.ydivisions / 2))
                            }
                            //calculate offset of 0 line -> only evaluated if own axis is used
                            if (channel.zeroLineOffset.value !== undefined) {
                                //add value offset to min and max values
                                scaleValues.maxValue -= channel.zeroLineOffset.value;
                                scaleValues.minValue -= channel.zeroLineOffset.value;
                            }
                            return scaleValues;                             

                        }
                        else {
                            //check if we should take the values of our own scale
                            if ((channel.scaleType.value < 0) || (channel.scaleType.value === visuChlID)) {
                                //get scale values
                                //get min and max value casted to correct format
                                const scaleValues = {
                                    minValue: this.DA_DataManager.getCastedValue(realChl, channel.minScale),
                                    maxValue: this.DA_DataManager.getCastedValue(realChl, channel.maxScale)
                                }    
                                //calculate offset of 0 line -> only evaluated if own axis is used
                                if (channel.zeroLineOffset.value !== undefined) {
                                    //add value offset to min and max values
                                    scaleValues.maxValue -= channel.zeroLineOffset.value;
                                    scaleValues.minValue -= channel.zeroLineOffset.value;
                                }
                                return scaleValues;                                             
                            }
                            else {
                                //get referenced channel
                                let refChannel = visuChannels.get(channel.scaleType.value);
                                let refVisChlID = channel.scaleType.value;
                                //init array of ids for check of recursision
                                let refArray = [channel.scaleType.value];
                                if (refChannel !== undefined) {
                                    //get visu channel until reference of scale is our own
                                    while ((refChannel.scaleType.value >= 0) && (refVisChlID !== refChannel.scaleType.value)) {
                                        //check for recursision
                                        for (let i=0;i<refArray.length;i++) {
                                            if (refChannel.scaleType.value === refArray[i]) {
                                                //id detected which was already referenced -> stop
                                                this._log('_getScaleValues(): recursision detected', 'ERROR');
                                                return;                                
                                            }
                                        }
                                        //save reference of channel
                                        refArray.push(refChannel.scaleType.value);                        
                                        //take care if this channel also references to other channels scale but not our own
                                        if (refChannel.scaleType.value !== visuChlID) {
                                            refVisChlID = refChannel.scaleType.value;
                                            refChannel = visuChannels.get(refChannel.scaleType.value);
                                            //check if channel found
                                            if (refChannel === undefined) {
                                                this._log('_getScaleValues(): channel not found', 'ERROR');
                                                return;
                                            }
                                        }
                                        else {
                                            //referencing to own values
                                            refChannel = channel;
                                            break;
                                        }
                                    }
                                    if (refChannel.chlReference !== undefined) {
                                        //get real channel of referenced scale channel
                                        const refRealChl = this.DA_DataManager.getChannelByID(refChannel.chlReference.stationNumber, refChannel.chlReference.chlID);
                                        if (refRealChl !== undefined) {
                                            //get scale values
                                            let scaleValues = {
                                                minValue: this.DA_DataManager.getCastedValue(refRealChl, refChannel.minScale),
                                                maxValue: this.DA_DataManager.getCastedValue(refRealChl, refChannel.maxScale)
                                            } 
                                            //calculate offset of 0 line -> only evaluated if own axis is used
                                            if (refChannel.zeroLineOffset.value !== undefined) {
                                                //add value offset to min and max values
                                                scaleValues.maxValue -= refChannel.zeroLineOffset.value;
                                                scaleValues.minValue -= refChannel.zeroLineOffset.value;
                                            }
                                            return scaleValues;                                                                                         
                                        }
                                        else {
                                            this._log('_getScaleValues(): failed to get real channel of reference', 'ERROR');
                                        }                                        
                                    }
                                    else {
                                        this._log('_getScaleValues(): invalid channel reference', 'ERROR');
                                    }
                                }
                                else {
                                    this._log('_getScaleValues(): channel not found', 'ERROR');
                                    return;
                                }     
                            }                
                        }
                    }
                    else {
                        this._log('_getScaleValues(): failed to get real channel', 'ERROR');
                    }
                }
                else {
                    this._log('_getScaleValues(): invalid channel reference', 'ERROR');
                }                                        
            }
            else {
                this._log('_getScaleValues(): invalid datamanager instance', 'ERROR');
            }

        }
    }

    /**
     * gets the time configured for division in µs
     * @returns 
     */
    _getµsPerDivision() {
        //get time per division in µs
        switch (this.timedivisionunit) {

            //****************************************************************************************
            //** µs
            //****************************************************************************************
            case 0:
                return this.timedivision;

            //****************************************************************************************
            //** ms
            //****************************************************************************************
            case 1:
                return this.timedivision * 1000;
                
            //****************************************************************************************
            //** sec
            //****************************************************************************************
            case 2:
                return this.timedivision * 1000000;
                
            //****************************************************************************************
            //** min
            //****************************************************************************************
            case 3:

                return this.timedivision * 60000000;
                
            //****************************************************************************************
            //** hours
            //****************************************************************************************
            case 4:
                return this.timedivision * 3600000000;
                
            //****************************************************************************************
            //** days
            //****************************************************************************************
            case 5:
                return this.timedivision * 86400000000;
            
            default:
                return 0;    
        }
    }

    _updateXAxisTexts() {
        //check if times available
        if ((this.timeRange !== undefined) && (this.displayedTimeRange !== undefined)) {
            //get canvas context of x axis
            let context = this.$.xAxisCanvas.getContext('2d');
            //fonts should already be set but clear texts
            context.clearRect(0, this.baseLineOffset + this.sepLineSize, context.canvas.width, context.canvas.height - this.labelOffset);
            //calculate step width of divisions
            const step = context.canvas.width / this.xdivisions;        
            //get all texts
            let texts = [];
            let textWidths = 0;          
            for (let i=0;i<=this.xdivisions;i++) {
                //get text -> switch order cause displaying from right to left
                texts[i] = this._getXAxisText(context, this.xdivisions - i, step, this.displayedTimeRange.start, this.timeRange.maxTime);
                //check for multiple line text
                if (texts[i].length > 0) {
                    let maxWidth = 0;
                    for (let j=0;j<texts[i].length;j++) {
                        //get maximum width of texts
                        if (texts[i][j].textSize.width > maxWidth) {
                            maxWidth = texts[i][j].textSize.width;
                        }
                    }
                    //increase text width
                    textWidths += maxWidth; 
                }
                else {
                    this._log('_updateXAxisTexts(): invalid text', 'ERROR');                            
                }
            }  
            const avgTextWidth = textWidths / (this.xdivisions + 1);
            //calculate minimum number of steps between labels
            //multiplied by 2 cause first and last texts are shifted
            let minSteps = Math.ceil(avgTextWidth * 2/ step);
            let labelStep;
            //now we have to check for a possible number of steps to keep the same distance
            while (minSteps <= this.xdivisions/2) {
                if ((this.xdivisions % minSteps) === 0) {
                    //found yeepee
                    labelStep = minSteps;
                    break;
                } 
                minSteps ++;
            }
            //check if labelstep was found
            if (labelStep === undefined) {
                labelStep = this.xdivisions;
            }
            //now draw texts
            let x = 0;
            for (let i=0;i<=this.xdivisions;i++) {
                //draw text
                if ((i % labelStep) === 0) {
                    let labelOffset = 0;
                    let labelYPos = 0;
                    //multiline texts possible
                    if (texts[i].length > 0) {
                        //set offset for first text
                        labelYPos = this.baseLineOffset + this.sepLineSize + this.labelOffset;
                        for (let j=0;j<texts[i].length;j++) {
                            if (i == 0) {
                                labelOffset = (texts[i][j].textSize.width / 2) + 3;
                            }
                            if (i == parseInt(this.xdivisions)) {
                                labelOffset = -(texts[i][j].textSize.width / 2)  - 3;
                            }
                            //calculate label y pos
                            labelYPos += texts[i][j].textSize.actualBoundingBoxAscent;
                            context.fillText(texts[i][j].text, x + labelOffset, labelYPos);
                            //set offset for next text
                            labelYPos += this.labelOffset + texts[i][j].textSize.actualBoundingBoxDescent;                                      
                        }
                    }
                }
                //get to next postion
                x += step;                     
            } 
        }
        
    }

    /**
     * method is used to fetch values at the cursor positions
     */
    _evaluateCursors() {
        //check if displayed time range available -> build in drawing curves
        if ((this.displayedTimeRange !== undefined) && (this.timeRange !== undefined)) {
            //get size of drawing area
            const drawWidth = this.$.drawingArea.offsetWidth;
            const drawHeight = this.$.drawingArea.offsetHeight; 
            //check for instance of datamanager
            if (this.DA_DataManager !== undefined) {
                //get visu channels
                const visuChls = this.DA_DataManager.getVisuChannels(this.graphinstanceid);
                if (visuChls !== undefined) {
                    let cursorTimeX1, cursorTimeX2;
                    //check for active cursor
                    if ((this.x1cursoractive === 1) && (this.x1cursorvalue !== undefined)) {
                        //next thing is to get the time the cursor is placed at
                        cursorTimeX1 = this.displayedTimeRange.start - 
                                        (((this.displayedTimeRange.start - this.displayedTimeRange.end) / drawWidth) * (drawWidth - (drawWidth/2) - this.x1cursorvalue));
                    }
                    //check for active cursor
                    if ((this.x2cursoractive === 1) && (this.x2cursorvalue !== undefined)) {
                        //next thing is to get the time the cursor is placed at
                        cursorTimeX2 = this.displayedTimeRange.start - 
                                        (((this.displayedTimeRange.start - this.displayedTimeRange.end) / drawWidth) * (drawWidth - (drawWidth/2) - this.x2cursorvalue));
                    }
                    //init object for cursor infos
                    let cursorInfos = {
                        cursorTimes: {
                            unit: this.timedivisionunit,
                            latestTime: this.timeRange.maxTime,
                            x1: cursorTimeX1,
                            x2: cursorTimeX2
                        },
                        cursorValues: new Map()
                    }                                               
                    //go through visu channels and get data at position
                    for (let [visuChlID, channel] of visuChls) {
                        //check for valid reference
                        if (channel.chlReference !== undefined) {
                            //get real channel
                            const realChl = this.DA_DataManager.getChannelByID(channel.chlReference.stationNumber, channel.chlReference.chlID);
                            if (realChl !== undefined) {
                                //check if entries available
                                if (realChl.noOfEntries > 0) {
                                    //object for values
                                    let values = {
                                        x1: undefined,
                                        x2: undefined,
                                        y1: undefined,
                                        y2: undefined
                                    }
                                    //check if channel can be displayed
                                    const latestEntry = (realChl.timeFirstEntry + ((realChl.noOfEntries - 1) * realChl.sampleInterval));                                
                                    //check x1 cursor value needs to be fetched
                                    if (cursorTimeX1 !== undefined) {
                                        //always take lower entry as new one might be valid again
                                        const indexDiff = Math.ceil((latestEntry - cursorTimeX1) / realChl.sampleInterval);
                                        //check if within range
                                        if (indexDiff < realChl.noOfEntries) {
                                            //get value
                                            let index = realChl.actIndex - indexDiff;
                                            if (index < 0) {
                                                index = realChl.maxNoOfValues + index;
                                            }
                                            //do not evaluate invalid value
                                            if (realChl.buffer[index] !== realChl.invalidValue) {
                                                values.x1 = realChl.buffer[index];
                                            }
                                        }
                                    }
                                    //check x2 cursor value needs to be fetched
                                    if (cursorTimeX2 !== undefined) {
                                        //always take lower entry as new one might be valid again
                                        const indexDiff = Math.ceil((latestEntry - cursorTimeX2) / realChl.sampleInterval);
                                        //check if within range
                                        if (indexDiff < realChl.noOfEntries) {
                                            //get value
                                            let index = realChl.actIndex - indexDiff;
                                            if (index < 0) {
                                                index = realChl.maxNoOfValues + index;
                                            }
                                            //do not evaluate invalid value
                                            if (realChl.buffer[index] !== realChl.invalidValue) {
                                                values.x2 = realChl.buffer[index];
                                            }    
                                        }
                                    } 
                                    //get min and max scale value-> possible that channel is referencing to scale of other channel
                                    const scaleValues = this._getScaleValues(visuChlID, channel, visuChls, drawHeight);
                                    if (scaleValues !== undefined) {
                                        //get scale range
                                        let scaleRange = (scaleValues.maxValue - scaleValues.minValue);                                    
                                        //check if value for y1 cursor should be fetched
                                        if ((this.y1cursoractive === 1) && (this.y1cursorvalue !== undefined)) {
                                            values.y1 = scaleValues.minValue + ((scaleRange / drawHeight) * (drawHeight - drawHeight/2 - this.y1cursorvalue));
                                        } 
                                        //check if value for y1 cursor should be fetched
                                        if ((this.y2cursoractive === 1) && (this.y2cursorvalue !== undefined)) {
                                            values.y2 = scaleValues.minValue + ((scaleRange / drawHeight) * (drawHeight - drawHeight/2 - this.y2cursorvalue));
                                        }
                                    }                                          
                                    //set values in map
                                    cursorInfos.cursorValues.set(visuChlID, values);                                                                                      
                                }    
                            }
                            else {
                                this._log('_evaluateCursors(): get real channel failed', 'ERROR');
                            }                            
                        }
                        else {
                            this._log('_evaluateCursors(): invalid channel reference', 'ERROR');
                        }
                    }
                    //dispatch event distributing cursor values
                    dispatchEvent(new CustomEvent('_DA_UPDATE_CURSOR_VALUES', {
                        'detail': {
                            graphInstID: this.graphinstanceid,
                            cursorInfos: cursorInfos
                        }
                    }));
                }
                else {
                    this._log('_evaluateCursors(): get visu channels failed', 'ERROR');
                }                
            }
            else {
                this._log('_evaluateCursors(): invalid datamanager', 'ERROR');
            }
      
        }
    }

    /**
     * method is used to get the channelcolor
     * @param {*} channel - channel to get the color from
     * @returns 
     */
    _getChannelColor(channel) {
        if (channel !== undefined) {
            //get channel color
            if (channel.selectedColor.value === undefined) {
                //take default color
                return channel.defaultColor;
            }
            else {
                return this._makeColorString(channel.selectedColor.value);
            }              
        }
        else {
            this._log('_getChannelColor(): invalid channel', 'ERROR');
        }
    }

    /**
     * method is used to convert value of colorpicker to string
     * @param {*} value - value which should be converted
     * @returns 
     */
	_makeColorString(value) {
		let valueString = value.toString(16);
		let colorString = '#' + valueString.padStart(8,'0');;
		return colorString;
	}

    /**
     * is used to get min and max recorded time
     * @param {*} visuChls - map of visualisation channels
     */
    _getTimeRange(visuChls) {
        //check for valid input
        if ((visuChls !== undefined) && (visuChls.size > 0)) {
            //get time range overall displayed channels
            if (this.DA_DataManager !== undefined) {
                let timeRange = {
                    minTime: undefined,
                    maxTime: undefined
                }
                let latestEntry;
                let firstEntry;
                //loop through channels to see which one is active
                for (let [visuChlID, channel] of visuChls) {
                    if (channel.channelActive.value) {
                        //check for reference to real channel
                        if (channel.chlReference !== undefined) {
                            //try to get real channel
                            const realChl = this.DA_DataManager.getChannelByID(channel.chlReference.stationNumber, channel.chlReference.chlID);
                            if (realChl !== undefined) { 
                                //only evaluate if entries available
                                if (realChl.noOfEntries > 0) {
                                    firstEntry = realChl.timeFirstEntry;
                                    //get latest entry
                                    latestEntry = (firstEntry + ((realChl.noOfEntries - 1) * realChl.sampleInterval));                                        
                                    if (this.mode === 'osci') {
                                        //get time of entry at index 0
                                        if (realChl.actIndex > 0) {
                                            firstEntry = latestEntry - (realChl.actIndex * realChl.sampleInterval);
                                        }
                                        latestEntry = firstEntry + ((realChl.maxNoOfValues - 1) * realChl.sampleInterval)
                                    }
                                    //check for minimum
                                    if ((timeRange.minTime === undefined) || (realChl.timeFirstEntry < timeRange.minTime)) {
                                        timeRange.minTime = firstEntry;
                                    }                        
                                    //check for maximum
                                    if ((timeRange.maxTime === undefined) || (latestEntry > timeRange.maxTime)) {
                                        timeRange.maxTime = latestEntry;
                                    }                                          
                                }                       
                            }
                            else {
                                this._log('_getTimeRange(): get real channel failed', 'ERROR');
                            }
                        }
                        else {
                            this._log('_getTimeRange(): invalid channel reference', 'ERROR');
                        }                         
                    }
                }

                //now we should have valid min and max times
                if ((timeRange.minTime !== undefined) && (timeRange.maxTime !== undefined)) {
                    return timeRange;
                }
            }
        }
        else {
            this._log('_getTimeRange(): invalid visu channels', 'ERROR');
        }
    }

    /***************************************************************************************************************************
     * gets called if scroll value changed
     ***************************************************************************************************************************/      
    _scrollValueChanged(newValue) {
        if (!this.isdesignmode) {
            //check if already rendered
            if (this.rendered === true) {
                //redraw new -> evaluation of scrollposition is done in drawcurves
                this._requestAnimation();             
            }            
        }
    }
    
    /***************************************************************************************************************************
     * gets called if pinch zoom was done
     ***************************************************************************************************************************/      
    _zoomView(event) {
        //only evaluated zoom in trend cause zooming is done with valueperdivision in osci mode
        if (this.mode === 'trend') {
            //check if it is the first time we get into zoom until last up event
            if (this.pinchZoomCoords === undefined) {
                //get center point
                this.pinchZoomCoords = {
                    x: ((event.detail.touches.x[0] + event.detail.touches.x[1])/2),
                    y: ((event.detail.touches.y[0] + event.detail.touches.y[1])/2)
                }
                //schmar2: calculate distance for x and y separate
                //save distance between first coordinates
                this.pinchZoomDistStart = event.detail.dist;
                //set start zoomfactor
                this.startZoomFactorX = ((this.zoomfactorx !== undefined) && (parseFloat(this.zoomfactorx) > 0)) ? this.zoomfactorx : 1;
                this.startZoomFactorY = ((this.zoomfactory !== undefined) && (parseFloat(this.zoomfactory) > 0)) ? this.zoomfactory : 1;
            }
            else {         
                //calc current zoomfactor
                let newZoomFactor = (event.detail.dist / this.pinchZoomDistStart);
                const drawingBounds = this.$.drawingArea.getBoundingClientRect(); 
                //calculate offset positions
                const offsetPos = {
                    x: (this.pinchZoomCoords.x - drawingBounds.left),
                    y: (this.pinchZoomCoords.y - drawingBounds.top)
                }           
                //set new ratio
                this.zoom(offsetPos, this.startZoomFactorX * newZoomFactor, this.startZoomFactorY * newZoomFactor);
            }
        }    
    }

    /***************************************************************************************************************************
     * method is called if double click event was fired
     ***************************************************************************************************************************/          
    _doubleClicked(event) {
        //only evaluate double click in trend cause zooming is done with valueperdivision in osci mode
        if (this.mode === 'trend') {
            if (this.zoommode !== this.zoomOptions.none) {
                let zoomFactor = 1;
                //check if zoomed in
                if (((this.zoomfactorx === undefined) || (parseFloat(this.zoomfactorx) == 1)) &&
                    ((this.zoomfactory === undefined) || (parseFloat(this.zoomfactory) == 1))) {
                    //zoom in 4 times
                    zoomFactor = 4;
                }
                const drawingBounds = this.$.drawingArea.getBoundingClientRect(); 
                //calculate offset positions
                const offsetPos = {
                    x: (event.pageX - drawingBounds.left),
                    y: (event.pageY - drawingBounds.top)
                }
                this.zoom(offsetPos, zoomFactor, zoomFactor);  
            }
            else {
                //keep possibility to reset scroll values
                this.yScrollValue = 0;
                this.scrollvalue = 0;
                //redraw everything as scroll is evaluated in draw
                this._redrawAll();
            }
        }
    }

    /**
     * method is used to zoom in or out
     * @param {*} centerPos - x, y offset coordinates which should be centered
     * @param {*} zoomFactorX - zoomfactor which should be applied on x axis
     * @param {*} zoomFactorY - zoomfactor which should be applied on y axis
     * @param {*} avoidEvent - false .. event with zoom is dispatched; true .. event with zoom isn't dispatched
     */
    zoom(centerPos, zoomFactorX, zoomFactorY, avoidEvent = false) {
        //check for inputs
        if ((centerPos !== undefined) && (zoomFactorX !== undefined) && (zoomFactorY !== undefined)) {
                //check if zoom should be resetted
                if (zoomFactorX !== 1) {
                    //check if x zoom should be done
                    if ((this.zoommode === this.zoomOptions.both) || (this.zoommode === this.zoomOptions.onlyX)) {
                        //get boundings of drawing area
                        const drawingBounds = this.$.drawingArea.getBoundingClientRect(); 
                        //get unscaled drawingSizes cause translation is in pixel and evaluated unscaled
                        const drawWidth = this.$.drawingArea.offsetWidth;
                        //save old zoomfactor
                        const oldZoomFactorX = this.zoomfactorx;
                        //not zoomed in so zoom in
                        this.zoomfactorx = zoomFactorX;
                        //calculate position in graph
                        const xPercInGraph = (drawingBounds.width - centerPos.x) / drawingBounds.width
        
                        //calculate actual view time range
                        let actViewTimeRange = (this.viewTimeRange / oldZoomFactorX);
                        let xAxisFactor = actViewTimeRange / drawWidth; 
                        let scrollTimeRange = (this.timeRange.maxTime - this.timeRange.minTime) - actViewTimeRange;
                        const xPercPos = ((drawWidth * xPercInGraph) + ((this.scrollvalue * scrollTimeRange) / 1000) / xAxisFactor) / (drawWidth * oldZoomFactorX)

                        //calculate translation from zooming
                        const xTranslation = ((drawWidth * this.zoomfactorx) * xPercPos) - (drawWidth * xPercInGraph);

                        //calculate scrollvalue
                        actViewTimeRange = (this.viewTimeRange / this.zoomfactorx);
                        xAxisFactor = actViewTimeRange / drawWidth; 
                        scrollTimeRange = (this.timeRange.maxTime - this.timeRange.minTime) - actViewTimeRange;     
                        this.scrollvalue = (((xTranslation * xAxisFactor))/ scrollTimeRange) * 1000;
                        if (this.scrollvalue > 1000) {
                            this.scrollvalue = 1000;
                        }
                        else if (this.scrollvalue < 0) {
                            this.scrollvalue = 0;
                        }
                    }                    
  
                }
                else {
                    //only set zoomfactor if not already 1 otherwise observer will be called
                    //avoid observer
                    this.avoidZoomXObserver = true;
                    this.zoomfactorx = 1;                
                    this.scrollvalue = 0; 
                }
                //check if zoom should be resetted
                if (zoomFactorY !== 1) {
                    //check if x zoom should be done
                    if ((this.zoommode === this.zoomOptions.both) || (this.zoommode === this.zoomOptions.onlyY)) {
                        //get boundings of drawing area
                        const drawingBounds = this.$.drawingArea.getBoundingClientRect(); 
                        //get unscaled drawingSizes cause translation is in pixel and evaluated unscaled
                        const drawHeight = this.$.drawingArea.offsetHeight;
                        //save old zoomfactor
                        const oldZoomFactorY = this.zoomfactory;
                        //not zoomed in so zoom in
                        this.zoomfactory = zoomFactorY;
                        //calculate position in graph
                        const yPercInGraph = (drawingBounds.height - centerPos.y) / drawingBounds.height;

                        //calculate percentage of position in whole range
                        if (this.yScrollValue === undefined) {
                            this.yScrollValue = 0;
                        }
                        const yPercPos = ((drawHeight * yPercInGraph) + this.yScrollValue) / (drawHeight * oldZoomFactorY);

                        //calculate translation from zooming
                        const yTranslation = ((drawHeight * this.zoomfactory) * yPercPos) - (drawHeight * yPercInGraph);
                        //directly set to scroll value
                        this.yScrollValue = yTranslation;
                    }                    
                }
                else {
                    //only set zoomfactor if not already 1 otherwise observer will be called
                    //avoid observer
                    this.avoidZoomYObserver = true;
                    this.zoomfactory = 1;                
                    this.yScrollValue = 0;   
                }
                //redraw everything - unfortunatly
                this._redrawAll(); 
                if (avoidEvent === false) {
                    //fire event to zoom graph views with same group number
                    dispatchEvent(new CustomEvent('_DA_ZOOM_GRAPHVIEW', {
                        detail: {
                            graphInstID: this.graphinstanceid,
                            group: this.group,
                            centerPos: centerPos,
                            zoomFactorX: zoomFactorX,
                            zoomFactorY: zoomFactorY
                        }
                    }));                
                }                 
        }
        else {
            this._log('zoom(): invalid inputs', 'ERROR');
        }
    }

    /**
     * zoom factor got changed
     */
    _zoomFactorXChanged() {
        if (!this.isdesignmode) {
            //only evaluate zoom factor in trend cause zooming is done with valueperdivision in osci mode
            if (this.mode === 'trend') {         
                //check if observer should be avoided
                if (this.avoidZoomXObserver !== true) {
                    //check if already rendered
                    if (this.rendered === true) {
                        //check if offsets should be reset 
                        //do not check with === cause type doesn't matter
                        if (this.zoomfactorx == 1) {
                            //reset scroll value
                            this.scrollvalue = 0;                 
                        }
                        //redraw new -> evaluation of zoomfactor is done in drawcurves
                        this._redrawAll();                              
                    }                
                }
                this.avoidZoomXObserver = false;   
            }
        }    
    }

    /**
     * zoom factor got changed
     */
     _zoomFactorYChanged() {
        if (!this.isdesignmode) {
            //only evaluate zoom factor in trend cause zooming is done with valueperdivision in osci mode
            if (this.mode === 'trend') {        
                //check if observer should be avoided
                if (this.avoidZoomYObserver !== true) {
                    //check if already rendered
                    if (this.rendered === true) {
                        //check if offsets should be reset 
                        //do not check with === cause type doesn't matter
                        if (this.zoomfactory == 1) {
                            this.yScrollValue = 0;                   
                        }
                        //redraw new -> evaluation of zoomfactor is done in drawcurves
                        this._redrawAll();                              
                    }                
                }
                this.avoidZoomYObserver = false;   
            }
        }    
    }

    /**
     * gets called if drawing area got tracked
     * @param {*} event - event data
     */
    _track(event) {
        if (event !== undefined) {
            switch (event.detail.state) {

                case 'start':
                    if (this.scaleFactor === undefined) {
                        this.scaleFactor = this.getScaleFactor();
                    }                        
                    //check if cursor is selected
                    this.cursorTrack = this._getCursorTrack(event.detail.x - event.detail.dx, event.detail.y - event.detail.dy);
                    //simply save scrollvalues
                    this.xStartScrollValue = this.scrollvalue;
                    this.yStartScrollValue = this.yScrollValue;
                    break;

                case 'track':
                    //scrolling only if no cursor was in corridor
                    if (this.cursorTrack !== undefined) {
                        switch (this.cursorTrack) {
                            case 'x1':
                                //move x1 cursor
                                this.x1cursorvalue += event.detail.ddx / this.scaleFactor.x;
                                break;

                            case 'x2':
                                //move x2 cursor
                                this.x2cursorvalue += event.detail.ddx / this.scaleFactor.x;
                                break;  
                                
                            case 'y1':
                                //move y1 cursor
                                this.y1cursorvalue += event.detail.ddy / this.scaleFactor.y;
                                break;
                                
                            case 'y2':
                                //move y2 cursor
                                this.y2cursorvalue += event.detail.ddy / this.scaleFactor.y;
                                break;                                     
                        }
                    }
                    //only do shift of graphs in trend cause position of osci is located to horizontal position
                    else if (this.mode === 'trend') {
                        //x scrolling
                        const drawWidth = this.$.drawingArea.offsetWidth;
                        let actViewTimeRange = this.viewTimeRange;
                        if ((this.zoomfactorx !== undefined) && (parseFloat(this.zoomfactorx) > 0)) {
                            actViewTimeRange /= this.zoomfactorx;
                        }
                        const xAxisFactor = actViewTimeRange / drawWidth; 
                        const scrollTimeRange = (this.timeRange.maxTime - this.timeRange.minTime) - actViewTimeRange;     
                        this.scrollvalue = this.xStartScrollValue + ((((event.detail.dx / this.scaleFactor.x) * xAxisFactor))/ scrollTimeRange) * 1000;
                        if (this.scrollvalue > 1000) {
                            this.scrollvalue = 1000;
                        }
                        else if (this.scrollvalue < 0) {
                            this.scrollvalue = 0;
                        }                       

                        //y scrolling
                        this.yScrollValue = this.yStartScrollValue + (event.detail.dy / this.scaleFactor.y);                            
                    }
                    break;
            }        

            if (!this.isdesignmode) {
                //check if already rendered
                if (this.rendered === true) {
                    if (this.cursorTrack !== undefined) {
                        this._requestAnimation();
                    }
                    else {
                        //redraw new -> evaluation of zoomfactor is done in drawcurves
                        this._redrawAll();                               
                    }    
                }            
            }                 

        }
        else {
            this._log('_track(): invalid eventdata', 'ERROR');
        }            
    }

    /**
     * check if track start is on cursor
     * @param {*} x - x coordinate of the track start
     * @param {*} y - y coordinate of the track start
     */
    _getCursorTrack(x, y) {
        if ((x !== undefined) && (y !== undefined)) {
            //get size of drawing area
            const drawWidth = this.$.drawingArea.offsetWidth;
            const drawHeight = this.$.drawingArea.offsetHeight;
            //get control bound
            const drawBounds = this.$.drawingArea.getBoundingClientRect();
            //calc offset coordinates
            const offsetCoords = {
                x: (x - drawBounds.left) / this.scaleFactor.x,
                y: (y - drawBounds.top) / this.scaleFactor.y
            }
            let minOffset;      
            let offset;   
            let cursorToBeTracked;    
            //check if cursor active
            if (this.x1cursoractive === 1) {
                //calculate offset to cursor position
                offset = Math.abs(offsetCoords.x - (this.x1cursorvalue + drawWidth/2));
                //check if offset within range
                if ((offset <= this.cursorTrackOffset) &&
                    ((offset < minOffset) || (minOffset === undefined))) {
                    cursorToBeTracked = 'x1';
                    minOffset = offset;
                }
            }
            //check if cursor active
            if (this.x2cursoractive === 1) {
                //calculate offset to cursor position
                offset = Math.abs(offsetCoords.x - (this.x2cursorvalue + drawWidth/2));
                //check if offset within range
                if ((offset <= this.cursorTrackOffset) &&
                    ((offset < minOffset) || (minOffset === undefined))) {
                    cursorToBeTracked = 'x2';
                    minOffset = offset;
                }
            } 
            //check if cursor active
            if (this.y1cursoractive === 1) {
                //calculate offset to cursor position
                offset = Math.abs(offsetCoords.y - (this.y1cursorvalue + drawHeight/2));
                //check if offset within range
                if ((offset <= this.cursorTrackOffset) &&
                    ((offset < minOffset) || (minOffset === undefined))) {
                    cursorToBeTracked = 'y1';
                    minOffset = offset;
                }
            }
            //check if cursor active
            if (this.y2cursoractive === 1) {
                //calculate offset to cursor position
                offset = Math.abs(offsetCoords.y - (this.y2cursorvalue + drawHeight/2));
                //check if offset within range
                if ((offset <= this.cursorTrackOffset) &&
                    ((offset < minOffset) || (minOffset === undefined))) {
                    cursorToBeTracked = 'y2';
                    minOffset = offset;
                }
            }                        
            return cursorToBeTracked;
        }
        else {
            this._log('_getCursorTrack(): invalid event coordinates', 'ERROR');
        }
    }

    /**
     * method gets called if state or value of cursor changed
     */
    _cursorChanged() {
        //redraw cursor lines
        this._drawCursor();
        //update cursor values
        this._evaluateCursors();
    }

    /**
     * method is used to draw cursor lines
     */
    _drawCursor() {
        //get cursor canvas context
        let context = this.$.cursorCanvas.getContext('2d');
        //get size of drawing area
        const drawWidth = this.$.drawingArea.offsetWidth;
        const drawHeight = this.$.drawingArea.offsetHeight;
        //set size of canvas
        context.canvas.width = drawWidth;
        context.canvas.height = drawHeight;        
        //check if cursors need to be drawn
        if (((this.x1cursoractive === 1) && (this.x1cursorvalue !== undefined)) || 
            ((this.x2cursoractive === 1) && (this.x2cursorvalue !== undefined)) || 
            ((this.y1cursoractive === 1) && (this.y1cursorvalue !== undefined)) || 
            ((this.y2cursoractive === 1) && (this.y2cursorvalue !== undefined))) {
            //set font for cursor labels
            const fontFamily = getComputedStyle(this).fontFamily;
            const fontSize = getComputedStyle(this).fontSize;                    
            context.font = fontSize + " " + fontFamily;
            context.fillStyle = this.cursorcolor;
            context.textAlign = "center";                 
            context.beginPath();
            const halfDrawWidth = drawWidth/2;
            const halfDrawHeight = drawHeight/2;
            //------------------------------------------------------------------------------------------------------------------
            //-- draw x1 cursor
            //------------------------------------------------------------------------------------------------------------------
            if (this.x1cursoractive === 1) {
                if (this.x1cursorvalue > halfDrawWidth) {
                    this.x1cursorvalue = halfDrawWidth;
                }
                else if (this.x1cursorvalue < -halfDrawWidth) {
                    this.x1cursorvalue = -halfDrawWidth;
                }
                //draw cursor
                context.moveTo(this.x1cursorvalue + halfDrawWidth, 0);
                context.lineTo(this.x1cursorvalue + halfDrawWidth, drawHeight);
                //display text besides cursor
                let textSize = context.measureText('x1');
                context.fillText('x1', this.x1cursorvalue + halfDrawWidth + this.labelOffset + textSize.width / 2, 
                                this.labelOffset + textSize.actualBoundingBoxAscent);
            }

            //------------------------------------------------------------------------------------------------------------------
            //-- draw x2 cursor
            //------------------------------------------------------------------------------------------------------------------
            if (this.x2cursoractive === 1) {
                if (this.x2cursorvalue > halfDrawWidth) {
                    this.x2cursorvalue = halfDrawWidth;
                }
                else if (this.x2cursorvalue < -halfDrawWidth) {
                    this.x2cursorvalue = -halfDrawWidth;
                }
                //draw cursor
                context.moveTo(this.x2cursorvalue + halfDrawWidth, 0);
                context.lineTo(this.x2cursorvalue + halfDrawWidth, drawHeight);
                //display text besides cursor
                let textSize = context.measureText('x2');
                context.fillText('x2', this.x2cursorvalue + halfDrawWidth + this.labelOffset + textSize.width / 2, 
                                this.labelOffset + textSize.actualBoundingBoxAscent);            
            }
            
            //------------------------------------------------------------------------------------------------------------------
            //-- draw y1 cursor
            //------------------------------------------------------------------------------------------------------------------
            if (this.y1cursoractive === 1) {
                if (this.y1cursorvalue > halfDrawHeight) {
                    this.y1cursorvalue = halfDrawHeight;
                }
                else if (this.y1cursorvalue < -halfDrawHeight) {
                    this.y1cursorvalue = -halfDrawHeight;
                }
                //draw cursor
                context.moveTo(0, this.y1cursorvalue + halfDrawHeight);
                context.lineTo(drawWidth, this.y1cursorvalue + halfDrawHeight);
                //display text besides cursor
                let textSize = context.measureText('y1');
                context.fillText('y1', drawWidth - (this.labelOffset + textSize.width / 2), 
                                this.y1cursorvalue + halfDrawHeight + this.labelOffset + textSize.actualBoundingBoxAscent);           
            }

            //------------------------------------------------------------------------------------------------------------------
            //-- draw y2 cursor
            //------------------------------------------------------------------------------------------------------------------
            if (this.y2cursoractive === 1) {
                if (this.y2cursorvalue > halfDrawHeight) {
                    this.y2cursorvalue = halfDrawHeight;
                }
                else if (this.y2cursorvalue < -halfDrawHeight) {
                    this.y2cursorvalue = -halfDrawHeight;
                }
                //draw cursor
                context.moveTo(0, this.y2cursorvalue + halfDrawHeight);
                context.lineTo(drawWidth, this.y2cursorvalue + halfDrawHeight);
                //display text besides cursor
                let textSize = context.measureText('y2');
                context.fillText('y2', drawWidth - (this.labelOffset + textSize.width / 2), 
                                this.y2cursorvalue + halfDrawHeight + this.labelOffset + textSize.actualBoundingBoxAscent);                  
            }
            
            //set styles
            context.strokeStyle = this.cursorcolor;
            let lineWidth = parseInt(this.cursorwidth);
            //minimum linewidth is 1, 0 is not applied to context.lineWidth
            if (lineWidth === 0) {
                lineWidth = 1;
            }
            context.lineWidth = lineWidth;            

            //check linestyle
            switch (this.cursorstyle) {
                
                //******************************************************************************************************************
                //** solid -> no space
                //******************************************************************************************************************
                case 'solid':
                    context.setLineDash([]);
                    break;

                //******************************************************************************************************************
                //** dotted -> same distance of space and line
                //******************************************************************************************************************
                case 'dotted':
                    context.setLineDash([1, 1]);
                    break;                    

                //******************************************************************************************************************
                //** dashed -> longer lines bigger spaces
                //******************************************************************************************************************
                case 'dashed':
                    context.setLineDash([10, 5]);
                    break;   
            }

            context.stroke();
            context.closePath();
        }
    }

    /**
     * gets called if autoset event was fired
     * @param {*} data - data of event
     */
    _autoset(data) {
        let graphInstanceID, visuChlID;
        //check for input
        if (data !== undefined) {
            //get graph id - if visuChlID is not defined the autoset is ment for all visuchannels of graph
            graphInstanceID = String(data[0]);
            //get channel id - if visuChlID is not defined the autoset is ment for all visuchannels of graph
            visuChlID = data[1];
        }             
        //check if ment for us   
        if ((graphInstanceID === undefined) || (graphInstanceID === this.graphinstanceid)) {
            //check if da datamanager ready
            if (this.DA_DataManager !== undefined) {
                //get visu channels of graph
                let visuChls = this.DA_DataManager.getVisuChannels(this.graphinstanceid);
                if (visuChls !== undefined) {
                    //reset zoomfactor
                    this.zoom(null, 1, 1);
                    //get height of drawing area
                    const drawHeight = this.$.drawingArea.offsetHeight;
                    let drawSpace = drawHeight - (this.autosetOffset * 2);
                    //drawing offset used for splitview
                    let drawOffset = this.autosetOffset;                                        
                    if (visuChlID !== undefined) {
                        let channel = visuChls.get(visuChlID);
                        if (channel !== undefined) {
                            //check if channel active
                            if (channel.channelActive.value === 1) {
                                this._doAutoset(visuChlID, channel, drawHeight, drawOffset, drawSpace);
                                //update graph
                                this._redrawAll();                             
                            }
                        }
                        else {
                            this._log("_autoset(): get channel failed", 'ERROR');
                        }                          
                    }
                    else {
                        //get area used for split views
                        let splitAreas = 0;
                        let splitCnt = 0;
                        //all channels which should be split but have not configured a split area are splitted automatically
                        let defaultSplitCnt = 0;
                        let defaultSplitArea = 0;
                        //if there are channels which should be split but also some which should not be split
                        //we display the unsplitted at top
                        let useNonSplitArea = false;                        
                        //check if channels should be split
                        if (this.splitmode > 0) {
                            for (let [visuChlID, channel] of visuChls) {
                                //check if channel active
                                if (channel.channelActive.value === 1) {
                                    //check if channel should be split
                                    if ((this.splitmode === 2) || (channel.splitView.value === 1)) {
                                        if (channel.splitArea.value > 0) {
                                            splitAreas += (channel.splitArea.value * drawSpace) / 100;
                                            splitCnt += 1;
                                        }
                                        else {
                                            defaultSplitCnt += 1;
                                        }
                                    }
                                    else {
                                        //there are channels which should not be split
                                        useNonSplitArea = true;
                                    } 
                                }
                            }
                            //add spaces between channels
                            if ((splitCnt > 0) && (splitAreas > 0)) {
                                if (useNonSplitArea === true) {
                                    splitAreas += splitCnt * this.splitdistance;
                                }
                                else {
                                    splitAreas += (splitCnt - 1) * this.splitdistance;
                                }                                 
                            }
                            //limit split areas
                            if (splitAreas > drawSpace) {
                                splitAreas = drawSpace;
                            }  
                            //calculate area for split views which have no size configured
                            defaultSplitArea = drawSpace - splitAreas; 
                            if (defaultSplitCnt > 0) {
                                if (defaultSplitArea > 0) {
                                    //are there channels which should not be displayed split?
                                    if (useNonSplitArea === true) {
                                        defaultSplitArea = (defaultSplitArea - (defaultSplitCnt * this.splitdistance))/(defaultSplitCnt + 1);
                                    }
                                    else {
                                        defaultSplitArea = (defaultSplitArea - ((defaultSplitCnt - 1) * this.splitdistance))/defaultSplitCnt;
                                    }                                      
                                }    
                            }                       
                        }                      
                        for (let [visuChlID, channel] of visuChls) {
                            //check if channel active
                            if (channel.channelActive.value === 1) {
                                //check if channel should be split
                                switch (this.splitmode) {
                                    //-------------------------------------------------------------------------------
                                    //-- nothing should be displayed split
                                    //-------------------------------------------------------------------------------
                                    case 0:
                                        this._doAutoset(visuChlID, channel, drawHeight, drawOffset, drawSpace);
                                        break;
                                    
                                    //-------------------------------------------------------------------------------
                                    //-- only channels where splitview is set should be displayed separatly
                                    //-------------------------------------------------------------------------------
                                    case 1:
                                        //check if channel should be displayed separatly
                                        if (channel.splitView.value === 1) {
                                            let actDrawSpace = defaultSplitArea;
                                            if (channel.splitArea.value > 0) {
                                                //calculate drawSpace
                                                actDrawSpace = (channel.splitArea.value * drawSpace) / 100;
                                                //limit drawspace
                                                if ((drawOffset + actDrawSpace) > drawSpace) {
                                                    actDrawSpace = drawSpace - drawOffset;
                                                }
                                            }
                                            this._doAutoset(visuChlID, channel, drawHeight, drawOffset, actDrawSpace);
                                            drawOffset += actDrawSpace + this.splitdistance;
                                            //limit drawoffset
                                            if (drawOffset > drawSpace) {
                                                drawOffset = drawSpace;
                                            }
                                        }
                                        else {
                                            this._doAutoset(visuChlID, channel, drawHeight, drawHeight - defaultSplitArea - this.autosetOffset, defaultSplitArea);
                                        }
                                        break;                                        

                                    //-------------------------------------------------------------------------------
                                    //-- all channels should be displayed separatly
                                    //-------------------------------------------------------------------------------
                                    case 2:
                                        let actDrawSpace = defaultSplitArea;
                                        if (channel.splitArea.value > 0) {
                                            //calculate drawSpace
                                            actDrawSpace = (channel.splitArea.value * drawSpace) / 100;
                                            //limit drawspace
                                            if ((drawOffset + actDrawSpace) > drawSpace) {
                                                actDrawSpace = drawSpace - drawOffset;
                                            }
                                        }
                                        this._doAutoset(visuChlID, channel, drawHeight, drawOffset, actDrawSpace);
                                        drawOffset += actDrawSpace + this.splitdistance;
                                        //limit drawoffset
                                        if (drawOffset > drawSpace) {
                                            drawOffset = drawSpace;
                                        }
                                        break;                                         
                                }  
                            }
                        }
                        //update graph
                        this._redrawAll();                      
                    }
                }
                else {
                    this._log("_autoset(): invalid visuchannels", 'ERROR');
                }                   
            }
            else {
                this._log("_autoset(): datamanager not ready", 'ERROR');
            }  
        }                 
    }

    /**
     * actually executes the autoset function
     * @param {*} visuChlID - id of the visu channel
     * @param {*} channel - channel for which autoset should be done
     * @param {*} drawHeight - height of the whole drawing area (if not defined no splitview should be done)
     * @param {*} drawOffset - drawing offset of the channel for splitview (if not defined no splitview should be done)
     * @param {*} drawSpace - height of the drawing area used for this channel (if not defined no splitview should be done)
     */
    async _doAutoset(visuChlID, channel, drawHeight = undefined, drawOffset = undefined, drawSpace = undefined) {
        if ((visuChlID !== undefined) && (channel !== undefined)) {
            //check for real channel reference
            if (channel.chlReference !== undefined) {
                //get real channel
                const realChl = this.DA_DataManager.getChannelByID(channel.chlReference.stationNumber, channel.chlReference.chlID);
                if (realChl !== undefined) { 
                    let minValue, maxValue, origMinValue, origMaxValue;
                    //check if entries available
                    if (realChl.noOfEntries > 0) {
                        //get minimum and maximum value and average
                        for (let i=0;i<realChl.noOfEntries;i++) {
                            //do not evaluate invalid value
                            if (realChl.buffer[i] !== realChl.invalidValue) {
                                if ((realChl.buffer[i] < minValue) || (minValue === undefined)) {
                                    minValue = realChl.buffer[i];
                                }
                                if ((realChl.buffer[i] > maxValue) || (maxValue === undefined)) {
                                    maxValue = realChl.buffer[i];
                                }                                 
                            }                                
                        }
                        origMinValue = minValue;
                        origMaxValue = maxValue;
                        //if channel should be displayed split, we need to manipulate min and maxvalue
                        if ((drawHeight !== undefined) && (drawOffset !== undefined) && (drawSpace !== undefined)) {
                            let yScaleFactor = ((maxValue - minValue) / drawSpace);
                            //calculate maxvalue
                            maxValue += yScaleFactor * drawOffset;
                            //calculate minvalue
                            minValue -= yScaleFactor * (drawHeight - drawOffset - drawSpace);
                        }
                    }
                    else {
                        minValue = 0;
                        maxValue = 0; 
                        origMinValue = 0;
                        origMaxValue = 0;                    
                    }
                    //limit max and minvalue to datatype maximum and minimum
                    const limits = this.DA_DataManager.limitValues(realChl.dataType, minValue, maxValue);
                    if (limits !== undefined) {
                        minValue = limits.minValue;
                        maxValue = limits.maxValue;
                        //calculate value of the zeroline
                        let zeroValue = 0;
                        if (realChl.unitInfos !== undefined) {
                            zeroValue = realChl.unitInfos.zeroValue;
                        }
                        if (this.mode === 'trend') {
                            //reset zero offset
                            if (channel.zeroLineOffset.ref !== undefined) {
                                //write on datapoint
                                if (this.datapointApi !== undefined) {
                                    this.datapointApi.writeDataPoint(channel.zeroLineOffset.ref.getId(), zeroValue);
                                }
                                else {
                                    this._log('_doAutoset(): invalid datapoint api', 'ERROR');
                                }
                            }
                            channel.zeroLineOffset.value = zeroValue;       
                            //in case of integer we have to ceil otherwise floating number will be lost
                            if (realChl.dataType !== this.dataTypes.float32) {
                                minValue = minValue >= 0 ? Math.ceil(minValue) : Math.floor(minValue);
                                maxValue = maxValue >= 0 ? Math.ceil(maxValue) : Math.floor(maxValue);
                            }         
                            
                            //check if the scale values should be prettified
                            if (this.prettyscale === true) {
                                //get unitinfo of channel
                                const unitInfo = this._getUnitInfo(channel);
                                if (unitInfo !== undefined) {
                                    const dp = this.datapointApi.getDatapoint(realChl.srcVarID);
                                    if (dp !== null) {
                                        //get floating point info
                                        const isFloatingPoint = dp.isFloatingPoint();
                                        //we have to convert values to unit before prettifing them
                                        try {
                                            maxValue = await this._formatValue(maxValue, unitInfo, isFloatingPoint);  
                                            minValue = await this._formatValue(minValue, unitInfo, isFloatingPoint);

                                            const prettyValues = this._prettyScale(minValue, maxValue);
                                            if (prettyValues !== undefined) {
                                                //convert values back
                                                maxValue = await this.convertValueBack(prettyValues.maxValue, unitInfo, isFloatingPoint);
                                                minValue = await this.convertValueBack(prettyValues.minValue, unitInfo, isFloatingPoint);                                            
                                            }
                                            else {
                                                this._log('_doAutoset(): failed to prettify scale', 'ERROR');
                                            }
                                        } 
                                        catch(error) {
                                            this._log('_doAutoset(): failed to format value -> ' + error, 'ERROR');
                                        }         
                                    }
                                    else {
                                        this._log('_doAutoset(): failed to get datapoint', 'ERROR');
                                    }
                                } 
                                else {
                                    //just du prettyscale
                                    const prettyValues = this._prettyScale(minValue, maxValue);
                                    if (prettyValues !== undefined) {
                                        //convert values back
                                        maxValue = prettyValues.maxValue;
                                        minValue = prettyValues.minValue;                                            
                                    }
                                    else {
                                        this._log('_doAutoset(): failed to prettify scale', 'ERROR');
                                    }
                                }      
                            }

                            //get uncasted value to write on datapoint
                            minValue = this.DA_DataManager.getUncastedValue(realChl, minValue, channel.minScale.ref);
                            maxValue = this.DA_DataManager.getUncastedValue(realChl, maxValue, channel.maxScale.ref);

                            //check for datapoint given
                            if (channel.minScale.ref !== undefined) {
                                if (this.datapointApi !== undefined) {
                                    this.datapointApi.writeDataPoint(channel.minScale.ref.getId(), minValue);
                                }
                                else {
                                    this._log("_doAutoset(): invalid datapoint api", 'ERROR')
                                }
                            }
                            channel.minScale.value = minValue;
                            if (channel.maxScale.ref !== undefined) {
                                if (this.datapointApi !== undefined) {
                                    this.datapointApi.writeDataPoint(channel.maxScale.ref.getId(), maxValue);
                                }
                                else {
                                    this._log("_doAutoset(): invalid datapoint api", 'ERROR')
                                }                        
                            }
                            channel.maxScale.value = maxValue;
                        }
                        else {
                            //calculate zerolineoffset
                            zeroValue = ((maxValue - minValue)/2) - (maxValue - zeroValue);
                            //reset zero offset
                            if (channel.zeroLineOffset.ref !== undefined) {
                                //write on datapoint
                                if (this.datapointApi !== undefined) {
                                    this.datapointApi.writeDataPoint(channel.zeroLineOffset.ref.getId(), zeroValue);
                                }
                                else {
                                    this._log('_doAutoset(): invalid datapoint api', 'ERROR');
                                }
                            }
                            channel.zeroLineOffset.value = zeroValue;                                         
                            //calculate value per division
                            let value = (maxValue - minValue) / this.ydivisions;
                            //in case of integer we have to ceil otherwise floating number will be lost
                            if (realChl.dataType !== this.dataTypes.float32) {
                                value = Math.ceil(value);
                            }                           
                            //get value casted for writing on datapoint
                            value = this.DA_DataManager.getUncastedValue(realChl, value, channel.valuePerDivision.ref);
                            //check for complex property
                            if (channel.valuePerDivision.ref !== undefined) {
                                if ((this.datapointApi !== undefined) && (this.constApi !== undefined)) {
                                    let dp;
                                    //check if scheme
                                    if (channel.valuePerDivision.src === this.constApi.PROP_SRC_SCHEME) {
                                        //get selected property
                                        const dpProp = channel.valuePerDivision.ref.getSelectedProp();
                                        if (dpProp !== undefined) {
                                            dp = dpProp.getDatapoint();
                                        }
                                        else {
                                            this._log("_valuePerDivChanged(): failed to get selected prop", 'ERROR');
                                        }
                                    }
                                    else {
                                        //ref is datapoint
                                        dp = channel.valuePerDivision.ref;
                                    }
                                    //get uncasted value
                                    this.datapointApi.writeDataPoint(dp.getId(), value);
                                }
                                else {
                                    this._log("_valuePerDivChanged(): invalid datapoint api", 'ERROR');
                                }
                            }
                            channel.valuePerDivision.value = value;                                                                                   
                        }                        
                    }
                    else {
                        this._log("_doAutoset(): failed to get datatype limits", 'ERROR');
                    }                                                  
                }                      
            }
            else {
                this._log("_doAutoset(): invalid channel reference", 'ERROR');
            }            
        }
        else {
            this._log("_doAutoset(): invalid inputs", 'ERROR');
        }            
    }

    /**
     * method is used to prettify scale
     * @param {*} minValue - minimum value
     * @param {*} maxValue - maximum value
     * @returns 
     */
    _prettyScale(minValue, maxValue) {
        if ((minValue !== undefined) && (maxValue !== undefined)) {
            //do nice scaling
            const scaleRange = maxValue - minValue;
            //calculate unsmoothed value per division
            let divisionValue = scaleRange / this.ydivisions;
            //calculate fix division label
            const divisionLabelOffset = Math.round(this.ydivisions / 2);
            const divisionLabel = maxValue - (divisionLabelOffset * divisionValue);
            //get nice scale range to get sure range can be displayed
            const niceScaleRange = this._niceNumber(scaleRange);
            //make nice division value
            divisionValue = this._niceNumber(niceScaleRange / this.ydivisions);
            //calculate correction of label to be a multiple of the nice division value
            //calculate new division label
            const newDivisionLabel = divisionValue * Math.round(divisionLabel / divisionValue);
            return {
                maxValue: newDivisionLabel + (divisionLabelOffset * divisionValue),
                minValue: newDivisionLabel - ((this.ydivisions - divisionLabelOffset) * divisionValue)
            } 
        }
        else {
            this._log('_prettyScale(): invalid values', 'ERROR');
        }
    }    

    /**
     * method is used to get pretty values
     * @param {*} number - number which has to be prettyfied
     * @returns 
     */
    _niceNumber(number) {
        //calculate potention to get a value between 0 and 1
        let pow10 = Math.ceil(Math.log10(number));
        pow10 = Math.pow(10, pow10);
        //get value between 0 and 1
        number = number/pow10;
        //smooth value
        if (number <= 0.1) {
            number = 0.1;
        }
        else if (number <= 0.2) {
            number = 0.2;
        }
        else if (number <= 0.25) {
            number = 0.25;
        }
        else if (number <= 0.5) {
            number = 0.5;
        }
        else if (number <= 0.75) {
            number = 0.75;
        }
        else {
            number = 1.0;
        }
        //get to correct potention again
        return number * pow10;
    }

    /**
     * method is used to convert a value back to base units
     * @param {*} value - value which should be converted back
     * @param {*} unitInfo - unit property of the datapoint
     * @param {*} isFloatingPoint - value is evaluated as floating point
     */
     async convertValueBack(value, unitInfo, isFloatingPoint) {
        //check for valid value -> as unitinfo is also valid if undefined in case of no unit configured, we don't question that
        if (value !== undefined) {
            //check if value needs to be formatted
            if (unitInfo !== undefined) {
                //check if apis ready for formating values
                if ((this.propApi !== undefined) && (this.constApi !== undefined)) {
                    if (unitInfo.src === this.constApi.PROP_SRC_UNIT) {
                        return await this.propApi.convertValueBackWithUnit(unitInfo.value, value, isFloatingPoint);
                    }
                    else if (unitInfo.src === this.constApi.PROP_SRC_SCHEME) {
                        const schemeVals = unitInfo.value.split(':');
                        return await this.propApi.convertValueBackWithUnitscheme(schemeVals[1], schemeVals[0], value, 0, isFloatingPoint, false);
                    }
                    else if (unitInfo.src === this.constApi.PROP_SRC_SWITCH_PROP) {
                        //scheme and unit configured
                        if ((unitInfo.children !== undefined) && (unitInfo.children.length > 0)) {
                            let unit = 0;
                            let unitScheme = 0;
                            for (let i=0;i<unitInfo.children.length;i++) {
                                //check for unit
                                if (unitInfo.children[i].src === this.constApi.PROP_SRC_UNIT) {
                                    unit = unitInfo.children[i].value;
                                }
                                else if (unitInfo.children[i].src === this.constApi.PROP_SRC_SCHEME) {
                                    unitScheme = unitInfo.children[i].value;
                                }
                            }
                            const schemeVals = unitScheme.split(':');
                            return await this.propApi.convertValueBackWithUnitscheme(schemeVals[1], schemeVals[0], value, unit, isFloatingPoint, false);                            
                        }
                        else {
                            this._log('_convertValueBack(): invalid constellation', 'ERROR');
                        }
                    }
                }
                else {
                    this._log('_convertValueBack(): api not ready', 'ERROR');
                }                
            }
            else {
                //value doesn't need to be converted back
                return value;
            }
        }
        else {
            this._log('_convertValueBack(): invalid value', 'ERROR');
        }
    }

    /**
     * method is used to check if a trigger was raised referenced to at least one channel of the graph
     * @param {*} visuChls - map of visu channels configured for this graph
     * @param {*} stationNumber - number of the station where the channels are located
     * @param {*} triggerData - data of the trigger which should be checked if ment for this graph
     */
    _checkForTrigger(visuChls, stationNumber, triggerData) {
        //check for valid inputs
        if ((visuChls !== undefined) && (stationNumber !== undefined) && (triggerData !== undefined)) {
            for (let [visuChlID, channel] of visuChls) {
                //channel must be active so drawing trigger is sensefull
                if (channel.channelActive.value === 1) {
                    if (channel.chlReference !== undefined) {
                        //check if data available
                        const realChl = this.DA_DataManager.getChannelByID(channel.chlReference.stationNumber, channel.chlReference.chlID);
                        if (realChl !== undefined) {
                            if (realChl.noOfEntries > 0) {
                                //check if it a channel of this graph was triggered
                                if ((channel.chlReference.stationNumber === stationNumber) && 
                                    ((triggerData.mode === this.triggerModes.all) || 
                                    ((triggerData.mode === this.triggerModes.single) && (channel.chlReference.chlID === triggerData.destination)) || 
                                    ((triggerData.mode === this.triggerModes.group) && (realChl.group === triggerData.destination)))) {
                                    //ment for us so return color in which the trigger line should be displayed
                                    if (triggerData.mode === this.triggerModes.single) {
                                        //if trigger triggers a single channel we use the color of the channel so the user is able to 
                                        //see on first sight which channel got triggered
                                        return this._getChannelColor(channel);
                                    }
                                    else {
                                        return this.triggercolor;
                                    }  
                                }                            
                            }                            
                        }
                        else {
                            this._log('_checkForTrigger(): channel not found', 'ERROR');
                        } 
                    }
                    else {
                        this._log('_checkForTrigger(): invalid channel reference', 'ERROR');
                    }                      
                }                              
            }
        }
        else {
            this._log('_checkForTrigger(): invalid inputs', 'ERROR');
        }
    }

    /**
     * gets called if number of scales was changed
     */
    _noOfScalesChanged() {
        //update everything
        if (this.rendered) {
            this._redrawAll();
        }
    }

    /**
     * gets called if scale should be switched
     */
     _firstScaleChanged() {
        //update everything
        if (this.rendered) {
            this._redrawAll();
        }
    }

    /**
     * this method is used to sort channels by the visu channel id
     * @param {*} channels - channels which should be sorted
     */
    _sortChannels(channels) {
        //check for valid channels
        if (channels !== undefined) {
            //first filter for active channels
            let activeChlArray = [...channels.entries()].filter((mapAttr) => {
                return (mapAttr[1].channelActive.value === 1) && (mapAttr[1].scaleType.value === -1);
            });
            let sortedChlArray = activeChlArray.sort((a, b) => {
                if (a[0] > b[0]) return 1;
                if (a[0] < b[0]) return -1;
                return 0;                    
            });
            //check if first scale out of array
            if (this.firstscaleid >= sortedChlArray.length) {
                this.firstscaleid = 0;
            }           
            else if (this.firstscaleid < 0) {
                this.firstscaleid = sortedChlArray.length - 1;
            }
            //next sort so array starts with first scale id
            const leftOver = sortedChlArray.splice(this.firstscaleid);
            //paste at the beginning
            sortedChlArray.unshift(...leftOver);        

            return new Map(sortedChlArray);
        }
        else {
            this._log('_sortChannels(): invalid channels', 'ERROR');
        }
    }

    /**
     * gets called if mode of graphs was changed
     */
    _modeChanged() {
        if (this.isdesignmode) {
            //check if component already rendered
            if (this.rendered) {
                //redraw everything
                this._redrawAll();
            }    
        }
    }

    /**
     * gets called if the horizontal position for osci changed
     */
    _horizontalChanged(newValue, oldValue) {
        //check if in oscimode to react
        if ((this.mode === 'osci') && (this.avoidHorizontalObserver !== true)) {
            //calculate pre and postsamples
            this._calcPrePostSamples();                               
        }
        if (this.rendered) {
            //clear old timeout
            this.sigClearTimeout(this.horizontalTimeout);
            //full line only gets displayed for 3 seconds
            this.horizontalTimeout = this.sigSetTimeout(() => {
                this.horizontalTimeout = undefined;
                this._requestAnimation();
            }, 3000);
            //redraw
            this._requestAnimation();                         
        } 
        this.avoidHorizontalObserver = false;   
    }

    /**
     * method is used to calculate pre and postsamples by information of horizontal offset
     */
    _calcPrePostSamples() {
        //check if da datamanager instance available
        if (this.DA_DataManager !== undefined) {
            //check if rendered and trigger infos available
            if ((this.rendered) && (this.triggerInfos !== undefined)) {
                //get draw width
                const drawWidth = this.$.drawingArea.offsetWidth;
                //get offset in time
                const horizontalOffset = (this.horizontalpos/drawWidth) * this.viewTimeRange;
                //get trigger channel to calculate presamples
                const triggerChannel = this.DA_DataManager.getChannelByID(this.triggerInfos.stationNumber, this.triggerchannel);
                if (triggerChannel !== undefined) {
                    //calculate recording time
                    const recordingTime = (triggerChannel.maxNoOfValues - 1) * triggerChannel.sampleInterval;
                    //calculate presamples
                    this.preSamples = recordingTime/2 + horizontalOffset;
                    if (this.preSamples < 0) {
                        this.preSamples = 0;
                    }
                    else if (this.preSamples > recordingTime) {
                        this.preSamples = recordingTime;
                    }
                    //calculate postsamples
                    this.postSamples = recordingTime - this.preSamples;
                    //set new pre and postsamples
                    this.DA_DataManager.setPrePostSamples(this.triggerInfos.CRC, this.preSamples, this.postSamples);                                
                }                
            }
        }
        else {
            this._log('_calcPrePostSamples(): invalid datamanager instance', 'ERROR');
        }
    }

    async _getTriggerInfos() {
        //check for valid apis
        if (this.propApi !== undefined) {
            //get osci trigger property
            const osciTriggerProp = this.propApi.getProperty(this.id, 'oscitrigger');
            if (osciTriggerProp !== null) {
                const osciTrigger = osciTriggerProp.getDatapoint(osciTriggerProp.value);
                if (osciTrigger !== null) {
                    try {
                        return {
                            stationNumber: osciTrigger.getStation(),
                            CRC: await osciTrigger.getValue()
                        }
                    } 
                    catch(error) {
                        this._log('_getTriggerInfos(): failed to get value -> ' + error, 'ERROR');
                    }
                }
                else {
                    this._log('_getTriggerInfos(): get datapoint failed', 'ERROR');
                }
            }
            else {
                this._log('_getTriggerInfos(): get property failed', 'ERROR');
            }
        }
        else {
            this._log('_getTriggerInfos(): invalid apis', 'ERROR');
        }                                        
    }

    /**
     * method is used to get the pre and postsamples of oscitrigger
     */
     _getPrePost(triggerCRC) {
         if (triggerCRC !== undefined) {
            //check if da manager available
            if (this.DA_DataManager !== undefined) {
                //get presamples
                this.DA_DataManager.getPrePostSamples(triggerCRC);
            }
            else {
                this._log('_getPrePost(): invalid manager', 'ERROR');
            }             
         }
         else {
            this._log('_getPrePost(): invalid inputs', 'ERROR');
         }
    }

    /**
     * method is used to get the threshold
     * @param {*} triggerCRC - crc of the trigger
     */
    _getThreshold(triggerCRC) {
        if (triggerCRC !== undefined) {
            //check if da manager available
            if (this.DA_DataManager !== undefined) {
                //get threshold
                this.DA_DataManager.getThreshold(triggerCRC);
            }
            else {
                this._log('_getThreshold(): invalid manager', 'ERROR');
            }             
         }
         else {
            this._log('_getThreshold(): invalid inputs', 'ERROR');
         }
    }

    /**
     * gets called if the trigger level changes
     */
    _triggerLevelChanged() {
        //check if threshold should be calculated
        if ((this.mode === 'osci') && (this.avoidTriggerLevelObserver !== true)) {
            this._calcThreshold();
        }    
        this.avoidTriggerLevelObserver = false; 
        //redraw cause of triggerlevel
        //clear old timeout
        this.sigClearTimeout(this.triggerLevelTimeout);
        //full line only gets displayed for 3 seconds
        this.triggerLevelTimeout = this.sigSetTimeout(() => {
            this.triggerLevelTimeout = undefined;
            this._requestAnimation();
        }, 3000);
        this._requestAnimation();              
    }

    /**
     * gets called if the trigger channel changed
     */
    _triggerChannelChanged() {
        //check if threshold should be calculated
        if (this.mode === 'osci') {
            this._calcThreshold();
        }
    }

    /**
     * calculate triggerlevel
     */
    _calcThreshold() {
        //check if rendered already
        if (this.rendered) {
            //check if datamanager was initialized
            if (this.DA_DataManager !== undefined) {
                //check if triggerinfo available
                if (this.triggerInfos !== undefined) {
                    //get visuchannels
                    const visuChls = this.DA_DataManager.getVisuChannels(this.graphinstanceid);
                    if ((visuChls !== undefined) && (visuChls.size > 0)) {
                        for (let [visuChlID, channel] of visuChls) {
                            if (channel.chlReference !== undefined) {
                                //check if it is about this channel
                                if ((channel.chlReference.stationNumber === this.triggerInfos.stationNumber) && (channel.chlReference.chlID === this.triggerchannel)) {
                                    //get channel to get datatype
                                    const realChl = this.DA_DataManager.getChannelByID(channel.chlReference.stationNumber, channel.chlReference.chlID);
                                    if (realChl !== undefined) {
                                        //get height of drawing area
                                        const drawHeight = this.$.drawingArea.offsetHeight;                                        
                                        //get scale values
                                        const scaleValues = this._getScaleValues(visuChlID, channel, visuChls, drawHeight);
                                        if (scaleValues !== undefined) {
                                            //calculate value of threshold
                                            const triggerLevel = drawHeight/2 - this.triggerlevel;
                                            this.threshold = scaleValues.maxValue - ((triggerLevel / drawHeight) * (scaleValues.maxValue - scaleValues.minValue));
                                            //write new threshold to trigger
                                            this.DA_DataManager.setThreshold(this.triggerInfos, this.threshold, this.triggerchannel);                        
                                        }
                                    }
                                    else {
                                        this._log('_calcThreshold(): get real channel failed', 'ERROR');
                                    }
                                    break;
                                }
                            }    
                            else {
                                this._log('_calcThreshold(): invalid channel reference', 'ERROR');
                            }                                                           
                        }
                    }                    
                }
            }                 
        }
    }

    /**
     * method is used to calculate the triggerlevel by the threshold
     */
    _calcTriggerLevel() {
        //check if rendered already
        if (this.rendered) {
            //check if datamanager was initialized
            if (this.DA_DataManager !== undefined) {
                //check if triggerinfo available
                if (this.triggerInfos !== undefined) {
                    //get visuchannels
                    const visuChls = this.DA_DataManager.getVisuChannels(this.graphinstanceid);
                    if ((visuChls !== undefined) && (visuChls.size > 0)) {
                        for (let [visuChlID, channel] of visuChls) {
                            if (channel.chlReference !== undefined) {
                                //check if it is about this channel
                                if ((channel.chlReference.stationNumber === this.triggerInfos.stationNumber) && (channel.chlReference.chlID === this.triggerchannel)) {
                                    //get channel to get datatype
                                    const realChl = this.DA_DataManager.getChannelByID(channel.chlReference.stationNumber, channel.chlReference.chlID);
                                    if (realChl !== undefined) {
                                        //get height of drawing area
                                        const drawHeight = this.$.drawingArea.offsetHeight;                                        
                                        //get scale values
                                        const scaleValues = this._getScaleValues(visuChlID, channel, visuChls, drawHeight);
                                        if (scaleValues !== undefined) {
                                            //calculate trigger level
                                            const triggerLevel = ((scaleValues.maxValue - this.threshold) / (scaleValues.maxValue - scaleValues.minValue)) * drawHeight;
                                            //avoid observer
                                            if (this.triggerlevel !==  Math.round(drawHeight/2 - triggerLevel)) {
                                                this.avoidTriggerLevelObserver = true;
                                                this.triggerlevel = Math.round(drawHeight/2 - triggerLevel);
                                            }
                                        }
                                    }
                                    else {
                                        this._log('_calcTriggerLevel(): get real channel failed', 'ERROR');
                                    }
                                    break;
                                }
                            }    
                            else {
                                this._log('_calcTriggerLevel(): invalid channel reference', 'ERROR');
                            }                                                           
                        }
                    }                    
                }
            }                 
        }        
    }

    /**
     * casts the triggerthreshold to the datatype needed for channel comparision
     * @param {*} realChl - real channel reference
     * @param {*} value - value which should be casted (has to be signed double integer)
     * @returns 
     */    
    _getCastedThreshold(realChl, value) {
        if ((realChl !== undefined) && (value !== undefined)) {
            //dataview used to cast datatypes of value
            let valueDV = new DataView(new ArrayBuffer(4));
            //write value as signed double integer in dataview
            valueDV.setInt32(0, value);
            //check for datatype of recorded value
            switch (realChl.dataType) {
                case this.dataTypes.int8:
                case this.dataTypes.int16:
                case this.dataTypes.int32:
                    return value;

                case this.dataTypes.bool:
                case this.dataTypes.uint8:
                case this.dataTypes.uint16:
                case this.dataTypes.uint32:
                    return valueDV.getUint32(0);
                    
                case this.dataTypes.float32:
                    return valueDV.getFloat32(0);
                    
                default:
                    this._log('_getCastedThreshold(): invalid datatype', 'ERROR');
                    return;  
            }  
        }
        else {
            this._log('_getCastedThreshold(): invalid inputs', 'ERROR');
        }
    }

    /***************************************************************************************************************************
     * gets called if displayed timerange was changed
     ***************************************************************************************************************************/    
    _displayedTimeRangeChanged() {
        dispatchEvent(new CustomEvent('_DA_GRAPH_UPDATE_DISPLAYED_TIMERANGE', {
            detail: {
                graphInstID: this.graphinstanceid,
                timeRange: this.displayedTimeRange
            }
        }));
    }

    /***************************************************************************************************************************
     * gets called if timerange changed
     ***************************************************************************************************************************/    
     _timeRangeChanged() { 
        dispatchEvent(new CustomEvent('_DA_GRAPH_UPDATE_TIMERANGE', {
            detail: {
                graphInstID: this.graphinstanceid,
                timeRange: this.timeRange
            }
        }));
    }

    /***************************************************************************************************************************
     * gets called if the color of the trigger changes
     ***************************************************************************************************************************/    
     _triggerColorChanged() {
        if (!this.isdesignmode) {
            if (this.rendered) {
                this._requestAnimation();
            }            
        }
    }   

    /**
     * gets called from the runtime to check if changed event should be fired or not
     * @param {*} property - property which should be checked for change
     * @param {*} originalResult - result of the original check
     * @param {*} value - new value
     * @param {*} old - old value
     */   
     sigShouldPropertyUpdate(property, originalResult, value, old) {
        //call base
        const result = super.sigShouldPropertyUpdate(property, originalResult, value, old);
        //original result has to be true too cause thats the check for value change
        return result && originalResult;
    }
        
    /***************************************************************************************************************************
     * gets called if control gets disconnected from dom
     ***************************************************************************************************************************/    
    disconnectedCallback() {
        super.disconnectedCallback();
        //remove eventlisteners
        this._removeEventListeners();
    }

    /***************************************************************************************************************************
     * gets called if eventlisteners should be removed
     ***************************************************************************************************************************/    
    _removeEventListeners() {
        if (this.evtMediaApi !== undefined) {
            //-----------------------------------------------------------------------------------------------------
            //-- unsubscribe autoset
            //-----------------------------------------------------------------------------------------------------  
            this.evtMediaApi.unsubscribeWithId(this.majorID, this.autoset.minorID, this.autosetHandler);               
        }
    }
    
    /***************************************************************************************************************************
     * gets called if event for exporting records was received
     ***************************************************************************************************************************/   
    _exportRecords(userData) {
        if (this.DA_DataManager) {
            this.DA_DataManager.startExport(this.graphinstanceid,userData);
        } else {
            this._log('_exportRecords(): DataManager not available', 'ERROR');
        }
    }

}
customElements.define(LasalRuntimeSigControlDAGraphs.is, LasalRuntimeSigControlDAGraphs);