<?xml version="1.0" encoding="UTF-8" standalone="no"?><addon xmlns="http://icplayer.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" id="Plot" xsi:schemaLocation="http://icplayer.com ../../tools/addon.xsd">
	<model>
		<property name="Expressions" nameLabel="Plot_property_expressions" type="list">
			<property isLocalized="false" name="id" nameLabel="Plot_property_id" type="string"/>
			<property isLocalized="false" name="expression" nameLabel="Plot_property_expression" type="string"/>
			<property isLocalized="false" name="xMin" nameLabel="Plot_property_x_min" type="int"/>
			<property isLocalized="false" name="xMax" nameLabel="Plot_property_x_max" type="int"/>
			<property isLocalized="false" name="yMin" nameLabel="Plot_property_y_min" type="int"/>
			<property isLocalized="false" name="yMax" nameLabel="Plot_property_y_max" type="int"/>
			<property isLocalized="false" name="selectable" nameLabel="Plot_property_selectable" type="boolean"/>
			<property isLocalized="false" name="correct" nameLabel="Plot_property_correct" type="boolean"/>
			<property isLocalized="false" name="y to x" nameLabel="Plot_property_y_to_x" type="boolean"/>
			<property isLocalized="false" name="hidden" nameLabel="Plot_property_hidden" type="boolean"/>
			<property isLocalized="false" name="color" nameLabel="Plot_property_color" type="string"/>
			<property isLocalized="false" name="mark at length" nameLabel="Plot_property_mark_at_length" type="string"/>
		</property>
		<property name="Variables" nameLabel="Plot_property_variables" type="list">
			<property isLocalized="false" name="plot id" nameLabel="Plot_property_plot_id" type="string"/>
			<property isLocalized="false" name="variable" nameLabel="Plot_property_variable" type="string"/>
			<property isLocalized="false" name="value" nameLabel="Plot_property_value" type="string"/>
			<property isLocalized="false" name="expected" nameLabel="Plot_property_expected" type="string"/>
		</property>
		<property name="Points" nameLabel="Plot_property_points" type="list">
			<property isLocalized="false" name="x value" nameLabel="Plot_property_x_value" type="string"/>
			<property isLocalized="false" name="y value" nameLabel="Plot_property_y_value" type="string"/>
			<property isLocalized="false" name="selected" nameLabel="Plot_property_selected" type="boolean"/>
			<property isLocalized="false" name="correct" nameLabel="Plot_property_correct" type="boolean"/>
			<property isLocalized="false" name="not scored" nameLabel="Plot_property_not_scored" type="boolean"/>
		</property>
		<property isLocalized="false" name="xMin" nameLabel="Plot_property_x_min" type="int"/>
		<property isLocalized="false" name="xMax" nameLabel="Plot_property_x_max" type="int"/>
		<property isLocalized="false" name="yMin" nameLabel="Plot_property_y_min" type="int"/>
		<property isLocalized="false" name="yMax" nameLabel="Plot_property_y_max" type="int"/>
		<property isLocalized="false" name="Grid" nameLabel="Plot_property_grid" type="boolean"/>
		<property isLocalized="false" name="GridStepX" nameLabel="Plot_property_grid_step_x" type="int"/>
		<property isLocalized="false" name="GridStepY" nameLabel="Plot_property_grid_step_y" type="int"/>
		<property isLocalized="false" name="Arrowhead size" nameLabel="Plot_property_arrow_head_size" type="int"/>
		<property isLocalized="false" name="Axis values" nameLabel="Plot_property_axis_values" type="boolean"/>
		<property isLocalized="false" name="Axis x values" nameLabel="Plot_property_axis_x_values" type="string"/>
		<property isLocalized="false" name="Axis y values" nameLabel="Plot_property_axis_y_values" type="string"/>
		<property isLocalized="true" name="X axis description" nameLabel="Plot_property_x_axis_description" type="string"/>
		<property isLocalized="true" name="Y axis description" nameLabel="Plot_property_y_axis_description" type="string"/>
		<property isLocalized="false" name="hide X axis" nameLabel="Plot_property_hide_x_axis" type="boolean"/>
		<property isLocalized="false" name="hide Y axis" nameLabel="Plot_property_hide_y_axis" type="boolean"/>
		<property isLocalized="false" name="Point active area size" nameLabel="Plot_property_points_active_area_size" type="int"/>
		<property isLocalized="false" name="Point radius" nameLabel="Plot_property_points_radius" type="int"/>
		<property isLocalized="false" name="Point outline radius" nameLabel="Plot_property_points_outline_radius" type="int"/>
		<property isLocalized="false" name="Max selected points" nameLabel="Plot_property_max_selected_points" type="int"/>
		<property isLocalized="false" name="Not activity" nameLabel="Plot_property_not_activity" type="boolean"/>
		<property isLocalized="false" name="Free points" nameLabel="Plot_property_free_points" type="boolean"/>
		<property isLocalized="false" name="Asymptote DY" nameLabel="Plot_property_asymptote_dy" type="int"/>
		<property isLocalized="false" name="Broadcast" nameLabel="Plot_property_broadcast" type="string"/>
		<property isLocalized="false" name="Decimal separator" nameLabel="Plot_property_decimal_separator" type="string"/>
		<property isLocalized="false" name="X axis values position" nameLabel="Plot_property_x_axis_values_position" type="int"/>
		<property isLocalized="false" name="Y axis values position" nameLabel="Plot_property_y_axis_values_position" type="int"/>
		<property isLocalized="false" name="Check marks" nameLabel="Plot_property_check_marks" type="{No, One mark, Individual marks}"/>
		<property isLocalized="false" name="Correct marks HTML" nameLabel="Plot_property_correct_marks_html" type="string"/>
		<property isLocalized="false" name="Error marks HTML" nameLabel="Plot_property_error_marks_html" type="string"/>
	</model>
<css>.canvas {
	background-color:#ffffff;
}
/* axis */
.axisText {
	font-family:Verdana, sans-serif; 
	font-size:8pt; 
	fill:#000000;
	stroke-width:0;
}
.axisXText {
	text-anchor: end;
}		
.axisYText {
}	
.axis {
	stroke:#000000;
	stroke-width: 1;
}
.axisArrows {
	fill: #000000;
}
.axisThicksTextX {
	text-anchor: middle;
	dominant-baseline: central
}
.axisThicksTextY {
	text-anchor: left;
	dominant-baseline: central
}
/* grid */
.grid {
	stroke:#ddd;
	stroke-width: 1;
    stroke-dasharray: 2 2;
}
/* plot */
.draw_active_area {
	stroke-opacity:0.0;
	stroke-linecap:round;
	stroke-linejoin:round;
	stroke-width:10;
	fill:none;
	stroke:#ffffff;
}
.draw_outline_base {
	stroke-linecap:round;
	stroke-linejoin:round;
	stroke-opacity:0;
	fill:none;
}
.draw_outline {
	stroke-opacity:0.5;
	stroke-width:5;
	fill:none;
	stroke:#008800;
}
.draw_outline_mark_error {
	stroke-opacity:0.5;
	stroke-width:5;
	fill:none;			
	stroke:#ff0000;
}
.draw_outline_mark_correct {
	stroke-opacity:0.5;
	stroke-width:5;
	fill:none;
	stroke:#008800;
}
.draw {
	fill: none;
	stroke:#0000ff;
	stroke-width: 2;			
}
.draw_selected {
	fill: none;
	stroke-width:3;
	stroke:#008800;
}
.draw_over{
	stroke:#888888;
}	
.draw_mark_error {
	stroke: #ff0000;
    stroke-width:2;
}
.draw_mark_correct {
	stroke:#008800;
}
/* point */
.point_active_area {
	stroke-width:0;
	fill:#ff0000;
	fill-opacity:0;
}
.point_outline_base {
	stroke-opacity:0;
	stroke:#bb0000;
	stroke-width:2;
	fill-opacity:0;
	fill:#dd0000;
}		
.point_outline {
	stroke-opacity:1;
	stroke:#006600;
	stroke-width:2;
	fill-opacity:1;
	fill:#00bb00;
}	
.point_outline_mark_error {
	stroke-opacity:1;
	stroke:#bb0000;
	stroke-width:2;
    fill-opacity:1;
	fill:#ff0000;
}
.point_outline_mark_correct {
	stroke-opacity:1;
	stroke:#006600;
	stroke-width:2;
	fill-opacity:1;
	fill:#00bb00;
}
.point {
	fill:#dddddd;
	stroke:#bbbbbb;
	stroke-width:0.5;
}
.point_selected {
	fill:#00bb00;
	stroke:#006600;
	stroke-width:2;
}
.point_over {
	fill:#aaaaaa;
	stroke:#666666;
	stroke-width:2;
}
.point_error {
	fill:#ff0000;
	stroke:#bb0000;
	stroke-width:2;
}
.point_correct {
	fill:#00bb00;
	stroke:#00bb00;
	stroke-width:2;
}

.plot_show_answers {
    stroke-opacity:1;
    stroke:#006600;
    stroke-width:2;
    fill-opacity:1;
    fill:#00bb00;
}

.canvas .mark.addon_mark.mark_error{
	color: #ff0000;
}

.canvas .mark.addon_mark.mark_correct{
	color: #00bb00;
}

.canvas .mark.addon_mark {
	position: absolute;
	left: 90%;
	top: 10%;
}

.canvas .mark.point_mark {
	transform: scale(1, -1) translate(8px, 0px);
	dominant-baseline: text-after-edge;
}

.canvas .mark.expression_mark {
	transform: scale(1, -1);
	dominant-baseline: hanging;
}

.canvas .mark.point_mark.mark_correct,
.canvas .mark.expression_mark.mark_correct {
	fill:#00bb00;
	stroke:#00bb00;
	stroke-width:2;
}

.canvas .mark.point_mark.mark_error,
.canvas .mark.expression_mark.mark_error {
	fill:#ff0000;
	stroke:#bb0000;
	stroke-width:2;
}

.canvas .mark {
	font-size: 16px;
}
</css><view>&lt;div class="canvas" style="width:100%; height:100%; overflow:hidden"&gt;
&lt;/div&gt;
</view><preview>&lt;div class="canvas" style="width:100%; height:100%; overflow:hidden"&gt;
&lt;/div&gt;
</preview><presenter>function AddonPlot_create(){
    function Plot() {
        this.VERSION = '1.1.17';
        this.STATE_CORRECT = 1;
        this.STATE_INCORRECT = 0;
        this.STATE_NOT_ACTIVITY = '';
        this.STATE_NULL = 0;
        this.STATE_SELECT_POINT = 1;
        this.STATE_DESELECT_POINT = 0;
        this.INFINITY_NEGATIVE_VALUE = -2147483647;
        this.INFINITY_POSITIVE_VALUE = 2147483647;
        this.ASYMPTOTE_MINIMUM_TRIAL = 3;
        this.TYPE_X_TO_Y = 1;
        this.TYPE_Y_TO_X = 2;
        this.asymptoteMinimumDY = 5;
        this.interactive = true;
        this.xMin = -10;
        this.xMax = 10;
        this.yMin = -10;
        this.yMax = 10;
        this.zoomStep = 0.1;
        this.expressions = [];
        this.points = [];
        this.svg = null;
        this.stepX = 0;
        this.stepY = 0;
        this.offsetX = 0;
        this.offsetY = 0;
        this.width = 0;
        this.height = 0;
        this.gridStepX = 1;
        this.gridStepY = 1;
        this.grid = true;
        this.lastOffsetX = 0;
        this.lastOffsetY = 0;
        this.arrowheadSize = 6;
        this.axisValues = true;
        this.svgDoc = null;
        this.uiEnabled = true;
        this.initXMin = this.xMin;
        this.initXMax = this.xMax;
        this.initYMin = this.yMin;
        this.initYMax = this.yMax;
        this.pointActiveArea = 15;
        this.pointRadius = 3;
        this.pointOutlineRadius = 7;
        this.selectedPoints = [];
        this.xAxisDescription = 'x';
        this.yAxisDescription = 'y';
        this.xAxisVisible = true;
        this.yAxisVisible = true;
        this.xAxisCyclicValues = [];
        this.xAxisCustomValues = [];
        this.yAxisCyclicValues = [];
        this.yAxisCustomValues = [];
        this.maxSelectedPoints = 0;
        this.stateChanged = function(){};
        this.convertValueToDisplay = function(val){ return val; };
        this.isActivity = true;
        this.freePoints = false;
        this.precision = {x: 100, y:100};
        this.attempted = false;
        this.xAxisValuesPosition = 0;
        this.yAxisValuesPosition = 0;

        this.CHECKMARKS = {
            NO: "No",
            ONE_MARK: "One mark",
            INDIVIDUAL_MARKS: "Individual marks"
        };

        this.setXAxisValuesPosition = function (value) {
            this.svgDoc.find('.axisThicksTextX').attr('y', value);
        };

        this.setYAxisValuesPosition = function (value) {
            var element = this.svgDoc.find('.axisThicksTextY');
            element.attr('x', value);
            if(value &lt; 0) {
                element.attr("text-anchor", "end");
            }
        };

        this.setScale = function () {
            this.svgDoc.find('.scale').attr('transform', 'scale(1, -1)');
        };
        this.setStep = function () {
            this.stepX = this.width/(Math.max(this.xMin, this.xMax) - Math.min(this.xMin, this.xMax));
            this.stepY = this.height/(Math.max(this.yMin, this.yMax) - Math.min(this.yMin, this.yMax));
        };
        this.translate = function() {
            this.offsetX = this.xMin*this.stepX*(-1);
            this.offsetY = this.yMax*this.stepY*(-1);
            this.svgDoc.find('.translate').attr('transform', 'translate('+this.offsetX+', '+this.offsetY+')');
        };
        this.draw = function() {
            this.clearCanvas();
            if(this.grid) {
                this.drawGrid();
            }
            this.drawAxis();
            this.drawPlots();
            if(this.points.length &gt; 0 || this.freePoints) {
                this.drawPoints();
            }
            this.enableUI(this.uiEnabled);
            if(this.xAxisValuesPosition != '' &amp;&amp; !isNaN(this.xAxisValuesPosition)){
                this.setXAxisValuesPosition(this.xAxisValuesPosition);
            }

            if(this.yAxisValuesPosition != '' &amp;&amp; !isNaN(this.yAxisValuesPosition)){
                this.setYAxisValuesPosition(this.yAxisValuesPosition);
            }
        };
        this.clearCanvas = function() {
            this.svgDoc.find('.drawings').children().remove();
            this.svgDoc.find('.grid').children().remove();
            this.svgDoc.find('.axis').children().remove();
            this.svgDoc.find('.points').children().remove();
            this.svgDoc.find('.pointsAreas').children().remove();
            this.svgDoc.find('.drawingsAreas').children().remove();
        }
        this.drawXAxis = function() {
            var path = this.svg.createPath();
            this.svg.line(this.svgDoc.find('.axis'), -this.offsetX, 0, this.width-this.offsetX, 0);
            path.move(this.width - this.offsetX, 0).
            line(this.width - this.offsetX - this.arrowheadSize, this.arrowheadSize/2).
            line(this.width - this.offsetX - this.arrowheadSize, -this.arrowheadSize/2).
            line(this.width - this.offsetX, 0);
            this.svg.text(this.svgDoc.find('.axis'), this.width-this.offsetX-3, 13, this.xAxisDescription, {
                'class':'axisText axisXText',
                transform:'scale(1,-1)'
            });
            this.svg.path(this.svgDoc.find('.axis'), path, {
                'class':'axisArrows'
            });
        };
        this.drawYAxis = function() {
            var path = this.svg.createPath();
            this.svg.line(this.svgDoc.find('.axis'), 0, -this.offsetY, 0, -this.height-this.offsetY);
            path.move(0, -this.offsetY).
            line(this.arrowheadSize/2, -this.offsetY - this.arrowheadSize).
            line(-this.arrowheadSize/2, -this.offsetY - this.arrowheadSize).
            line(0, -this.offsetY);
            this.svg.text(this.svgDoc.find('.axis'), 7, this.offsetY+10, this.yAxisDescription, {
                'class':'axisText axisYText',
                transform:'scale(1,-1)'
            });
            this.svg.path(this.svgDoc.find('.axis'), path, {
                'class':'axisArrows'
            });
        };
        this.drawAxis0Point = function() {
            this.svg.text(this.svgDoc.find('.axis'), 3, 13, '0', {
                'class':'axisText',
                transform:'scale(1,-1)'
            });
        };
        this.drawXTick = function(x) {
            if(x &lt; this.width - this.offsetX - this.arrowheadSize) {
                this.svg.line(this.svgDoc.find('.axis'), x, -3, x, 3);
            }
        };
        this.drawYTick = function(y) {
            if(y &lt; -this.offsetY - this.arrowheadSize) {
                this.svg.line(this.svgDoc.find('.axis'), -3, y, 3, y);
            }
        };
        this.drawXTickValue = function(x, coords) {
            if(x &lt; this.width - this.offsetX - 15) {
                if(this.xAxisVisible &amp;&amp; this.yAxisVisible &amp;&amp; coords.x == 0) {
                    return;
                } else {
                    this.svg.text(this.svgDoc.find('.axis'), x, 10, this.convertValueToDisplay(coords.x.toString()), {
                        'class':'axisText axisThicksTextX',
                        transform:'scale(1,-1)'
                    });
                }
            }
        };
        this.drawYTickValue = function(y, coords) {
            if(this.xAxisVisible &amp;&amp; this.yAxisVisible &amp;&amp; coords.y == 0) {
            ;
            } else {
                this.svg.text(this.svgDoc.find('.axis'), 7, y, this.convertValueToDisplay(coords.y.toString()), {
                    'class':'axisText axisThicksTextY',
                    transform:'scale(1,-1)'
                });
            }
        };
        this.checkAndDrawXTickValue = function(x, dx, cx, tx) {
            var currentTick;
            var coords = this.px2coords(cx, 0);
            //check cyclic values
            if(this.xAxisCyclicValues.length &gt; 0) {
                currentTick = Math.round(x/dx);
                $.each(this.xAxisCyclicValues, function(k, v) {
                    if(currentTick%v == 0) {
                        plot.drawXTickValue(tx, coords);
                        return;
                    }
                })
            } else if(this.xAxisCustomValues.length == 0) {
                this.drawXTickValue(tx, coords);
            }
        };
        this.drawCustomXTicks = function() {
            $.each(this.xAxisCustomValues, function(idx, val) {
                p = plot.coords2px(val, 0);
                plot.drawXTick(p.x);
                plot.drawXTickValue(p.x, {x:val, y:0});
            });
        };
        this.drawXTicks = function() {
            var x, sp;
            var dx = this.gridStepX*this.stepX;
            var modX = this.offsetX%(dx);
            //get starting point of viewport
            sp = this.offsetX &lt; 0 ? -this.offsetX + modX: 0
            //above 0
            for(x = sp; x &lt;= this.width - this.offsetX; x += dx) {
                this.drawXTick(x);
                this.checkAndDrawXTickValue(x, dx, x+this.offsetX, x);
            }
            //below 0
            for(x = sp; x &gt;= -this.offsetX - modX; x-= dx) {
                this.drawXTick(x);
                this.checkAndDrawXTickValue(x, dx, x+this.offsetX, x);
            }
            this.drawCustomXTicks();
        };
        this.checkAndDrawYTickValue = function(y, dy, cy, ty) {
            coords = this.px2coords(0, cy);
            //check cyclic values
            if(this.yAxisCyclicValues.length &gt; 0) {
                currentTick = Math.round(y/dy);
                $.each(this.yAxisCyclicValues, function(k, v) {
                    if(currentTick%v == 0) {
                        plot.drawYTickValue(ty, coords);
                        return;
                    }
                })
            } else if(this.yAxisCustomValues.length == 0) {
                this.drawYTickValue(ty, coords);
            }
        };
        this.drawCustomYTicks = function() {
            var p;
            $.each(this.yAxisCustomValues, function(idx, val){
                p = plot.coords2px(0, val);
                plot.drawYTick(p.y);
                plot.drawYTickValue(-p.y, {
                    x:0,
                    y:val
                });
            });
        };
        this.drawYTicks = function() {
            var y;
            var dy = this.gridStepY*this.stepY;
            var modY = this.offsetY%(dy);
            sp = Math.abs(this.offsetY) &lt; this.height ? 0 : Math.abs(this.offsetY) - this.height - (Math.abs(this.offsetY) - this.height)%dy;
            //above 0
            for(y = sp; y &lt;= sp+this.height+dy; y += dy) {
                this.drawYTick(y);
                this.checkAndDrawYTickValue(y, dy, -y-this.offsetY, -y);
            }
            //below 0
            sp = this.offsetY &gt; 0 ? this.offsetY - modY : 0;
            for(y = sp; y &lt; this.offsetY + this.height; y += dy) {
                this.drawYTick(-y);
                this.checkAndDrawYTickValue(y, dy, y-this.offsetY, y);
            }
            this.drawCustomYTicks();
        };
        this.drawAxis = function() {
            if(this.yAxisVisible) {
                this.drawYAxis();
            }
            if(this.xAxisVisible) {
                this.drawXAxis();
            }
            if(this.xAxisVisible &amp;&amp; this.yAxisVisible) {
                this.drawAxis0Point();
            }
            if(this.axisValues) {
                if(this.xAxisVisible) {
                    this.drawXTicks();
                }
                if(this.yAxisVisible) {
                    this.drawYTicks();
                }
            }
        };
        this.drawGrid = function() {
            var dx = this.gridStepX*this.stepX;
            var modX = this.offsetX%(dx);
            for(var x = -this.offsetX+modX; x &lt;= this.width-this.offsetX; x += dx) {
                //pionowe
                this.svg.line(this.svgDoc.find('.grid'), x, -this.offsetY, x, -this.height-this.offsetY);
            }

            var dy = this.gridStepY*this.stepY;
            var modY = this.offsetY%(dy);
            for(var y = -this.offsetY+modY; y &gt;= -this.height-this.offsetY; y -= dy) {
                //poziome
                this.svg.line(this.svgDoc.find('.grid'), -this.offsetX, y, this.width-this.offsetX, y);
            }
        };
        this.drawPoints = function() {
            var dx = this.gridStepX*this.stepX;
            var modX = this.offsetX%(dx);
            var dy = this.gridStepY*this.stepY;
            var modY = this.offsetY%(dy);
            var point;
            var html = $('&lt;span class="point"&gt;&lt;/span&gt;');
            for(var x = -this.offsetX+modX; x &lt;= this.width-this.offsetX; x += dx) {
                var idx = 0;
                for(var y = -this.offsetY+modY; y &gt;= -this.height-this.offsetY; y -= dy) {
                    point = this.px2coords(x+this.offsetX,dy*idx-modY);
                    this.svg.circle(this.svgDoc.find('.points'), x, y, this.pointOutlineRadius, {
                        'class':'point_outline_base',
                        vx:point.x,
                        vy:point.y
                        });
                    this.svg.circle(this.svgDoc.find('.points'), x, y, this.pointRadius, {
                        'class':'point',
                        vx:point.x,
                        vy:point.y
                        });
                    if(this.interactive) {
                        this.svg.rect(this.svgDoc.find('.pointsAreas'), x-this.pointActiveArea/2, y-this.pointActiveArea/2, this.pointActiveArea, this.pointActiveArea, {
                            'class':'point_active_area',
                            vx:point.x,
                            vy:point.y
                            });
                    }
                    idx++;
                }
            }
            $.each(plot.selectedPoints, function(k, v) {
                var refObj = plot.svgDoc.find('.point[vx="'+v.x+'"][vy="'+v.y+'"]');
                var refObjOutline = plot.svgDoc.find('.point_outline_base[vx="'+v.x+'"][vy="'+v.y+'"]');
                refObj.addClass('point_selected');
                refObjOutline.addClass('point_outline');
                if(!v.clickable) {
                    refObj.addClass('nonclickable');
                }
            });

            if(this.interactive) {
                obj = this.svgDoc.find('.point_active_area');
                obj.mouseover(function() {
                    var vx = $(this).attr('vx');
                    var vy = $(this).attr('vy');
                    var point;
                    var point = plot.svgDoc.find('.point[vx="'+vx+'"][vy="'+vy+'"]');
                    if(plot.maxSelectedPoints == 0 || plot.selectedPoints.length &lt; plot.maxSelectedPoints || point.hasClass('point_selected')) {
                        point = plot.svgDoc.find('.point[vx="'+vx+'"][vy="'+vy+'"]');
                        if(!point.hasClass('nonclickable')) {
                            point.addClass('point_over');
                        }
                    }
                });
                obj.mouseout(function() {
                    var vx = $(this).attr('vx');
                    var vy = $(this).attr('vy');
                    plot.svgDoc.find('.point[vx="'+vx+'"][vy="'+vy+'"]').removeClass('point_over');
                });
                obj.click(function(e) {
                    e.stopPropagation();

                    var vx = $(this).attr('vx');
                    var vy = $(this).attr('vy');
                    var refObj = plot.svgDoc.find('.point[vx="'+vx+'"][vy="'+vy+'"]');
                    if(!refObj.hasClass('nonclickable')) {
                        if(refObj.hasClass('point_selected')) {
                            plot._deselectPoint(vx, vy, false);
                        } else {
                            plot._selectPoint(vx, vy, false);
                        }
                    }
                });
            }
        };
        this._touchPoint = function(x, y) {
            this.attempted = true;
            var pvx = parseFloat(x);
            var pvy = parseFloat(y);
            //mark as touched in model if exists
            $.each(plot.points, function(k,v) {
                if(v.x == pvx &amp;&amp; v.y == pvy) {
                    v.touched = true;
                }
            });
        }
        this._deselectPoint = function(x,y, isShowAnswersMode) {
            if (!isShowAnswersMode) {
                 this._touchPoint(x, y);
            }
            var pvx = parseFloat(x);
            var pvy = parseFloat(y);
            var refObj = plot.svgDoc.find('.point[vx="'+x+'"][vy="'+y+'"]');
            var refObjOutline = plot.svgDoc.find('.point_outline_base[vx="'+x+'"][vy="'+y+'"]');
            refObj.removeClass('point_selected');
            refObjOutline.removeClass('point_outline');
            $.each(this.selectedPoints, function(k, v) {
                if(v.x == pvx &amp;&amp; v.y == pvy) {
                    plot.selectedPoints.splice(k, 1);
                    if (!isShowAnswersMode) {
                        plot.stateChanged({
                            item:'point_'+pvx+'_'+pvy,
                            value:0,
                            score:plot.getPointEventScore(pvx, pvy, plot.STATE_DESELECT_POINT)
                        });
                    }
                    return false;
                }
            });
        }
        this._selectPoint = function(x,y, isShowAnswersMode) {
            if (!isShowAnswersMode) {
                this._touchPoint(x, y);
            }
            var pvx = parseFloat(x);
            var pvy = parseFloat(y);
            var refObj = plot.svgDoc.find('.point[vx="'+x+'"][vy="'+y+'"]');
            var refObjOutline = plot.svgDoc.find('.point_outline_base[vx="'+x+'"][vy="'+y+'"]');
            //check if we can add another point
            if(plot.maxSelectedPoints == 0 || plot.selectedPoints.length &lt; plot.maxSelectedPoints) {
                refObj.addClass('point_selected');
                refObjOutline.addClass('point_outline');
                var hasPoint = false;
                $.each(plot.selectedPoints, function(k, v) {
                    if(v.x == pvx &amp;&amp; v.y == pvy) {
                        hasPoint = true;
                        return false;
                    }
                });
                if(!hasPoint) {
                    plot.selectedPoints.push({
                        x:pvx,
                        y:pvy,
                        clickable: true
                    });
                    if (!isShowAnswersMode) {
                        plot.stateChanged({
                            item:'point_'+pvx+'_'+pvy,
                            value:1,
                            score:plot.getPointEventScore(pvx, pvy, plot.STATE_SELECT_POINT)
                        });
                    }
                }
            }
        }
        this.removePlot = function(p) {
            this.svgDoc.find('.drawingsAreas .draw_active_area[refuid="'+p+'"]').remove();
            this.svgDoc.find('.drawings .draw_outline_base[ouid="'+p+'"]').remove();
            this.svgDoc.find('.drawings .draw[uid="'+p+'"]').remove();
        }
        this.drawPlot = function(p) {
            var obj, cp;
            var path = this.svg.createPath();
            //function type x to y
            if(this.expressions[p].type == this.TYPE_X_TO_Y) {
                path = this._drawXToY(p);
            //function type y to x
            } else if(this.expressions[p].type == this.TYPE_Y_TO_X) {
                path = this._drawYToX(p);
            }
            this.svg.path(this.svgDoc.find('.drawings'), path, {
                ouid:p,
                'class': 'draw_outline_base'
            });
            this.svg.path(this.svgDoc.find('.drawings'), path, {
                uid:p,
                isselected: this.expressions[p].selected ? 1 : 0,
                'class': 'is_plot draw draw_'+(parseInt(p)+1)
            });
            //restore style
            cp = plot.svgDoc.find('.drawings [uid="'+p+'"]');
            var props = this.expressions[p].cssProperties;
            props['stroke'] = this.expressions[p].cssColor;
            cp.data({
                cssStyle: this._composeStyle(props)
            });
            cp.attr('style', cp.data().cssStyle);
            if(this.interactive) {
                this.svg.path(this.svgDoc.find('.drawingsAreas'), path, {
                    refuid:p,
                    'class':'draw_active_area'
                });
            }
            if(this.expressions[p].selected) {
                plot.svgDoc.find('.is_plot[uid="'+p+'"]').addClass('draw_selected draw_'+(parseInt(p)+1)+'_selected').removeAttr('style');
                plot.svgDoc.find('.draw_outline_base[ouid="'+p+'"]').addClass('draw_outline draw_'+(parseInt(p)+1)+'_outline');
            }
            if(this.expressions[p].selectable &amp;&amp; this.interactive) {
                obj = this.svgDoc.find('.draw_active_area[refuid="'+p+'"]');
                obj.mouseover(function() {
                    var id = parseInt($(this).attr('refuid'));
                    p = plot.svgDoc.find('.is_plot[uid="'+id+'"]');
                    p.addClass('draw_over draw_'+(id+1)+'_over').removeAttr('style');
                });
                obj.mouseout(function() {
                    var id = parseInt($(this).attr('refuid'));
                    p = plot.svgDoc.find('.is_plot[uid="'+id+'"]');
                    p.removeClass('draw_over draw_'+(id+1)+'_over');
                    if(p.attr('isselected') != 1) {
                        p.attr('style', p.data().cssStyle);
                    }
                });
                obj.click(function(e) {
                    e.stopPropagation();

                    var id = parseInt($(this).attr('refuid'));
                    var refObj = plot.svgDoc.find('.is_plot[uid="'+id+'"]');
                    var refObjOutline = plot.svgDoc.find('.draw_outline_base[ouid="'+id+'"]');
                    var selected = parseInt(refObj.attr('isselected'));
                    plot.expressions[id].touched = true;
                    plot.attempted = true;
                    if(selected) {
                        refObj.attr('isselected', 0);
                        refObj.attr('style', refObj.data().cssStyle);
                        refObj.removeClass('draw_selected draw_'+(id+1)+'_selected');
                        refObjOutline.removeClass('draw_outline draw_'+(id+1)+'_outline');
                        plot.expressions[id].selected = false;
                        plot.stateChanged({
                            item:'plot_'+plot.expressions[id].id,
                            value:0,
                            score: plot.getPlotEventScore(plot.expressions[id].id)
                        });
                    } else {
                        refObj.attr('isselected', 1);
                        refObj.addClass('draw_selected draw_'+(id+1)+'_selected');
                        refObj.removeAttr('style');
                        refObjOutline.addClass('draw_outline draw_'+(id+1)+'_outline');
                        plot.expressions[id].selected = true;
                        plot.stateChanged({
                            item:'plot_'+plot.expressions[id].id,
                            value:1,
                            score: plot.getPlotEventScore(plot.expressions[id].id)
                        });
                    }
                });
            }
        }
        this.areIndividualMarks = function () {
            return this.checkMarks === this.CHECKMARKS.INDIVIDUAL_MARKS;
        }
        this.isOneMark = function () {
            return this.checkMarks === this.CHECKMARKS.ONE_MARK;
        }
        this.createIndividualMarksSVGDOMGroup = function () {
            const $groupsParent = this.svgDoc.find(".overall");
            this.svg.group($groupsParent, {
                class_: "marks"
            });
        }
        this.drawOneCorrectMark = function () {
            this.drawOneMark(true);
        }
        this.drawOneErrorMark = function () {
            this.drawOneMark(false);
        }
        this.drawOneMark = function (isCorrect) {
            const additionalClassName = isCorrect ? "mark_correct" :  "mark_error";
            const innerHTML = isCorrect ? this.correctMarksHTML : this.errorMarksHTML;

            const addonMark = document.createElement("div");
            addonMark.classList.add("mark");
            addonMark.classList.add("addon_mark");
            addonMark.classList.add(additionalClassName);
            $(addonMark).html(window.xssUtils.sanitize(innerHTML));
            this.svgDoc.parent().append(addonMark);
        }
        this.drawCorrectMarkForPoint = function (x, y) {
            this.drawMarkForPoint(x, y, true);
        }
        this.drawErrorMarkForPoint = function (x, y) {
            this.drawMarkForPoint(x, y, false);
        }
        this.drawMarkForPoint = function (x, y, isCorrect) {
            this.svgDoc.find('.marks .mark.point_mark[vx="' + x + '"][vy="' + y + '"]').remove();

            const additionalClassName = isCorrect ? "mark_correct" :  "mark_error";
            const innerHTML = isCorrect ? this.correctMarksHTML : this.errorMarksHTML;
            const $source = this.svgDoc.find('.point[vx="' + x + '"][vy="' + y + '"]');

            this.svg.text(this.svgDoc.find(".marks"), "",  {
                "x": $source.attr("cx"),
                "y": -$source.attr("cy"),
                "vx": x,
                "vy": y,
                "class": "mark point_mark " + additionalClassName,
            });

            const $text = this.svgDoc.find('.marks .mark.point_mark[vx="' + x + '"][vy="' + y + '"]');
            $text.html(window.xssUtils.sanitize(innerHTML));
        }
        this.drawCorrectMarkForExpression = function(idx) {
            this.drawMarkForExpression(idx, true);
        }
        this.drawErrorMarkForExpression = function(idx) {
            this.drawMarkForExpression(idx, false);
        }
        this.drawMarkForExpression = function (idx, isCorrect) {
            this.svgDoc.find('.marks .mark.expression_mark[refuid="' + idx + '"]').remove();

            const additionalClassName = isCorrect ? "mark_correct" :  "mark_error";
            const innerHTML = isCorrect ? this.correctMarksHTML : this.errorMarksHTML;

            const path = this.svgDoc.find('.is_plot[uid="'+idx+'"]')[0];
            const pathDistance = path.getTotalLength() * this.expressions[idx].markAtLength;
            const midpoint = path.getPointAtLength(pathDistance);

            this.svg.text(this.svgDoc.find(".marks"), "",  {
                "refuid": idx,
                "class": "mark expression_mark " + additionalClassName,
            });

            const $text = this.svgDoc.find('.marks .mark.expression_mark[refuid="' + idx + '"]');
            $text.attr("x", midpoint.x);
            $text.attr("y", -midpoint.y);
            $text.html(window.xssUtils.sanitize(innerHTML));
        }
        this.removeMarks = function() {
            this.svgDoc.find(".marks").remove();
            $(presenter.view).find(".mark").remove();
        }
        this.drawPlots = function() {
            $.each(this.expressions, function(idx, val) {
                if(val.visible) {
                    plot.drawPlot(idx);
                }
            });
        };
        this._drawYToX = function(p) {
            var path = this.svg.createPath();
            var rx, ry, i;
            var idx = 0;
            var values = [];
            var sy = -this.offsetY;
            var ey = -this.height-this.offsetY;
            var domain = this._getPlotDomain(p);
            var xMin = domain.xMin;
            var xMax = domain.xMax;
            var yMin = domain.yMin;
            var yMax = domain.yMax;

            //create variables for parser
            var variables = this._mapPlotVariables(p);

            for(i = sy; i &gt;= ey; i--) {
                ry = this.yMax-(idx)/this.stepY;
                //add x to parser variables
                variables.y = ry;
                try {
                    rx = Parser.evaluate(this.expressions[p].expression, variables);
                } catch (e) {
                    break;
                }
                if(rx &gt; this.INFINITY_POSITIVE_VALUE) {
                    //console.log(i,ry,'-n');
                    rx = Infinity;
                }
                if(rx &lt; this.INFINITY_NEGATIVE_VALUE) {
                    //console.log(i,ry,'+n');
                    rx = -Infinity;
                }

                values.push({
                   rx: rx.toFixed(5),
                   ry: ry.toFixed(5),
                   i: i
                });

                idx++;
            }

            var d1 = d2 = d3 = dX = 0;
            var asymptote = false;
            var positioning = true; //set position at the beginning of drawing
            var closedAsymptote = false;
            var prevXMin = prevXMax = prevYMin = prevYMax = false;

            for(var j = 0; j&lt;values.length; j+=1) {
                ry = values[j].ry;
                rx = values[j].rx;
                i = values[j].i;

                //yMin is set
                if(yMin !== null) {
                    //range check
                    if(ry &lt; yMin) {
                        prevYMin = ry;
                        positioning = true;
                        continue;
                    }
                    //if we pass from out of range to in range
                    if(prevYMin !== false &amp;&amp; prevYMin &lt; yMin &amp;&amp; ry &gt;= yMin) {
                        prevYMin = false;
                        //console.log('przejscie xmin',i,rx)
                        //calculate equation for xMin
                        variables.y = yMin;
                        try {
                            rx = Parser.evaluate(this.expressions[p].expression, variables);
                        } catch (e) {
                            break;
                        }
                        //force to draw y at draw point x
                        i = this.coords2px(0, yMin).y;
                    }
                }

                //yMax is set
                if(yMax !== null) {
                    //we are in range
                    if(ry &lt;= yMax) {
                        prevYMax = ry;
                    }
                    //we pass xMax
                    if(prevYMax !== false &amp;&amp; prevYMax &lt;= yMax &amp;&amp; ry &gt; yMax) {
                        prevYMax = false;
                        //console.log('przejscie xmax',i,rx)
                        //calculate equation for xMin
                        variables.y = yMax;
                        try {
                            rx = Parser.evaluate(this.expressions[p].expression, variables);
                        } catch (e) {
                            break;
                        }
                        //force draw x value
                        i = this.coords2px(0,yMax).y;
                    } else if(ry &gt; yMax) {
                        positioning = true;
                        prevYMax = false;
                        continue;
                    }
                }

                if(xMin !== null) {
                    //range check
                    if(rx &lt; xMin) {
                        prevXMin = rx;
                    }
                    //if we pass from out of range to in range - open path with yMin
                    if(prevXMin !== false &amp;&amp; prevXMin &lt; xMin &amp;&amp; rx &gt;= xMin) {
                        positioning = true;
                        prevXMin = false;
                        //console.log('wejscie xmin',i,rx,ry);
                        rx = xMin;
                    } else if(j&gt;0 &amp;&amp; values[j-1].rx &gt;= xMin &amp;&amp; rx &lt; xMin)  {
                        //if we pass from in range to out of range (close path)
                        positioning = false;
                        //console.log('zejscie xmin', i, rx, ry);
                        rx = xMin;
                    } else if( rx &lt; xMin){
                        continue;
                    }
                }

                if(xMax !== null) {
                    if(rx &lt;= xMax) {
                        prevXMax = rx;
                    }
                    //we pass yMax
                    if(prevXMax !== false &amp;&amp; prevXMax &lt;= xMax &amp;&amp; rx &gt; xMax) {
                        prevXMax = false;
                        //console.log('zejscie poza xmax',j,i,rx,ry)
                        rx = xMax;
                    } else if(j &gt; 0 &amp;&amp; values[j-1].rx &gt; xMax &amp;&amp; rx &lt;= xMax) {
                        //console.log('wejscie z poza xmax',j,i,rx,ry,values[j-1].ry )
                        positioning = true;
                        rx = xMax;
                    } else if(rx &gt; xMax) {
                        positioning = true;
                        prevXMax = false;
                        continue;
                    }
                }

                if(j &gt; this.ASYMPTOTE_MINIMUM_TRIAL) {
                    dX = rx-values[j-1].rx;
                    d3 = values[j-1].rx - values[j-2].rx;
                    d2 = values[j-2].rx - values[j-3].rx;
                    d1 = values[j-3].rx - values[j-4].rx;
                }

                //detect discontinuous
                if(this.isDiscontinuous(d1, d2, d3, dX)) {
                    rx = Number.NaN;
                }

                //detect asymptote
                asymptote=this.hasAsymptote(d1, d2, d3, dX);

                if(asymptote.asymptote) {
                    //console.log('Asymptote: ',i, j,asymptote.asymptote,asymptote.val, rx,ry,'d:',d1,d2,d3,dX);
                    //draw graph to -+Infinity
                    //draw to -Infinity
                    if(!closedAsymptote) {
                        //draw to -Infinity
                        if(asymptote.val == 1) {
                            //if prevoius value was in range draw closing asymptote line
                            if(j&gt;0 &amp;&amp; values[j-1].rx &gt;= xMin &amp;&amp; rx &lt; xMin) {
                                path.line((this.xMin*this.stepX).toFixed(5), i);
                            }
                            path.move((this.xMax*this.stepX).toFixed(5), i);
                            //console.log(i,'-n closing');
                            closedAsymptote = true;
                        }
                        //draw to +Infinity
                        if(asymptote.val == -1) {
                            //if prevoius value was in range draw closing asymptote line
                            if(j &gt; 0 &amp;&amp; !(values[j-1].rx &gt; xMax &amp;&amp; rx &lt;= xMax)) {
                                path.line((this.xMax*this.stepX).toFixed(5), i);
                            }
                            path.move((this.xMin*this.stepX).toFixed(5), i);
                            //console.log(i,'+n closing');
                            closedAsymptote = true;
                        }
                    }
                } else {
                    closedAsymptote = false;
                    //if point in range
                    if(rx != Infinity &amp;&amp; rx != -Infinity &amp;&amp; !isNaN(rx)) {
                        //set starting point
                        if(positioning) {
                            path.move((rx*this.stepX).toFixed(5), i);
                            positioning = false;
                        }
                        //draw
                        if(!positioning) {
                            path.line((rx*this.stepX).toFixed(5),i);
                        }
                    } else {
                        positioning = true;
                    }
                }
            }

            return path;
        }

        this.isDiscontinuous = function(d1, d2, d3, dY) {
            if(Math.abs(dY) &gt; this.asymptoteMinimumDY &amp;&amp; d1 == 0 &amp;&amp; d2 == 0 &amp;&amp; d3 == 0 &amp;&amp; dY != 0) {
                return true;
            }
        };

        //detect asymptote
        this.hasAsymptote = function(d1, d2, d3, dY) {
            //if dY is greater than minimum step or is equal to +- infinity check deltas
            if(Math.abs(dY) &gt; this.asymptoteMinimumDY || dY == -Infinity || dY == Infinity) {
                if(d1 &gt; 0 &amp;&amp; d2 &gt; 0 &amp;&amp; d3 &gt; 0 &amp;&amp; d1 &lt; d2 &amp;&amp; d2 &lt; d3)	{
                    if(dY &lt; d3) {
                        return {
                            asymptote: true,
                            val: -1
                        };
                    } else {
                        return {
                            asymptote: false,
                            val: 0
                        };
                    }
                }
                if(d1 &lt; 0 &amp;&amp; d2 &lt; 0 &amp;&amp; d3 &lt; 0 &amp;&amp; d1 &gt; d2 &amp;&amp; d2 &gt; d3) {
                    if(dY &gt; d3) {
                        return {
                            asymptote: true,
                            val: 1
                        };
                    } else {
                        return {
                            asymptote: false,
                            val: 0
                        };
                    }
                }
            }

            return {
                asymptote: false,
                val: 0
            };
        }
        this._mapPlotVariables = function(p) {
            var variables = {};
            $.each(this.expressions[p].variables, function(k,v) {
                variables[k] = v.value;
            });

            return variables;
        }
        this._getPlotDomain = function(p) {
            var variables = this._mapPlotVariables(p);
            var xMin = this.expressions[p].xMin !== false ? Parser.evaluate(this.expressions[p].xMin.toString(), variables) : null;
            var xMax = this.expressions[p].xMax !== false ? Parser.evaluate(this.expressions[p].xMax.toString(), variables) : null;
            var yMin = this.expressions[p].yMin !== false ? Parser.evaluate(this.expressions[p].yMin.toString(), variables) : null;
            var yMax = this.expressions[p].yMax !== false ? Parser.evaluate(this.expressions[p].yMax.toString(), variables) : null;
            if(xMin === null || xMin &lt; this.xMin) {
                xMin = this.xMin;
            }
            if(xMax === null || xMax &gt; this.xMax) {
                xMax = this.xMax;
            }
            if(yMin === null || yMin &lt; this.yMin) {
                yMin = this.yMin;
            }
            if(yMax === null || yMax &gt; this.yMax) {
                yMax = this.yMax;
            }

            return {xMin: xMin, xMax:xMax, yMin: yMin, yMax: yMax};
        }
        this._drawXToY = function(p) {
            var path = this.svg.createPath();
            var rx, ry, i;
            var values = [];
            var sx = -this.offsetX;
            var ex = sx+this.width;
            var domain = this._getPlotDomain(p);
            var xMin = domain.xMin;
            var xMax = domain.xMax;
            var yMin = domain.yMin;
            var yMax = domain.yMax;

            //create variables for parser
            var variables = this._mapPlotVariables(p);

            //calculate values for every pixel
            for(i = sx; i &lt;= ex; i++) {
                rx = this.xMin+(i-sx)/this.stepX;
                //add x to parser variables
                variables.x = rx;
                try {
                    ry = Parser.evaluate(this.expressions[p].expression, variables);
                } catch (e) {
                    break;
                }
                if(ry &gt; this.INFINITY_POSITIVE_VALUE) {
                    //console.log(i,ry,'-n');
                    ry = Infinity;
                }
                if(ry &lt; this.INFINITY_NEGATIVE_VALUE) {
                    //console.log(i,ry,'+n');
                    ry = -Infinity;
                }

                values.push({
                   rx: rx.toFixed(5),
                   ry: ry.toFixed(5),
                   i: i
                });
            }

            var d1 = d2 = d3 = dY = 0;
            var asymptote = false;
            var positioning = true; //set position at the beginning of drawing
            var closedAsymptote = false;
            var prevXMin = prevXMax = prevYMin = prevYMax = false;

            for(var j = 0; j&lt;values.length; j+=1) {
                ry = values[j].ry;
                rx = values[j].rx;
                i = values[j].i;

                //xMin is set
                if(xMin !== null) {
                    //range check
                    if(rx &lt; xMin) {
                        prevXMin = rx;
                        positioning = true;
                        continue;
                    }
                    //if we pass from out of range to in range
                    if(prevXMin !== false &amp;&amp; prevXMin &lt; xMin &amp;&amp; rx &gt;= xMin) {
                        prevXMin = false;
                        //console.log('przejscie xmin',i,rx)
                        //calculate equation for xMin
                        variables.x = xMin;
                        try {
                            ry = Parser.evaluate(this.expressions[p].expression, variables);
                        } catch (e) {
                            break;
                        }
                        //force to draw y at draw point x
                        i = this.coords2px(xMin, 0).x;
                    }
                }

                //xMax is set
                if(xMax !== null) {
                    //we are in range
                    if(rx &lt;= xMax) {
                        prevXMax = rx;
                    }
                    //we pass xMax
                    if(prevXMax !== false &amp;&amp; prevXMax &lt;= xMax &amp;&amp; rx &gt; xMax) {
                        prevXMax = false;
                        //console.log('przejscie xmax',i,rx)
                        //calculate equation for xMin
                        variables.x = xMax;
                        try {
                            ry = Parser.evaluate(this.expressions[p].expression, variables);
                        } catch (e) {
                            break;
                        }
                        //force draw x value
                        i = this.coords2px(xMax, 0).x;
                    } else if(rx &gt; xMax) {
                        positioning = true;
                        prevXMax = false;
                        continue;
                    }
                }

                if(yMin !== null) {
                    //range check
                    if(ry &lt; yMin) {
                        prevYMin = ry;
                    }

                    //if we pass from out of range to in range - open path with yMin
                    if(prevYMin !== false &amp;&amp; prevYMin &lt; yMin &amp;&amp; ry &gt;= yMin) {
                        positioning = true;
                        prevYMin = false;
                        //console.log('wejscie ymin',i,rx,ry);
                        ry = yMin;
                    } else if(j&gt;0 &amp;&amp; values[j-1].ry &gt;= yMin &amp;&amp; ry &lt; yMin)  {
                        //if we pass from in range to out of range (close path)
                        positioning = false;
                        //console.log('zejscie ymin', i, rx, ry);
                        ry = yMin;
                    } else if( ry &lt; yMin){
                        continue;
                    }
                }

                if(yMax !== null) {
                    if(ry &lt;= yMax) {
                        prevYMax = ry;
                    }
                    //we pass yMax
                    if(prevYMax !== false &amp;&amp; prevYMax &lt;= yMax &amp;&amp; ry &gt; yMax) {
                        prevYMax = false;
                        //console.log('zejscie poza ymax',j,i,rx,ry)
                        ry = yMax;
                    } else if(j &gt; 0 &amp;&amp; values[j-1].ry &gt; yMax &amp;&amp; ry &lt;= yMax) {
                        //console.log('wejscie z poza ymax',j,i,rx,ry,values[j-1].ry )
                        positioning = true;
                        ry = yMax;
                    } else if(ry &gt; yMax) {
                        positioning = true;
                        prevYMax = false;
                        continue;
                    }
                }

                //calculate deltas
                if(j &gt; this.ASYMPTOTE_MINIMUM_TRIAL) {
                    dY = ry-values[j-1].ry;
                    d3 = values[j-1].ry - values[j-2].ry;
                    d2 = values[j-2].ry - values[j-3].ry;
                    d1 = values[j-3].ry - values[j-4].ry;
                }

                //detect discontinuous
                if(this.isDiscontinuous(d1, d2, d3, dY)) {
                    ry = Number.NaN;
                }
                //detect asymptote
                asymptote=this.hasAsymptote(d1, d2, d3, dY);

                if(asymptote.asymptote) {
                    //console.log('Asymptote: ',i, j,asymptote.asymptote,asymptote.val, rx,ry,'d:',d1,d2,d3,dY);
                    //draw graph to -+Infinity
                    //draw to -Infinity
                    if(!closedAsymptote) {
                        //draw to -Infinity
                        if(asymptote.val == 1) {
                            //if prevoius value was in range draw closing asymptote line
                            if(j&gt;0 &amp;&amp; values[j-1].ry &gt;= yMin &amp;&amp; ry &lt; yMin) {
                                path.line(i, (this.yMin*this.stepY).toFixed(5));
                            }
                            path.move(i, (this.yMax*this.stepY).toFixed(5));
                            //console.log(i,'-n closing');
                            closedAsymptote = true;
                        }
                        //draw to +Infinity
                        if(asymptote.val == -1) {
                            //if prevoius value was in range draw closing asymptote line
                            if(j &gt; 0 &amp;&amp; !(values[j-1].ry &gt; yMax &amp;&amp; ry &lt;= yMax)) {
                                path.line(i, (this.yMax*this.stepY).toFixed(5));
                            }
                            path.move(i, (this.yMin*this.stepY).toFixed(5));
                            //console.log(i,'+n closing');
                            closedAsymptote = true;
                        }
                    }
                }  else {
                    closedAsymptote = false;
                    //if point in range
                    if(ry != Infinity &amp;&amp; ry != -Infinity &amp;&amp; !isNaN(ry)) {
                        //set starting point
                        if(positioning) {
                            path.move(i, (ry*this.stepY).toFixed(5));
                            positioning = false;
                        }
                        //draw
                        if(!positioning) {
                            path.line(i, (ry*this.stepY).toFixed(5));
                        }
                    } else {
                        positioning = true;
                    }
                }
            }

            return path;
        }
        this.reset = function() {
            $.each(this.expressions, function(idx, val) {
                if(val.selectable) {
                    val.selected = false;
                }
                val.touched = false;
            })

            this.selectedPoints = [];
            $.each(this.points, function(idx, val) {
                if(val.initiallySelected) {
                    plot.selectedPoints.push({
                        x: val.x,
                        y: val.y,
                        clickable: val.clickable
                    });
                }
            });
            this.uiEnabled = true;
            this.restoreInitialViewPort();
        }
        this.restoreInitialViewPort = function() {
            this.xMin = this.initXMin;
            this.xMax = this.initXMax;
            this.yMin = this.initYMin;
            this.yMax = this.initYMax;
            this.setStep();
            this.translate();
            this.draw();
        }
        this.coords2px = function(cx, cy) {
            return {
                x:cx*this.stepX,
                y:cy*this.stepY
            }
        };
        this.px2coords = function(px, py) {
            var xRange = Math.max(this.xMin, this.xMax) - Math.min(this.xMin, this.xMax);
            var yRange = Math.max(this.yMin, this.yMax) - Math.min(this.yMin, this.yMax);

            var x = px*xRange/this.width+this.xMin;
            x = Math.round(this.precision.x*x)/this.precision.x;
            var y = (py*yRange/this.height)*(-1)+this.yMax;
            y = Math.round(this.precision.y*y)/this.precision.y;

            return {
                x: x,
                y: y
            };
        };

        this.calculateRange = function() {
            var xRange = Math.max(this.xMin, this.xMax) - Math.min(this.xMin, this.xMax);
            var offsetX = this.lastOffsetX*xRange/this.width
            this.lastOffsetX = 0;
            this.xMin -= offsetX;
            this.xMax -= offsetX;

            var yRange = Math.max(this.yMin, this.yMax) - Math.min(this.yMin, this.yMax);
            var offsetY = this.lastOffsetY*yRange/this.height
            this.lastOffsetY = 0;

            this.yMin -= offsetY;
            this.yMax -= offsetY;

            this.setStep();
        };

        this.calculatePrecision = function() {
            var idx = this.gridStepX.toString().indexOf('.');
            var idy = this.gridStepY.toString().indexOf('.');
            this.precision.x = idx !== -1 ? Math.pow(10, this.gridStepX.toString().length - idx - 1) : 100;
            this.precision.y = idy !== -1 ? Math.pow(10, this.gridStepY.toString().length - idx - 1) : 100;
        };

        /**
         * param int direction - -1 zoomOut, +1 zoomIn
         **/
        this.zoom = function(direction) {
            this.xMin -= this.xMin*this.zoomStep*direction;
            this.xMax -= this.xMax*this.zoomStep*direction;
            this.yMin -= this.yMin*this.zoomStep*direction;
            this.yMax -= this.yMax*this.zoomStep*direction;
            this.setStep();
            this.draw();
        };

        this.zoomIn = function(direction) {
            this.zoom(1);
        }

        this.zoomOut = function(direction) {
            this.zoom(-1);
        }

        this.move = function(dx, dy) {
            this.offsetX += dx;
            this.offsetY += dy;
            this.svgDoc.find('.translate').attr('transform', 'translate('+this.offsetX+', '+this.offsetY+')');
            this.lastOffsetX = dx;
            this.lastOffsetY = dy;
            this.calculateRange();
            this.draw();
        };

        this.moveLeft = function(dx) {
            this.move(parseInt(dx),0);
        }

        this.moveRight = function(dx) {
            this.move(parseInt(dx)*(-1),0);
        }

        this.moveUp = function(dy) {
            this.move(0, parseInt(dy)*(-1));
        }

        this.moveDown = function(dy) {
            this.move(0, parseInt(dy));
        }

        this._composeStyle = function(style) {
            var formattedStyle = '';
            $.each(style, function(o, v) {
                if(v!='') {
                    formattedStyle += o+':'+v+';'
                }
            });
            return formattedStyle;
        }
        this.enableUI = function(state) {
            this.svgDoc.find('.blocker').remove();
            if(state) {
                this.uiEnabled = true;
            } else {
                this.uiEnabled = false;
                this.svg.polygon(this.svgDoc.find('.overall'), [[-this.offsetX,-this.offsetY], [this.width-this.offsetX,-this.offsetY], [this.width-this.offsetX,-this.height-this.offsetY], [-this.offsetX, -this.height-this.offsetY]], {
                    'class':'blocker',
                    fill: '#000000',
                    'fill-opacity':0,
                    strokeWidth: 0
                });
            }
        }
        this.setVariable = function(id, variable, value) {
            if(id != '' &amp;&amp; variable != '') {
                $.each(this.expressions, function(idx, val){
                    if(val.id == id) {
                        if(val.variables[variable] != undefined) {
                            plot.attempted = true;
                            val.variables[variable]['value'] = value != '' ? value : 0;
                            if(val.variables[variable]['isExercise']) {
                                val.variables[variable]['touched'] = true;
                                //call chain of events
                                plot.stateChanged([
                                {
                                    item:'variable_'+val.id+'_'+variable,
                                    value:value,
                                    score: plot.isCorrectVariable(val.id, variable)
                                },

                                {
                                    item:'variables_'+val.id+'_'+variable,
                                    value:'',
                                    score: plot.areCorrectPlotVariables(val.id)
                                }
                                ]);
                            }
                        }
                        plot.removePlot(idx);
                        if(val.visible) {
                            plot.drawPlot(idx);
                        }
                    }

                });
            }
        }
        this.setVisible = function(id, value) {
            if(id != '') {
                $.each(this.expressions, function(idx, val) {
                    if(val.id == id) {
                        val.visible = value;
                        //remove existing plot
                        plot.removePlot(idx);
                        //show plot
                        if(value) {
                            plot.drawPlot(idx);
                        }
                    }
                });
            }
        }
        this.setPointVisibility = function(x, y, visibility) {
            if(x != '' &amp;&amp; y != '') {
                var refObj = plot.svgDoc.find('.point[vx="'+x+'"][vy="'+y+'"]');
                var refObjOutline = plot.svgDoc.find('.point_outline_base[vx="'+x+'"][vy="'+y+'"]');
                if(visibility &amp;&amp; !refObj.hasClass('point_selected')) {
                    this._selectPoint(x, y, false);
                } else if(!visibility &amp;&amp; refObj.hasClass('point_selected')) {
                    this._deselectPoint(x, y, false);
                }
            }
        }
        this.setPlotStyle = function(id, type, prop, value) {
            if(id != '') {
                if(type == 'plot') {
                    $.each(this.expressions, function(idx, val) {
                        if(val.id == id) {
                            if(prop == 'color' || prop == 'stroke') {
                                val.cssColor = value;
                            } else {
                                val.cssProperties[prop] = value;
                            }
                            //remove existing plot
                            plot.removePlot(idx);
                            //show plot
                            if(val.visible) {
                                plot.drawPlot(idx);
                            }
                        }
                    });
                }
            }
        }
        this.plotVariablesResult = function(plotId) {
            //check variables
            var variablesTodo = 0;
            var variablesDone = 0;
            var touched = false;
            $.each(this.expressions[plotId].variables, function(k,v) {
                if(v.isExercise) {
                    variablesTodo++;
                    if(v.expectedValue == v.value) {
                        variablesDone++;
                    }
                    if(v.touched) {
                        touched = true;
                    }
                }
            });

            return {
                todo: variablesTodo,
                done: variablesDone,
                touched: touched
            };
        };

        this.getPlotEventScore = function(pid) {
            if(!this.isActivity) {
                return this.STATE_NOT_ACTIVITY;
            }
            var state = this.STATE_INCORRECT;
            $.each(this.expressions, function(idx, val) {
                if(val.id == pid) {
                    if(val.selected == val.correctAnswer &amp;&amp; val.correctAnswer == true) {
                        state = plot.STATE_CORRECT;
                    } else if((val.correctAnswer == true &amp;&amp; val.selected == false) || (val.correctAnswer == false &amp;&amp; val.selected == false)) {
                        state = plot.STATE_NULL;
                    } else if(val.correctAnswer == false &amp;&amp; val.selected == true) {
                        state = plot.STATE_INCORRECT;
                    }
                    return false;
                }
            });
            return state;
        };
        this.isPointOnPlot = function(pid, x, y) {
            var rv, rc, domain;
            var isCorrect = false;
            $.each(this.expressions, function(idx, val) {
                if (val.id == pid) {
                    var variables = plot._mapPlotVariables(idx);
                    if(val.type == plot.TYPE_X_TO_Y) {
                        rc = y;
                        variables.x = x;
                    } else {
                        rc = x;
                        variables.y = y;
                    }
                    try {
                        rv = Parser.evaluate(plot.expressions[idx].expression, variables);
                    } catch (e) {
                        return false;
                    }

                    isCorrect = rv == rc;

                    //check if point is in range
                    if(isCorrect) {
                        domain = plot._getPlotDomain(idx);
                        if(x &lt; domain.xMin || x &gt; domain.xMax || y &lt; domain.yMin || y &gt; domain.yMax) {
                            isCorrect = false;
                        }
                    }
                    return false;
                }
            });

            return isCorrect;
        };
        this.getPointEventScore = function(vx, vy, state) {
            var currentState = this.STATE_NULL;
            var pointInDefinedPoints = false;
            if(!this.isActivity || (this.points.length == 0 &amp;&amp; this.freePoints)) {
                return this.STATE_NOT_ACTIVITY;
            }

            //check point in model points
            $.each(this.points, function(idx, val) {
                //we have this point
                if(val.x == vx &amp;&amp; val.y == vy) {
                    //check if should be selected or not
                    if((val.correct &amp;&amp; state == plot.STATE_SELECT_POINT) || (!val.correct &amp;&amp; state == plot.STATE_DESELECT_POINT)) {
                        currentState = plot.STATE_CORRECT;
                    } else if(state == plot.STATE_SELECT_POINT) {
                        currentState = plot.STATE_INCORRECT;
                    } else {
                        currentState = plot.STATE_NULL;
                    }
                    pointInDefinedPoints = true;
                    return false;
                }
            });

            if(pointInDefinedPoints) {
                return currentState;
            } else {
                return state == this.STATE_SELECT_POINT ? this.STATE_INCORRECT : this.STATE_NULL;
            }
        };

        this.isCorrectVariable = function(pid, variable) {
            var state = this.STATE_INCORRECT;
            if(!this.isActivity) {
                return this.STATE_NOT_ACTIVITY;
            }

            $.each(this.expressions, function(idx, val) {
                if(val.id == pid) {
                    state = val.variables[variable].value == val.variables[variable].expectedValue ? plot.STATE_CORRECT : plot.STATE_INCORRECT;
                    return false;
                }
            });

            return state;
        }

        this.areCorrectPlotVariables = function(pid) {
            if(!this.isActivity) {
                return this.STATE_NOT_ACTIVITY;
            }

            var state = this.STATE_CORRECT;
            $.each(this.expressions, function(idx, val) {
                if(val.id == pid) {
                    $.each(val, function(vidx, variableObj) {
                        if(variableObj.isExercise &amp;&amp; variableObj.value != variableObj.expectedValue) {
                            state = plot.STATE_INCORRECT;
                            return false;
                        }
                    })
                }
            })

            return state;
        }

        this.isAttempted = function () {
            return this.attempted;
        }

        this.setAttempted = function (state) {
            this.attempted = state;
        }
    }

    var presenter = function(){};
    var addonID;
    var plot = new Plot();
    presenter.isShowAnswersActive = false;
    presenter.isGradualShowAnswersActive = false;
    presenter.GSACounter = 0;
    presenter.ptpShowAnswersPoints = [];
    presenter.isPtpShowAnswersActive = false;

    presenter.ERROR_CODES = {
        'MARK_01': "Expression %expression% is invalid: mark at length must be empty or a positive number between 0 and 100",
    };

    presenter.showErrorMessage = function(message, substitutions) {
        let errorContainer;
        if (typeof(substitutions) == 'undefined') {
            errorContainer = '&lt;p&gt;' + message + '&lt;/p&gt;';
        } else {
            let messageSubst = message;
            for (let key in substitutions) {
                if (!substitutions.hasOwnProperty(key)) continue;
                messageSubst = messageSubst.replace('%' + key + '%', substitutions[key]);
            }
            errorContainer = '&lt;p&gt;' + messageSubst + '&lt;/p&gt;';
        }

        $(presenter.view).html(errorContainer);
    };

    function getErrorObject (errorCode, errorMessageSubstitutions) {
        return {
            isValid: false,
            errorCode: errorCode,
            errorMessageSubstitutions: errorMessageSubstitutions,
        };
    }

    function getCorrectObject (value) {
        return {
            isValid: true,
            value: value
        };
    }

    presenter.setWorkMode = function(){
        var ref;
        if(!presenter.isActivity) {
            return;
        }

        presenter.errorsMode = false;
        $.each(plot.expressions, function(idx, val) {
            ref = plot.svgDoc.find('.is_plot[uid="'+idx+'"]');
            if(ref.length &gt; 0) {
                ref.removeClass('draw_mark_error draw_'+(parseInt(idx)+1)+'_mark_error draw_mark_correct draw_'+(parseInt(idx)+1)+'_mark_correct');
                ref.attr('style', ref.data().cssStyle);
                plot.svgDoc.find('.draw_outline_base[ouid="'+idx+'"]').removeClass('draw_outline_mark_error draw_'+(parseInt(idx)+1)+'_outline_mark_error draw_outline_mark_correct draw_'+(parseInt(idx)+1)+'_outline_mark_correct');
            }
        });
        this.removePointsStateMarks();
        plot.removeMarks();
        plot.enableUI(true);
    };

    presenter.removePointsStateMarks = function() {
        plot.svgDoc.find('.point_error').removeClass('point_error');
        plot.svgDoc.find('.point_outline_mark_error').removeClass('point_outline_mark_error');
        plot.svgDoc.find('.point_correct').removeClass('point_correct');
        plot.svgDoc.find('.point_outline_mark_correct').removeClass('point_outline_mark_correct');
        plot.svgDoc.find('.point_outline_base').removeClass('plot_show_answers');
    };

    presenter.markPointAsError = function(x, y) {
        plot.svgDoc.find('.point[vx="' + x + '"][vy="' + y + '"]').addClass('point_error');
        plot.svgDoc.find('.point_outline_base[vx="' + x + '"][vy="' + y + '"]').addClass('point_outline_mark_error');
    };

    presenter.markPointAsCorrect = function(x, y) {
        plot.svgDoc.find('.point[vx="' + x + '"][vy="' + y + '"]').addClass('point_correct');
        plot.svgDoc.find('.point_outline_base[vx="' + x + '"][vy="' + y + '"]').addClass('point_outline_mark_correct');
    };

    presenter.setPointShowAnswersClass = function(x, y) {
        plot.svgDoc.find('.point_outline_base[vx="' + x + '"][vy="' + y + '"]').addClass('plot_show_answers');
    };

    presenter.onEventReceived = function (eventName, eventData) {
         switch (eventName) {
            case "ShowAnswers":
                presenter.showAnswers();
                break;
            case "HideAnswers":
                presenter.hideAnswers();
                break;
            case "GradualShowAnswers":
                presenter.gradualShowAnswers(eventData);
                break;
            case "GradualHideAnswers":
                presenter.gradualHideAnswers();
                break;
        }
    };

    presenter.showAnswers = function() {
        if (!presenter.isActivity) {
            return;
        }

        presenter.errorsMode &amp;&amp; presenter.setWorkMode();
        presenter.handleDisplayedAnswers();
        presenter.isShowAnswersActive = true;

        saveAndClearPlot();
        showAllExpressionsAnswers();
        showAllPointsAnswers();

        plot.enableUI(false);
    };

    presenter.gradualShowAnswers = function(eventData) {
        if (!presenter.isActivity || eventData.moduleID !== addonID) {
            return;
        }
        const answerIndex = parseInt(eventData.item, 10);
        gradualShowAnswers(answerIndex);
    };

    function gradualShowAnswers(answerIndex, includePrevious = false) {
        const isFirstGSA = !presenter.isGradualShowAnswersActive;
        presenter.errorsMode &amp;&amp; presenter.setWorkMode();
        presenter.isShowAnswersActive &amp;&amp; presenter.hideAnswers();
        presenter.isGradualShowAnswersActive = true;
        presenter.GSACounter = answerIndex;

        isFirstGSA &amp;&amp; saveAndClearPlot();
        const numberOfCorrectExpressions = plot.expressions.filter(val =&gt; val.correctAnswer).length;
        if (answerIndex &lt; numberOfCorrectExpressions) {
            showExpressionAnswer(answerIndex, includePrevious);
        } else {
            includePrevious &amp;&amp; showExpressionAnswer(numberOfCorrectExpressions - 1, includePrevious);
            showPointAnswer(answerIndex - numberOfCorrectExpressions, includePrevious);
        }

        plot.enableUI(false);
    }

    function saveAndClearPlot() {
        // save status of selected points
        presenter.clickedPoints = {};
        $.extend( true, presenter.clickedPoints, plot.selectedPoints );

        // clear plot from selected points
        $.each(presenter.clickedPoints, function(_, point) {
            plot._deselectPoint(point.x, point.y, true);
        });
        presenter.removePointsStateMarks();

        // clear plot from expressions
        $.each(plot.expressions, function(idx, val) {
            plot.removePlot(idx);
        });
    }

    presenter.setPtpShowAnswersPoints = function(points) {
        presenter.ptpShowAnswersPoints = points;
    }

    presenter.ptpShowAnswers = function(item) {
        presenter.handleDisplayedAnswers();
        presenter.isPtpShowAnswersActive = true;
        presenter.isShowAnswersActive = false;
        presenter.isGradualShowAnswersActive = false;
        presenter.GSACounter = item;
        presenter.saveAndClearPoints();

        for (var i = 0; i &lt; presenter.ptpShowAnswersPoints.length &amp;&amp; i &lt;= item; i++) {
            var point = presenter.ptpShowAnswersPoints[i];
            presenter.setPointShowAnswersClass(point.x, point.y);
        }

        presenter.enableUI(false);
    }

    presenter.saveAndClearPoints = function() {
        // save status of selected points
        presenter.clickedPoints = {};
        $.extend( true, presenter.clickedPoints, plot.selectedPoints );

        // clear plot from selected points
        $.each(presenter.clickedPoints, function(_, point) {
            plot._deselectPoint(point.x, point.y, true);
        });
        presenter.removePointsStateMarks();
    }

    function showAllExpressionsAnswers() {
        plot.expressions.forEach((expression, index) =&gt; {
            expression.correctAnswer &amp;&amp; showExpressionAsAnswer(index);
        })
    }

    function showExpressionAnswer(answerIndex, includePrevious = false) {
        let currentCorrectAnswerIndex = 0;
        for (let i = 0; i &lt; plot.expressions.length; i++) {
            if (!plot.expressions[i].correctAnswer) {
                continue;
            }
            if (currentCorrectAnswerIndex === answerIndex) {
                showExpressionAsAnswer(i);
                return;
            }
            includePrevious &amp;&amp; showExpressionAsAnswer(i);
            currentCorrectAnswerIndex++;
        }
    }

    function showExpressionAsAnswer (expressionIndex) {
        plot.drawPlot(expressionIndex);

        const $refObj = plot.svgDoc.find('.is_plot[uid="'+expressionIndex+'"]');
        $refObj.addClass('show_answers');
        $refObj.removeAttr('style');
        $refObj.removeClass('draw_selected');

        const $refObjOutline = plot.svgDoc.find('.draw_outline_base[ouid="'+expressionIndex+'"]');
        $refObjOutline.addClass('show_answers');
        $refObjOutline.removeClass('draw_outline');
        $refObjOutline.removeClass('draw_"' + expressionIndex + '"_outline');
    }

    function showAllPointsAnswers() {
        plot.points.forEach((point, index) =&gt; {
            point.correct &amp;&amp; showPointAsAnswer(index);
        })
    }

    function showPointAnswer(answerIndex, includePrevious = false) {
        let currentCorrectAnswerIndex = 0;
        for (let i = 0; i &lt; plot.points.length; i++) {
            if (!plot.points[i].correct) {
                continue;
            }
            if (currentCorrectAnswerIndex === answerIndex) {
                showPointAsAnswer(i);
                return;
            }
            includePrevious &amp;&amp; showPointAsAnswer(i);
            currentCorrectAnswerIndex++;
        }
    }

    function showPointAsAnswer(pointIndex) {
        const point = plot.points[pointIndex];
        presenter.setPointShowAnswersClass(point.x, point.y);
    }

    presenter.hideAnswers = function() {
        if (!presenter.isActivity || !presenter.isShowAnswersActive) {
            return;
        }

        presenter.isShowAnswersActive = false;
        _hideAnswers();
    };

    presenter.gradualHideAnswers = function() {
        if (!presenter.isActivity || !presenter.isGradualShowAnswersActive) {
            return;
        }

        presenter.isGradualShowAnswersActive = false;
        presenter.GSACounter = 0;
        _hideAnswers();
    };

    presenter.ptpHideAnswers = function() {
        if (!presenter.isPtpShowAnswersActive) return;

        presenter.isPtpShowAnswersActive = false;
        presenter.GSACounter = 0;
        _hideAnswers();
    };

    function _hideAnswers() {
        // POINTS
        presenter.removePointsStateMarks();

        $.each(presenter.clickedPoints, function(_, point) {
            plot._selectPoint(point.x, point.y, true);
        });

        //FUNCTIONS
        $.each(plot.expressions, function(idx, val) {
            if(val.correctAnswer) {
                plot.removePlot(idx);
            }
        });

        plot.drawPlots();

        plot.enableUI(true);
    }

    presenter.setShowErrorsMode = function() {
        if(!presenter.isActivity) {
            return;
        }

        presenter.handleDisplayedAnswers();

        var res;
        presenter.errorsMode = true;

        plot.areIndividualMarks() &amp;&amp; plot.createIndividualMarksSVGDOMGroup();
        plot.enableUI(false);

        $.each(plot.expressions, function(idx, val) {
            if(val.selectable &amp;&amp; val.touched) {
                if(val.correctAnswer == false &amp;&amp; val.selected == true) {
                    //mark error
                    plot.svgDoc.find('.is_plot[uid="'+idx+'"]').addClass('draw_mark_error draw_'+(parseInt(idx)+1)+'_mark_error').removeAttr('style');
                    plot.svgDoc.find('.draw_outline_base[ouid="'+idx+'"]').addClass('draw_outline_mark_error draw_'+(parseInt(idx)+1)+'_outline_mark_error');
                    plot.areIndividualMarks() &amp;&amp; plot.drawErrorMarkForExpression(idx);
                } else if(val.correctAnswer == true &amp;&amp; val.selected == true) {
                    //mark as correct only plots which are selectable, selected, touched and it\'s correct answer is true (selected)
                    plot.svgDoc.find('.is_plot[uid="'+idx+'"]').addClass('draw_mark_correct draw_'+(parseInt(idx)+1)+'_mark_correct').removeAttr('style');
                    plot.svgDoc.find('.draw_outline_base[ouid="'+idx+'"]').addClass('draw_outline_mark_correct draw_'+(parseInt(idx)+1)+'_outline_mark_correct');
                    plot.areIndividualMarks() &amp;&amp; plot.drawCorrectMarkForExpression(idx);
                }
            }
            res = plot.plotVariablesResult(idx);
            if(res.todo != res.done &amp;&amp; res.todo &gt; 0 &amp;&amp; res.touched) {
                //mark error
                plot.svgDoc.find('.is_plot[uid="'+idx+'"]').addClass('draw_mark_error draw_'+(parseInt(idx)+1)+'_mark_error').removeAttr('style');
                plot.svgDoc.find('.draw_outline_base[ouid="'+idx+'"]').addClass('draw_outline_mark_error draw_'+(parseInt(idx)+1)+'_outline_mark_error');
                plot.areIndividualMarks() &amp;&amp; plot.drawErrorMarkForExpression(idx);
            } else if(res.todo == res.done &amp;&amp; res.todo &gt; 0 &amp;&amp; res.touched) {
                //mark correct
                plot.svgDoc.find('.is_plot[uid="'+idx+'"]').addClass('draw_mark_correct draw_'+(parseInt(idx)+1)+'_mark_correct').removeAttr('style');
                plot.svgDoc.find('.draw_outline_base[ouid="'+idx+'"]').addClass('draw_outline_mark_correct draw_'+(parseInt(idx)+1)+'_outline_mark_correct');
                plot.areIndividualMarks() &amp;&amp; plot.drawCorrectMarkForExpression(idx);
            }
        });

        if(plot.points.length &gt; 0) {
            $.each(plot.points, function(idx, val) {
                res = presenter.grepPoints(plot.selectedPoints, val);
                if(val.notScored === false) {
                    if(!val.correct &amp;&amp; res &amp;&amp; val.touched) {
                        //mark wrong
                        presenter.markPointAsError(val.x, val.y);
                        plot.areIndividualMarks() &amp;&amp; plot.drawErrorMarkForPoint(val.x, val.y);
                    } else if(val.correct &amp;&amp; res &amp;&amp; val.touched) {
                        //mark correct
                        presenter.markPointAsCorrect(val.x, val.y);
                        plot.areIndividualMarks() &amp;&amp; plot.drawCorrectMarkForPoint(val.x, val.y);
                    }
                }
            });

            //check excess points
            $.each(plot.selectedPoints, function(idx, val) {
                res = presenter.grepPoints(plot.points, val);
                if(!res) {
                    presenter.markPointAsError(val.x, val.y);
                    plot.areIndividualMarks() &amp;&amp; plot.drawErrorMarkForPoint(val.x, val.y);
                }
            });
        }

        if (plot.isOneMark()) {
            if (presenter.getErrorCount() &gt; 0) {
                plot.drawOneErrorMark();
            } else if (presenter.getMaxScore() === presenter.getScore()) {
                plot.drawOneCorrectMark();
            }
        }
    };

    presenter.getMaxScore = function(){
        if(!presenter.isActivity) {
            return 0;
        }

        var todo = 0;

        $.each(plot.expressions, function(idx, val) {
            //include selectable plots
            if(val.selectable &amp;&amp; val.correctAnswer) {
                todo++;
            }
            //include expected variables
            var res = plot.plotVariablesResult(idx);
            if(res.todo &gt; 0) {
                todo++;
            }
        });
        //include all scorable points
        $.each(plot.points, function(idx, val) {
            if(val.notScored === false) {
                todo++;
            }
        })

        return todo;
    }

    presenter.getScore = function(){
        if(!presenter.isActivity) {
            return 0;
        }

        var done = 0;
        var res;

        $.each(plot.expressions, function(idx, val) {
            if(val.selectable &amp;&amp; val.correctAnswer == val.selected &amp;&amp; val.correctAnswer) {
                done++;
            }
            //get variables score
            res = plot.plotVariablesResult(idx);
            if(res.todo &gt; 0 &amp;&amp; res.todo == res.done) {
                done++;
            }
        });

        $.each(plot.points, function(idx, val) {
            res = presenter.grepPoints(plot.selectedPoints, val);
            if(((val.correct &amp;&amp; res) || (!val.correct &amp;&amp; !res)) &amp;&amp; val.notScored === false) {
                done++;
            }
        })

        return done;
    }

    presenter.grepPoints = function(arr, point) {
        var res = false;
        $.each(arr, function(idx, el) {
            if(el.x == point.x &amp;&amp; el.y == point.y) {
                res = true;
                return;
            }
        });

        return res;
    }

    presenter.getErrorCount = function(){
        if(!presenter.isActivity) {
            return 0;
        }

        var errors = 0;
        var res;
        $.each(plot.expressions, function(idx, val) {
            if(val.selectable &amp;&amp; val.correctAnswer == false &amp;&amp; val.selected == true &amp;&amp; val.touched) {
                errors++;
            }
            //check variables
            res = plot.plotVariablesResult(idx);
            if(res.todo != res.done &amp;&amp; res.touched) {
                errors++;
            }
        })

        //check points
        if(plot.points.length &gt; 0) {
            $.each(plot.points, function(idx, val) {
                res = presenter.grepPoints(plot.selectedPoints, val);
                //var res = $.grep(plot.selectedPoints, function(e){ return e.x == plot.points[p].x &amp;&amp; e.y == plot.points[p].y; });
                if(!val.correct &amp;&amp; res &amp;&amp; val.touched &amp;&amp; val.notScored === false) {
                    errors++;
                }
            })

            $.each(plot.selectedPoints, function(idx, val) {
                res = presenter.grepPoints(plot.points, val);
                //var res = $.grep(plot.points, function(e){ return e.x == plot.selectedPoints[p].x &amp;&amp; e.y == plot.selectedPoints[p].y; });
                if(!res) {
                    errors++;
                }
            });
        }

        return errors;
    }

    presenter.reset = function(){
        presenter.handleDisplayedAnswers();

        presenter.errorsMode = false;
        presenter._allDoneState = false;
        plot.setAttempted(false);
        $.each(plot.expressions, function(idx, val) {
            val.touched = false;
            val.cssColor = val.cssColorInitialValue;
            val.cssProperties = {};
            val.visible = val.initVisible;
            $.each(val.variables, function(vidx, variableObj) {
                variableObj.touched = false;
                variableObj.value = variableObj.initialValue;
            })
        });

        $.each(plot.points, function(idx, val) {
            val.touched = false;
        })
        plot.reset();
        presenter.isVisible = presenter.initIsVisible;
        presenter.updateVisibility();
    };

    presenter.run = function(view, model){
        model = presenter.upgradeModel(model);
        presenter.errorsMode = false;
        presenter.view = view;
        presenter.model = model;
        presenter._allDoneState = false;

        addonID = model.ID;

        presenter.initialize(presenter.view, presenter.model, true);

        const events = ["ShowAnswers", "HideAnswers", "GradualShowAnswers", "GradualHideAnswers"];
        events.forEach((eventName) =&gt; {
            presenter.eventBus.addEventListener(eventName, this);
        });
    };

    presenter.upgradeModel = function (model) {
        let upgradedModel = presenter.upgradeMarks(model);
        return presenter.upgradeYAxisValuesPosition(upgradedModel);
    };

    presenter.upgradeMarks = function (model) {
        const upgradedModel = {};
        jQuery.extend(true, upgradedModel, model);

        if (upgradedModel.hasOwnProperty("Expressions")) {
            upgradedModel.Expressions.map(function (expression) {
                if (!expression["mark at length"]) {
                    expression["mark at length"] = "50";
                }
            });
        }

        if (!upgradedModel.hasOwnProperty("Check marks")) {
            upgradedModel["Check marks"] = "No";
        }

        if (!upgradedModel["Correct marks HTML"]) {
            upgradedModel["Correct marks HTML"] = "&amp;#10004;";
        }

        if (!upgradedModel["Error marks HTML"]) {
            upgradedModel["Error marks HTML"] = "&amp;#10006;";
        }

        return upgradedModel;
    };

    presenter.upgradeYAxisValuesPosition = function (model) {
        const upgradedModel = {};
        jQuery.extend(true, upgradedModel, model);

        if (!upgradedModel.hasOwnProperty("Y axis values position")) {
            upgradedModel["Y axis values position"] = "";
        }

        return upgradedModel;
    };

    presenter.setPlayerController = function(controller) {
        presenter.playerController = controller;
        presenter.eventBus = presenter.playerController.getEventBus();
    };

    presenter.updateVisibility = function() {
        presenter.isVisible ? presenter.show() : presenter.hide();
    };

    presenter.initialize = function(view, model, isInteractive) {
        var v, p, el;
        this._model = model;
        presenter.isVisible = ModelValidationUtils.validateBoolean(model["Is Visible"]);
        presenter.initIsVisible = presenter.isVisible;
        presenter.decimalSeparator = (model['Decimal separator'] === undefined || model['Decimal separator'] == '') ? '.' : model['Decimal separator'];
        if(presenter.decimalSeparator != '.' &amp;&amp; presenter.decimalSeparator != ',') {
            presenter.decimalSeparator = '.';
        };
        if (isInteractive) {
            presenter.updateVisibility();
        } else {
            presenter.show();
        }
        plot.interactive = isInteractive;
        plot.width = parseInt(model['Width']);
        plot.height = parseInt(model['Height']);
        plot.xMin = (model['xMin'] == '' &amp;&amp; model['xMin'] != '0' ? -10 : parseFloat(this.toDotSeparator(model['xMin'])));
        plot.xMax = (model['xMax'] == '' &amp;&amp; model['xMax'] != '0' ? 10 : parseFloat(this.toDotSeparator(model['xMax'])));
        plot.yMin = (model['yMin'] == '' &amp;&amp; model['yMin'] != '0' ? -10 : parseFloat(this.toDotSeparator(model['yMin'])));
        plot.yMax = (model['yMax'] == '' &amp;&amp; model['yMax'] != '0' ? 10 : parseFloat(this.toDotSeparator(model['yMax'])));
        plot.initXMin = plot.xMin;
        plot.initXMax = plot.xMax;
        plot.initYMin = plot.yMin;
        plot.initYMax = plot.yMax;
        plot.grid = model['Grid'].toLowerCase() === 'true' ? true : false;
        plot.gridStepX = Math.abs(parseFloat(this.toDotSeparator(model['GridStepX']))) || 1;
        plot.gridStepY = Math.abs(parseFloat(this.toDotSeparator(model['GridStepY']))) || 1;
        plot.arrowheadSize = parseInt(model['Arrowhead size']) || 6;
        plot.asymptoteMinimumDY = this.toDotSeparator(model['Asymptote DY']) || 5;
        plot.axisValues = model['Axis values'].toLowerCase() === 'true' ? true : false;
        plot.xAxisDescription = model['X axis description'] || 'x';
        plot.yAxisDescription = model['Y axis description'] || 'y';
        plot.xAxisVisible = model['hide X axis'] === undefined || model['hide X axis'].toLowerCase() === 'false' || model['hide X axis'] == '' ? true : false;
        plot.yAxisVisible = model['hide Y axis'] === undefined || model['hide Y axis'].toLowerCase() === 'false' || model['hide Y axis'] == '' ? true : false;
        plot.xAxisValuesPosition = model['X axis values position'];
        plot.yAxisValuesPosition = model['Y axis values position'];
        var xAxisValues = model['Axis x values'] === undefined || model['Axis x values'] == '' || this._hasIllegalCharacters(model['Axis x values'].toString()) ? false : model['Axis x values'].toString().split(this.getSeparatorByDecimalSeparator());
        var yAxisValues = model['Axis y values'] === undefined || model['Axis y values'] == '' || this._hasIllegalCharacters(model['Axis y values'].toString()) ? false : model['Axis y values'].toString().split(this.getSeparatorByDecimalSeparator());
        if(xAxisValues !== false &amp;&amp; xAxisValues.length &gt; 0) {
            $.each(xAxisValues, function(idx, val) {
                if(val.toString().match(/\*/) !== null) {
                    plot.xAxisCyclicValues.push(presenter.toDotSeparator(parseInt(val)));
                } else {
                    plot.xAxisCustomValues.push(parseFloat(presenter.toDotSeparator(val)));
                }
            });
        }
        if(yAxisValues !== false &amp;&amp; yAxisValues.length &gt; 0) {
            $.each(yAxisValues, function(idx, val) {
                if(val.toString().match(/\*/) !== null) {
                    plot.yAxisCyclicValues.push(presenter.toDotSeparator(parseInt(val)));
                } else {
                    plot.yAxisCustomValues.push(parseFloat(presenter.toDotSeparator(val)));
                }
            });
        }
        plot.expressions = new Array();
        plot.pointActiveArea = parseInt(this.toDotSeparator(model['Point active area size'])) || 10;
        plot.pointRadius = parseInt(this.toDotSeparator(model['Point radius'])) || 3;
        plot.pointOutlineRadius = parseInt(this.toDotSeparator(model['Point outline radius'])) || 7;

        for (p in model['Expressions']) {
            if(model['Expressions'][p]['expression'] != '') {
                el = {
                    id: model['Expressions'][p]['id'] === undefined || model['Expressions'][p]['id'] == '' ? p : model['Expressions'][p]['id'],
                    expression: this.toDotSeparator(model['Expressions'][p]['expression']),
                    selectable: model['Expressions'][p]['selectable'].toLowerCase() === 'true' ? true : false,
                    selected: false,
                    correctAnswer: model['Expressions'][p]['correct'].toLowerCase() === 'true' ? true : false,
                    xMin: model['Expressions'][p]['xMin'] === undefined || (model['Expressions'][p]['xMin'] == '' &amp;&amp; model['Expressions'][p]['xMin'] != '0') ? false : this.toDotSeparator(model['Expressions'][p]['xMin']),
                    xMax: model['Expressions'][p]['xMax'] === undefined || (model['Expressions'][p]['xMax'] == '' &amp;&amp; model['Expressions'][p]['xMax'] != '0') ? false : this.toDotSeparator(model['Expressions'][p]['xMax']),
                    yMin: model['Expressions'][p]['yMin'] === undefined || (model['Expressions'][p]['yMin'] == '' &amp;&amp; model['Expressions'][p]['yMin'] != '0') ? false : this.toDotSeparator(model['Expressions'][p]['yMin']),
                    yMax: model['Expressions'][p]['yMax'] === undefined || (model['Expressions'][p]['yMax'] == '' &amp;&amp; model['Expressions'][p]['yMax'] != '0') ? false : this.toDotSeparator(model['Expressions'][p]['yMax']),
                    variables: {},
                    initVisible: model['Expressions'][p]['hidden'] !== undefined &amp;&amp; model['Expressions'][p]['hidden'] != '' &amp;&amp; model['Expressions'][p]['hidden'].toLowerCase() === 'true' ? false : true,
                    type: model['Expressions'][p]['y to x'] === undefined || model['Expressions'][p]['y to x'] == '' || model['Expressions'][p]['y to x'].toLowerCase() === 'false' ? plot.TYPE_X_TO_Y : plot.TYPE_Y_TO_X,
                    touched: false,
                    cssColorInitialValue: model['Expressions'][p]['color'] === undefined || model['Expressions'][p]['color'] == '' ? '' : model['Expressions'][p]['color'],
                    cssProperties: {}
                };
                el.cssColor = el.cssColorInitialValue;
                el.visible = el.initVisible;

                const validatedMarkAtLength = presenter.validateMarkAtLength(el.id, model["Expressions"][p]["mark at length"]);
                if (!validatedMarkAtLength.isValid) {
                    presenter.showErrorMessage(presenter.ERROR_CODES[validatedMarkAtLength.errorCode], validatedMarkAtLength.errorMessageSubstitutions);
                    return;
                }
                el.markAtLength = validatedMarkAtLength.value;
                plot.expressions.push(el);
            }
        }
        for(v in model['Variables']) {
            var plotId = model['Variables'][v]['plot id'];
            var variable = model['Variables'][v]['variable'];
            var variableValue = this.toDotSeparator(model['Variables'][v]['value']);
            var expectedValue = model['Variables'][v]['expected'] === undefined || (model['Variables'][v]['expected'] == '' &amp;&amp; model['Variables'][v]['expected'] != '0') ? null : this.toDotSeparator(model['Variables'][v]['expected']);

            if(plotId != '' &amp;&amp; variable != '') {
                for (var ex in plot.expressions) {
                    if(plot.expressions[ex].id == plotId) {
                        var iv = variableValue != '' ? parseFloat(variableValue) : 0;
                        el = {
                            initialValue: this.valueToFloat(variableValue),
                            value: this.valueToFloat(variableValue),
                            isExercise: expectedValue ? true : false,
                            expectedValue: parseFloat(expectedValue),
                            touched: false
                        };
                        plot.expressions[ex].variables[variable] = el;
                    }
                }
            }
        }

        plot.points = new Array();
        for (p in model['Points']) {
            if((model['Points'][p]['x value'] == '0' || model['Points'][p]['y value'] == '0') || (model['Points'][p]['x value'] != '' &amp;&amp; model['Points'][p]['y value'] != '')) {
                var point = {
                    x: this.toDotSeparator(model['Points'][p]['x value']),
                    y: this.toDotSeparator(model['Points'][p]['y value']),
                    initiallySelected: model['Points'][p]['selected'] === undefined || model['Points'][p]['selected'].toLowerCase() === 'false' ? false : true,
                    correct: model['Points'][p]['correct'] === undefined || model['Points'][p]['correct'].toLowerCase() === 'true' ? true : false,
                    touched: false,
                    notScored: model['Points'][p]['not scored'] === undefined || model['Points'][p]['not scored'].toLowerCase() === 'false' || model['Points'][p]['not scored'] == '' ? false : true,
                    clickable: true
                }
                //if we don't want that point in score make it non-clickable
                if(point.notScored) {
                    point.clickable = false;
                }
                plot.points.push(point);
                if(point.initiallySelected) {
                    plot.selectedPoints.push({
                        x: point.x,
                        y: point.y,
                        clickable: point.clickable
                    });
                }
            }
        }
        plot.maxSelectedPoints = model['Max selected points'] === undefined || model['Max selected points'] == '' || model['Max selected points'] == 0 ? 0 : parseInt(model['Max selected points']);
        presenter.isActivity = model['Not activity'] !== undefined &amp;&amp; model['Not activity'] != '' &amp;&amp; model['Not activity'].toLowerCase() === 'true' ? false : true;
        plot.isActivity = presenter.isActivity;
        plot.freePoints = model['Free points'] !== undefined &amp;&amp; model['Free points'] != '' &amp;&amp; model['Free points'].toLowerCase() === 'true' ? true : false;

        presenter.broadcast = [];
        if(model['Broadcast'] !== '' &amp;&amp; model['Broadcast'] !== undefined) {
            var broadcasts = model['Broadcast'].split(',');
            $.each(broadcasts, function(idx, val) {
                presenter.broadcast.push(val.trim());
            });
        }
        plot.stateChanged = presenter.stateChanged;
        plot.convertValueToDisplay = presenter.convertValueToDisplay;
        plot.calculatePrecision();

        var canvas = $(view).find('.canvas:first');
        canvas.click(function(e) {
            e.stopPropagation();
        });

        canvas.svg({
            onLoad: presenter.onSvgCreate,
            settings: {
                width: '100%',
                height: '100%',
                preserveAspectRatio: "none"
            }
        });

        if(plot.xAxisValuesPosition != '' &amp;&amp; !isNaN(plot.xAxisValuesPosition)){
            plot.setXAxisValuesPosition(plot.xAxisValuesPosition);
        }

        if(plot.yAxisValuesPosition != '' &amp;&amp; !isNaN(plot.yAxisValuesPosition)){
            plot.setYAxisValuesPosition(plot.yAxisValuesPosition);
        }

        plot.checkMarks = model["Check marks"];
        plot.correctMarksHTML = model["Correct marks HTML"];
        plot.errorMarksHTML = model["Error marks HTML"];
    };

    presenter.toDotSeparator = function(value) {
        return (value + '').replace(this.decimalSeparator, '.');
    };

    presenter.getSeparatorByDecimalSeparator = function() {
        return this.decimalSeparator == '.' ? ',' : ';';
    };

    presenter.convertValueToDisplay = function(value) {
        //set correct decimal separator
        value = presenter.replaceDecimalSeparator(value);
        //change minus to en dash
        value = value.replace(new RegExp('\-', 'g'), '\u2013');
        return value;
    };

    presenter.replaceDecimalSeparator = function(value) {
        return (value + '').replace(new RegExp('\\.', 'g'), presenter.decimalSeparator);
    };

    presenter.getDecimalSeparator = function() {
        return presenter.decimalSeparator;
    };

    presenter.validateMarkAtLength = function (expressionIdx, value) {
        const percentage = presenter.valueToPercentage(value);
        if (isNaN(percentage) || percentage &lt; 0 || percentage &gt; 1) {
            return getErrorObject("MARK_01", {expression: expressionIdx});
        }
        return getCorrectObject(percentage);
    }

    presenter.valueToFloat = function(val) {
        if(val === '' || val === undefined || !this.isCorrectDecimal(val)) {
            return Number.NaN;
        }
         val = this.toDotSeparator(val);
         return parseFloat(val);
    };

    presenter.valueToPercentage = function(value) {
        value = parseFloat(value, 10);
        if (isNaN(value)) {
            return value;
        }
        return value/100;
    };

    presenter._hasIllegalCharacters = function(word) {
        var tmpWord;
        if(this.decimalSeparator == ',') {
            //use ';' for ',' seprator
            tmpWord = word.replace(/[^\*0-9;,-]/g, '');
        } else {
            //use ',' for '.' separator
            tmpWord = word.replace(/[^\*0-9,.-]/g, '');
        }
        return tmpWord != word;
    };

    presenter.isCorrectDecimal = function(nmb) {
        if (nmb === null ||
                ModelValidationUtils.isStringEmpty(nmb) ||
                (this.getDecimalSeparator() === ',' &amp;&amp; nmb.toString().indexOf('.') !== -1) ||
                (this.getDecimalSeparator() === '.' &amp;&amp; nmb.toString().indexOf(',') !== -1)) {
            return false;
        }

        return true;
    };

    presenter.createPreview = function(view, model) {
        model = presenter.upgradeModel(model);
        presenter.errorsMode = false;
        presenter.view = view;
        presenter.model = model;
        presenter.initialize(presenter.view, presenter.model, false);
    };

    presenter.onSvgCreate = function(svg) {
        plot.svg = svg;
        plot.svgDoc = $(plot.svg._svg);
        var plotScale = plot.svg.group({
            class_:'scale',
            transform:''
        });
        var plotTranslate = plot.svg.group(plotScale, {
            class_:'translate',
            transform:''
        });
        var plotOverall = plot.svg.group(plotTranslate, {
            class_:'overall'
        });
        plot.svg.group(plotOverall, {
            class_:'grid'
        });
        plot.svg.group(plotOverall, {
            class_:'axis'
        });
        plot.svg.group(plotOverall, {
            class_:'drawings'
        });
        plot.svg.group(plotOverall, {
            class_:'points'
        });
        plot.svg.group(plotOverall, {
            class_:'drawingsAreas'
        });
        plot.svg.group(plotOverall, {
            class_:'pointsAreas'
        });
        plot.setScale();
        plot.setStep();
        plot.translate();
        plot.draw();
        if(!presenter.isVisible) {
            presenter.hide();
        }
    };

    presenter.executeCommand = function(name, params) {
        switch(name.toLowerCase()) {
            case 'zoomIn'.toLowerCase():
                plot.zoom(1);
                break;
            case 'zoomOut'.toLowerCase():
                plot.zoom(-1);
                break;
            case 'moveLeft'.toLowerCase():
                plot.move(parseInt(this.toDotSeparator(params)),0);
                break;
            case 'moveRight'.toLowerCase():
                plot.move(parseInt(this.toDotSeparator(params))*(-1),0);
                break;
            case 'moveDown'.toLowerCase():
                plot.move(0, parseInt(this.toDotSeparator(params)));
                break;
            case 'moveUp'.toLowerCase():
                plot.move(0, parseInt(this.toDotSeparator(params))*(-1));
                break;
            case 'getState'.toLowerCase():
                return this.getState();
                break;
            case 'setState'.toLowerCase():
                this.setState(params);
                break;
            case 'setVariable'.toLowerCase():
                plot.setVariable(params[0], params[1], presenter.valueToFloat(params[2]));
                break;
            case 'setVisible'.toLowerCase():
                plot.setVisible(params[0], parseInt(params[1]) == 1 ? true : false);
                break;
            case 'setPointVisibility'.toLowerCase():
                plot.setPointVisibility(this.toDotSeparator(params[0]), this.toDotSeparator(params[1]), parseInt(params[2]) == 1 ? true : false);
                break;
            case 'setPlotStyle'.toLowerCase():
                plot.setPlotStyle(params[0], params[1], params[2], params[3]);
                break;
            case 'hide'.toLowerCase():
                presenter.hide();
                break;
            case 'show'.toLowerCase():
                presenter.show();
                break;
            case 'restoreView'.toLowerCase():
                plot.restoreInitialViewPort();
                break;
            case 'isAttempted'.toLowerCase():
                return presenter.isAttempted();
                break;
            case 'setAttempted'.toLocaleLowerCase():
                presenter.setAttempted(params);
                break;
            case 'isAllOK'.toLocaleLowerCase():
                return presenter.isAllOK();
        }
        if(presenter.errorsMode) {
            presenter.setShowErrorsMode();
        }
    };

    presenter.enableUI = function(state) {
        plot.enableUI(state);
    };

    presenter.hide = function() {
        presenter.handleDisplayedAnswers();
        presenter.isVisible = false;
        presenter.setVisibility(false);
    };

    presenter.show = function() {
        presenter.handleDisplayedAnswers();
        presenter.isVisible = true;
        presenter.setVisibility(true);
    };

    presenter.setVisibility = function(isVisible) {
        $(presenter.view).css("visibility", isVisible ? "visible" : "hidden");
    };

    presenter.getState = function() {
        const wasShowAnswersActive = presenter.isShowAnswersActive;
        const wasGradualShowAnswersActive = presenter.isGradualShowAnswersActive;
        const wasPtpShowAnswersActive = presenter.isPtpShowAnswersActive;
        const previousGSACounter = presenter.GSACounter;
        presenter.handleDisplayedAnswers();

        var plotState = [];
        var variableState = [];
        var pointsState = [];

        $.each(plot.expressions, function(idx, val) {
            plotState[idx] = {
                selected: val.selected,
                touched: val.touched,
                visible: val.visible
            }
            //old state
            //plotState[p] = val.selected;

            $.each(val.variables, function(vidx, variableObj) {
                variableState.push({
                    plotId: val.id,
                    variable: vidx,
                    value: variableObj.value,
                    touched: variableObj.touched
                });
            });
        })

        $.each(plot.points, function(idx, val) {
            pointsState[idx] = {
                touched: val.touched
            }
        });

        var state = JSON.stringify({
            version: 2,
            plots: plotState,
            variables: variableState,
            selectedPoints: plot.selectedPoints,
            points: pointsState,
            isVisible: presenter.isVisible,
            isAttempted: presenter.isAttempted()
        });

        wasShowAnswersActive &amp;&amp; presenter.showAnswers();
        wasGradualShowAnswersActive &amp;&amp; gradualShowAnswers(previousGSACounter, true);
        wasPtpShowAnswersActive &amp;&amp; presenter.ptpShowAnswers(previousGSACounter);
        return state;
    };

    presenter.setState = function(state) {
        if(state != '' &amp;&amp; state !== undefined) {
            state = JSON.parse(state);
            //old state
            if(state.version == undefined) {
                if(state.plots.length &gt; 0) {
                    $.each(state.plots, function(idx, val) {
                        plot.expressions[idx].touched = true;
                        plot.expressions[idx].selected = val;

                    });
                    plot.selectedPoints = state.points;
                }
            } else {
                if(state.plots.length &gt; 0) {
                    $.each(state.plots, function(idx, val) {
                        plot.expressions[idx].selected = val.selected;
                        plot.expressions[idx].touched = val.touched;
                        plot.expressions[idx].visible = val.visible != undefined ? val.visible : true;
                    });
                }
                //restore variables
                if(state.variables.length &gt; 0) {
                    $.each(state.variables, function(sidx, sval) {
                        $.each(plot.expressions, function(pidx, pval) {
                            if(sval.plotId == pval.id) {
                                pval.variables[sval.variable].value = presenter.valueToFloat(sval.value);
                                pval.variables[sval.variable].touched = sval.touched;
                            }
                        });
                    });
                }
                $.each(state.points, function(idx, val) {
                    plot.points[idx].touched = val.touched
                })
                plot.selectedPoints = [];
                $.each(state.selectedPoints, function(k, v) {
                    plot.selectedPoints.push(
                        {
                            x: v.x,
                            y: v.y,
                            clickable: v.clickable != undefined ? v.clickable : true
                        }
                    )
                });
                if(state.isVisible === false) {
                    presenter.hide();
                } else {
                    presenter.show();
                }
                if(state.isAttempted === undefined) {
                    state.isAttempted = true;
                }
                presenter.setAttempted(state.isAttempted);
            }

            plot.draw();
        }
    }

    presenter.isAllOK = function () {
        return presenter.getMaxScore() === presenter.getScore() &amp;&amp; presenter.getErrorCount() === 0;
    };

    presenter.stateChanged = function(data) {
        var receiver;
        if(data) {
            //chained events when data is array
            if(!(data instanceof Array)) {
                //become chain with single event
                data = [data];
            }

            //call chained events
            for(var t=0;t&lt;data.length;t++) {
                data[t].source = addonID;
                data[t].item = data[t].item.toString();
                if(data[t].item.substring(0,6) == 'point_') {
                    data[t].item = presenter.replaceDecimalSeparator(data[t].item);
                }
                data[t].value = data[t].value.toString();
                data[t].score = data[t].score === null ? null : data[t].score.toString();
                //broadcast events or send event to bus
                if(presenter.broadcast.length &gt; 0) {
                    $.each(presenter.broadcast, function(idx, broadcastTo) {
                        receiver = presenter.playerController.getModule(broadcastTo);
                        if(receiver) {
                            receiver.onEvent('ValueChanged', data[t]);
                        }
                    });
                } else {
                    presenter.eventBus.sendEvent('ValueChanged', data[t]);
                }
            }
        }

        if(presenter.broadcast.length === 0) {
            if(presenter.isActivity &amp;&amp; presenter.isAllOK()) {
                if(!presenter._allDoneState) {
                    presenter._allDoneState = true;
                    sendAllOKEvent();
                }
            } else {
                presenter._allDoneState = false;
            }
        }
    };

    function sendAllOKEvent () {
        var eventData = {
            'source': addonID,
            'item': 'all',
            'value': '',
            'score': ''
        };

        presenter.eventBus.sendEvent('ValueChanged', eventData);
    }

    presenter.isAttempted = function () {
        presenter.handleDisplayedAnswers();
        return !this.isActivity ? true : this.getPlot().isAttempted();
    };

    presenter.setAttempted = function (state) {
        this.getPlot().setAttempted(state);
    };

    presenter.getPlot = function() {
        return plot;
    };

    presenter.handleDisplayedAnswers = function () {
        presenter.isShowAnswersActive &amp;&amp; presenter.hideAnswers();
        presenter.isGradualShowAnswersActive &amp;&amp; presenter.gradualHideAnswers();
        presenter.isPtpShowAnswersActive &amp;&amp; presenter.ptpHideAnswers();
    };

    presenter.isAnswersDisplayed = function () {
        return presenter.isShowAnswersActive || presenter.isGradualShowAnswersActive;
    };

    presenter.getActivitiesCount = function () {
        if (!presenter.isActivity) {
            return 0;
        }
        const correctPointsNumber = plot.points.filter(val =&gt; val.correct).length;
        const correctExpressionsNumber = plot.expressions.filter(val =&gt; val.correctAnswer).length;
        return correctPointsNumber + correctExpressionsNumber;
    };

    return presenter;
}
</presenter></addon>