123

Pen Settings

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

You're using npm packages, so we've auto-selected Babel for you here, which we require to process imports and make it all work. If you need to use a different JavaScript preprocessor, remove the packages in the npm tab.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Use npm Packages

We can make npm packages available for you to use in your JavaScript. We use webpack to prepare them and make them available to import. We'll also process your JavaScript with Babel.

⚠️ This feature can only be used by logged in users.

Code Indentation

     

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

HTML Settings

Here you can Sed posuere consectetur est at lobortis. Donec ullamcorper nulla non metus auctor fringilla. Maecenas sed diam eget risus varius blandit sit amet non magna. Donec id elit non mi porta gravida at eget metus. Praesent commodo cursus magna, vel scelerisque nisl consectetur et.

            
              <div id="canvasContainer"></div>
            
          
!
            
              html, body{
	margin : 0px;
	width : 100%;
	height : 100%;
	overflow: hidden;
}

#canvasContainer{
  margin : 0px;
  width : 100%;
  height : 100%;
}
            
          
!
            
              /*
 *
 *Currently not working on Mac/FireFox and possibly other platforms 
 *Sorry about this :( Will see if I can fix.
 *
 * @author Sakri Rosenstrom
 * A visualization explaining a Marching Squares algorithm as it executes
 * sample implementation here : https://codepen.io/sakri/full/vIKJp
 * http://www.sakri.net
 * https://twitter.com/sakri
 * http://www.devstate.net
 * Sources for this can be found at:
 * https://github.com/sakri/sakriNetCommonJS
 */


(function (window){

    var Sakri = window.Sakri || {};
    window.Sakri = window.Sakri || Sakri;

    Sakri.Geom = {};

    //==================================================
    //=====================::POINT::====================
    //==================================================

    Sakri.Geom.Point = function (x,y){
        this.x = isNaN(x) ? 0 : x;
        this.y = isNaN(y) ? 0 : y;
    };

    Sakri.Geom.Point.prototype.clone = function(){
        return new Sakri.Geom.Point(this.x,this.y);
    };

    Sakri.Geom.Point.prototype.update = function(x, y){
        this.x = isNaN(x) ? this.x : x;
        this.y = isNaN(y) ? this.y : y;
    };

    Sakri.Geom.Point.prototype.add = function(x, y){
        this.x += isNaN(x) ? 0 : x;
        this.y += isNaN(y) ? 0 : y;
    };

    Sakri.Geom.Point.prototype.equals = function(point){
        return this.x==point.x && this.y==point.y;
    };

    Sakri.Geom.Point.prototype.toString = function(){
        return "{x:"+this.x+" , y:"+this.y+"}";
    };



    //==================================================
    //===================::RECTANGLE::==================
    //==================================================

    Sakri.Geom.Rectangle = function (x, y, width, height){
        this.update(x, y, width, height);
    };

    Sakri.Geom.Rectangle.prototype.update = function(x, y, width, height){
        this.x = isNaN(x) ? 0 : x;
        this.y = isNaN(y) ? 0 : y;
        this.width = isNaN(width) ? 0 : width;
        this.height = isNaN(height) ? 0 : height;
    };

    Sakri.Geom.Rectangle.prototype.getRight = function(){
        return this.x + this.width;
    };

    Sakri.Geom.Rectangle.prototype.getBottom = function(){
        return this.y + this.height;
    };

    Sakri.Geom.Rectangle.prototype.getCenter = function(){
        return new Sakri.Geom.Point(this.getCenterX(), this.getCenterY());
    };

    Sakri.Geom.Rectangle.prototype.getCenterX = function(){
        return this.x + this.width/2;
    };

    Sakri.Geom.Rectangle.prototype.getCenterY=function(){
        return this.y + this.height/2;
    };


	Sakri.Geom.Rectangle.prototype.floor = function(){
		this.x = Math.floor(this.x);
		this.y = Math.floor(this.y);
		this.width = Math.floor(this.width);
		this.height = Math.floor(this.height);
	};
  
    Sakri.Geom.Rectangle.prototype.clone = function(){
        return new Sakri.Geom.Rectangle(this.x, this.y, this.width, this.height);
    };

    Sakri.Geom.Rectangle.prototype.toString = function(){
        return "Rectangle{x:"+this.x+" , y:"+this.y+" , width:"+this.width+" , height:"+this.height+"}";
    };



}(window));

/**
 * Created by sakri on 27-1-14.
 */
(function (window){

    var Sakri = window.Sakri || {};
    window.Sakri = window.Sakri || Sakri;

    Sakri.CanvasTextUtil = {};

    //=========================================================================================
    //==============::CANVAS TEXT PROPERTIES::====================================
    //========================================================

    Sakri.CanvasTextProperties = function(fontWeight, fontStyle, fontSize, fontFace){
        this.setFontWeight(fontWeight);
        this.setFontStyle(fontStyle);
        this.setFontSize(fontSize);
        this.fontFace = fontFace ? fontFace : "sans-serif";
    };

    Sakri.CanvasTextProperties.NORMAL = "normal";
    Sakri.CanvasTextProperties.BOLD = "bold";
    Sakri.CanvasTextProperties.BOLDER = "bolder";
    Sakri.CanvasTextProperties.LIGHTER = "lighter";

    Sakri.CanvasTextProperties.ITALIC = "italic";
    Sakri.CanvasTextProperties.OBLIQUE = "oblique";


    Sakri.CanvasTextProperties.prototype.setFontWeight = function(fontWeight){
        switch (fontWeight){
            case Sakri.CanvasTextProperties.NORMAL:
            case Sakri.CanvasTextProperties.BOLD:
            case Sakri.CanvasTextProperties.BOLDER:
            case Sakri.CanvasTextProperties.LIGHTER:
                this.fontWeight = fontWeight;
                break;
            default:
                this.fontWeight = Sakri.CanvasTextProperties.NORMAL;
        }
    };

    Sakri.CanvasTextProperties.prototype.setFontStyle = function(fontStyle){
        switch (fontStyle){
            case Sakri.CanvasTextProperties.NORMAL:
            case Sakri.CanvasTextProperties.ITALIC:
            case Sakri.CanvasTextProperties.OBLIQUE:
                this.fontStyle = fontStyle;
                break;
            default:
                this.fontStyle = Sakri.CanvasTextProperties.NORMAL;
        }
    };

    Sakri.CanvasTextProperties.prototype.setFontSize = function(fontSize){
        if(fontSize && fontSize.indexOf && fontSize.indexOf("px")>-1){
            var size = fontSize.split("px")[0];
            fontProperites.fontSize = isNaN(size) ? 24 : size;//24 is just an arbitrary number
            return;
        }
        this.fontSize = isNaN(fontSize) ? 24 : fontSize;//24 is just an arbitrary number
    };

    Sakri.CanvasTextProperties.prototype.getFontString = function(){
        return this.fontWeight + " " + this.fontStyle + " " + this.fontSize + "px " + this.fontFace;
    };

}(window));


/**
 * Created by sakri on 27-1-14.
 */
(function (window){

    var Sakri = window.Sakri || {};
    window.Sakri = window.Sakri || Sakri;

    Sakri.BitmapUtil = {};

    //TODO : rename "canvas" to "source", if it's an img, create a canvas and draw the img into it
    Sakri.BitmapUtil.getFirstNonTransparentPixelTopDown = function(canvas){
        var context = canvas.getContext("2d");
        var y, i, rowData;
        for(y=0; y<canvas.height; y++){
            rowData = context.getImageData(0, y, canvas.width, 1).data;
            for(i=0; i<rowData.length; i+=4){
                if(rowData[i+0] + rowData[i+1] + rowData[i+2] + rowData[i+3] > 0){
                    return new Sakri.Geom.Point(i/4, y);
                }
            }
        }
        return null;
    };


}(window));

/**
 * Created by @sakri on 28-1-14.
 *
 * Somewhat Naive implementation in that there are cases where the edge detection gets stuck in an eternal loop.
 * This is currently "handled" by a MAX_POINTS variable.
 * This implementation is "good enough" for most use cases though.
 *
 */
(function (window){

    var Sakri = window.Sakri || {};
    window.Sakri = window.Sakri || Sakri;

    Sakri.MarchingSquares = {};

    //Update this when working with large shapes (large bitmaps)
    //the "edge detection loop" stops at this figure. This is in place in the event that an infinite loop somehow appears (Should never happen).
    Sakri.MarchingSquares.MAX_POINTS = 10000;

    //This is a lookup table of all possible 4 pixel grids, used to decide "scanning positions" during the edge detection process
    //Zeros represent transparent pixels, Ones represent a non transparent pixel
    Sakri.MarchingSquares.possibleGrids = {
        "0011":new Sakri.Geom.Point(1,0),
        "1011":new Sakri.Geom.Point(1,0),
        "0001":new Sakri.Geom.Point(1,0),
        "1001":new Sakri.Geom.Point(1,0),

        "0100":new Sakri.Geom.Point(0,-1),
        "0101":new Sakri.Geom.Point(0,-1),
        "0111":new Sakri.Geom.Point(0,-1),
        "0110":new Sakri.Geom.Point(0,-1),

        "1100":new Sakri.Geom.Point(-1,0),
        "1000":new Sakri.Geom.Point(-1,0),
        "1101":new Sakri.Geom.Point(-1,0),

        "1110":new Sakri.Geom.Point(0,1),
        "1010":new Sakri.Geom.Point(0,1),
        "0010":new Sakri.Geom.Point(0,1)

    };


    /**
     * Apparently there were (are?) cases where duplicate points are registered along horizontal or vertical sets
     * of adjacent points.  I haven't been able to reproduce this, but have left this option in place for now.
     */
    Sakri.MarchingSquares.getUniquePoints = function(points){
        console.log("MarchingSquares.getUniquePoints() points.length : ",points.length);
        var unique = {};
        var uniquePoints = [];
        var pointString, p, i;
        for(i=0; i < points.length; i++){
            p = points[i];
            pointString = p.x+":"+p.y;
            if(unique[pointString] == null){
                unique[pointString] = true;
                uniquePoints.push(p);
            }
        }
        console.log("MarchingSquares.getUniquePoints() uniquePoints.length : ",uniquePoints.length);
        return uniquePoints;
    };

    //source can be a Canvas or an img element. See comment above getUniquePoints concerning the checkUnique flag
    Sakri.MarchingSquares.getBlobOutlinePoints = function(source, checkUnique){
        //Create a copy with a one pixel blank "border" in case source image/canvas has pixels which touch the border
        //The edge scan operates with an offset of -1,-1 meaning the returned points are accurate
        var canvas = document.createElement("canvas");
        canvas.width = source.width + 2;
        canvas.height = source.height + 2;
        var context = canvas.getContext("2d");
        context.drawImage(source,1,1);

        if(checkUnique){
            return Sakri.MarchingSquares.getUniquePoints(Sakri.MarchingSquares.scanOutlinePoints(canvas));
        }else{
            return Sakri.MarchingSquares.scanOutlinePoints(canvas);
        }
     };

    //this should be private, should not be called directly
    Sakri.MarchingSquares.scanOutlinePoints = function(canvas){
        var points = [];
        points[0] = Sakri.BitmapUtil.getFirstNonTransparentPixelTopDown(canvas);
        if(points[0] == null){
            return points;
        }
        points[0].add(-1, -1);//in order for the lookup to work, we move the position up and back one
        var context = canvas.getContext("2d");
        var currentPosition = points[0];
        var gridString = Sakri.MarchingSquares.getGridStringFromPoint(context, currentPosition);
        var next, i;
        for(i=1; i<Sakri.MarchingSquares.MAX_POINTS; i++){
            next = Sakri.MarchingSquares.getNextEdgePoint(currentPosition, gridString);
            if(next.equals(points[0])){
                break;
            }
            points[i] = next;
            currentPosition = next;
            gridString = Sakri.MarchingSquares.getGridStringFromPoint(context, currentPosition);
        }
        //Failsafe when the marching squares get stuck in an eternal loop. See note at the top.
        if(i >= Sakri.MarchingSquares.MAX_POINTS){
            console.log("MarchingSquares.scanOutlinePoints Sakri.MarchingSquares.MAX_POINTS reached");
            return [];
        }
        return points;
    };

    Sakri.MarchingSquares.getGridStringFromPoint = function(context, point){
        var gridString = "";
        var data = context.getImageData(point.x, point.y, 2, 2).data;
        for(i=0; i<16; i+=4){
            gridString += (data[i+0] + data[i+1] + data[i+2] + data[i+3] > 0 ? "1" : "0");
        }
        return gridString;
    };

    Sakri.MarchingSquares.getNextEdgePoint = function(point, gridString){
        var offsetPoint = Sakri.MarchingSquares.possibleGrids[gridString];
        if(point==null){
            throw new Error("MarchingSquares Error : gridString:"+gridString+" , not found in possibleGrids");
        }
        return new Sakri.Geom.Point(point.x + offsetPoint.x, point.y + offsetPoint.y);
    };

}(window));



    //========================
    //general properties for demo set up
    //========================
    var canvas;
    var context;
    var canvasContainer;
    var htmlBounds;
    var bounds;
    var minimumStageWidth = 250;
    var minimumStageHeight = 250;
    var resizeTimeoutId = -1;

    function init(){
        canvas = document.createElement('canvas');
        canvas.style.position = "absolute";
        context = canvas.getContext("2d");
        canvasContainer = document.getElementById("canvasContainer");
        canvasContainer.appendChild(canvas);
        window.onresize = resizeHandler;
        commitResize();
    }

    function getWidth( element ){return Math.max(element.scrollWidth,element.offsetWidth,element.clientWidth );}
    function getHeight( element ){return Math.max(element.scrollHeight,element.offsetHeight,element.clientHeight );}

    //avoid running resize scripts repeatedly if a browser window is being resized by dragging
    function resizeHandler(){
        context.clearRect(0,0,bounds.width, bounds.height);
        clearTimeoutsAndIntervals();
        clearTimeout (resizeTimeoutId);
        resizeTimeoutId = setTimeout(function(){commitResize();}, 300 );
    }

    function commitResize(){
        htmlBounds = new Sakri.Geom.Rectangle(0,0, getWidth(this.canvasContainer) , getHeight(canvasContainer));
        if(htmlBounds.width>=800){
            canvas.width = 800;
            canvas.style.left = htmlBounds.getCenterX() - 400+"px";
        }else{
            canvas.width = htmlBounds.width;
            canvas.style.left ="0px";
        }
        if(htmlBounds.height>600){
           canvas.height = 600;
           canvas.style.top = htmlBounds.getCenterY() - 300+"px";
        }else{
            canvas.height = htmlBounds.height;
            canvas.style.top ="0px";
        }
        bounds = new Sakri.Geom.Rectangle(0,0, canvas.width, canvas.height);
        context.clearRect(0,0,bounds.width, bounds.height);

        if(bounds.width>bounds.height){
            //landscape layout
            marchGridsBounds.x = bounds.getCenterX();
            charactersBounds.x = marchGridsBounds.x - charactersBounds.width;
            charactersBounds.y = marchGridsBounds.y = bounds.getCenterY() - charactersBounds.height/2;
        }else{
            //portrait layout
            charactersBounds.x = marchGridsBounds.x = bounds.getCenterX() - charactersBounds.width/2;
            marchGridsBounds.y = bounds.getCenterY()
            charactersBounds.y = marchGridsBounds.y - charactersBounds.height/2
        }

        if(bounds.width<minimumStageWidth || bounds.height<minimumStageHeight){
            stageTooSmallHandler();
            return;
        }

        startDemo();
    }

    function stageTooSmallHandler(){
        var warning = "Sorry, bigger screen required :(";
        var props = new Sakri.CanvasTextProperties(null,null,24);
        context.font = props.getFontString();
        context.fillStyle = "#000000";
        context.fillText(warning, bounds.getCenterX() - context.measureText(warning).width/2, bounds.getCenterY()-12);
    }



    //========================
    //Demo specific properties
    //========================
    var marchingSquaresTimeOutId = -1;
    var fontProperties = new Sakri.CanvasTextProperties(Sakri.CanvasTextProperties.BOLD, null, 40);
    var fillStyle = "#0b0b2e";
    var characters = "ABCDEFGHIJKLMNOPQRSTUVXYZ1234567890";
    var currentCharacterIndex = 0;
    var currentCharacterOutline;
    var charactersBounds = new Sakri.Geom.Rectangle(0,0,250,250);
    var marchGridsBounds = new Sakri.Geom.Rectangle(0,0,250,250);
    var characterCanvas;
    var characterContext;
    var characterScaleFactor;
    var marchingPixelContext;
    var marchingPixelCanvas;
    var currentScanPosition;
    var optionGridRect = new Sakri.Geom.Rectangle();
    var optionRect = new Sakri.Geom.Rectangle();
    var bigBounds = new Sakri.Geom.Rectangle();
    var outline;

    function clearTimeoutsAndIntervals(){
        clearTimeout(marchingSquaresTimeOutId);
    }

    function startDemo(){
        context.clearRect(0, 0, canvas.width, canvas.height);
        characterCanvas = document.createElement('canvas');
        marchingPixelCanvas = document.createElement("canvas");
        demoNextCharacter();
    }


    function demoNextCharacter(){
        context.clearRect(0,0,canvas.width, canvas.height);
        characterContext = characterCanvas.getContext("2d");

        characterContext.clearRect(0,0,characterCanvas.width, characterCanvas.height);
        characterContext.font = fontProperties.getFontString();
        characterContext.textBaseline = "top";
        characterCanvas.width = characterContext.measureText(characters[currentCharacterIndex]).width;
        characterCanvas.height = fontProperties.fontSize;

        characterContext = characterCanvas.getContext("2d");
        characterContext.fillStyle = "#AAAAAA";
        characterContext.font = fontProperties.getFontString();
        characterContext.textBaseline = "top";
        characterContext.fillText(characters[currentCharacterIndex],0,0);
        renderCharacter();
        renderAllOptions();
        currentCharacterIndex++;
        currentCharacterIndex %= characters.length;

        marchingPixelCanvas.width = characterCanvas.width + 2;
        marchingPixelCanvas.height = characterCanvas.height + 2;
        marchingPixelContext = marchingPixelCanvas.getContext("2d");
        marchingPixelContext.drawImage(characterCanvas,1,1);

        outline = [];
        outline[0] = Sakri.BitmapUtil.getFirstNonTransparentPixelTopDown(marchingPixelCanvas);
        outline[0].add(-1, -1);//in order for the lookup to work, we move the position up and back one
        currentScanPosition = outline[0].clone();

        displayCurrentScanPosition();
    }

    function displayCurrentScanPosition(){
        renderCharacter();

        context.fillStyle = "#FF0000";
        var position;
        for(var i=0;i<outline.length;i++){
            position = outline[i];
            context.fillRect(charactersBounds.x+position.x, charactersBounds.y+position.y, 1, 1);
            context.fillRect(bigBounds.x+position.x*characterScaleFactor, bigBounds.y+position.y*characterScaleFactor, characterScaleFactor, characterScaleFactor );
        }

        //render scan grid outline
        context.lineWidth = 1;
        context.strokeStyle = "#0000FF";
        context.strokeRect(bigBounds.x+(currentScanPosition.x-1)*characterScaleFactor, bigBounds.y+(currentScanPosition.y-1)*characterScaleFactor, characterScaleFactor*2, characterScaleFactor*2);


        marchingSquaresTimeOutId = setTimeout(scanNextOutlinePoint, 500);
        renderAllOptions();
    }

    function scanNextOutlinePoint(){
        //console.log("scanNextOutlinePoint()");
        var gridString = Sakri.MarchingSquares.getGridStringFromPoint(marchingPixelContext, currentScanPosition);
        var next = Sakri.MarchingSquares.getNextEdgePoint(currentScanPosition, gridString);
        if(next.equals(outline[0])){
            demoNextCharacter();
            return;
        }

        //render next
        renderAllOptions(gridString);

        currentScanPosition = next;
        outline.push(next);
        marchingSquaresTimeOutId = setTimeout(displayCurrentScanPosition, 500);
    }

    function renderCharacter(){
        context.clearRect(charactersBounds.x, charactersBounds.y, charactersBounds.width, charactersBounds.height);
        var width = characterContext.measureText(characters[currentCharacterIndex]).width;
        var height = fontProperties.fontSize;
        characterScaleFactor = Math.floor(Math.min(charactersBounds.width / width, charactersBounds.height / height));
        context.fillStyle = "#AAAAAA";
        var data = characterContext.getImageData(0,0, width, height).data;

        var i, x, y;
        bigBounds.update(charactersBounds.x + width , charactersBounds.y, width*characterScaleFactor, height*characterScaleFactor);
        //render character
        for(i=0; i<data.length; i+=4){
            if(data[i]+data[i+1]+data[i+2]+data[i+3] > 0){
                x = (i/4)%width;
                y = Math.floor((i/4)/width);
                context.fillRect(charactersBounds.x + x, charactersBounds.y + y, 1, 1 );
                context.fillRect(bigBounds.x+x*characterScaleFactor, bigBounds.y+y*characterScaleFactor, characterScaleFactor, characterScaleFactor );
            }
        }
        //render graph lines
        context.fillStyle = "#FFFFFF";

        for(i = characterScaleFactor; i < bigBounds.height; i+=characterScaleFactor){
            context.fillRect(bigBounds.x, bigBounds.y + i, bigBounds.width, 1);
        }
        for(i = characterScaleFactor; i < bigBounds.width; i+=characterScaleFactor){
            context.fillRect(bigBounds.x + i , bigBounds.y, 1, bigBounds.height);
        }
    }

    function renderAllOptions(currentOptionString){
        context.clearRect(optionGridRect.x, optionGridRect.y, optionGridRect.width, optionGridRect.height);
        var margin = 4;
        var cols = 4;
        var optionSize = marchGridsBounds.width/cols;
        optionRect = new Sakri.Geom.Rectangle(0,0,optionSize-margin, optionSize-margin);
        optionGridRect.width = optionSize/2.5;
        optionGridRect.height = optionSize/2.5;
        var options = [];
        var i, option;
        for(option in Sakri.MarchingSquares.possibleGrids){
            options.push({string:option, point:Sakri.MarchingSquares.possibleGrids[option]});
        }
        for(var i=0; i<options.length; i++){
            optionRect.x = marchGridsBounds.x + (i%cols) * optionSize;
            optionRect.y = marchGridsBounds.y + Math.floor(i/cols) * optionSize;
            optionRect.floor();
            context.strokeStyle = currentOptionString==options[i].string ? "#FF0000" : "#AAAAAA";
            context.strokeRect(optionRect.x, optionRect.y, optionRect.width, optionRect.height);
            renderOption(options[i], optionRect, currentOptionString==options[i].string);
        }

    }



    function renderOption(option, rect, highLight){
        context.strokeStyle = highLight ? "#FF0000" : "#666666";
        context.fillStyle = highLight ? "#FF0000" : "#333333";
        optionGridRect.x =  rect.getCenterX() - optionGridRect.width;
        optionGridRect.y = rect.getCenterY() - optionGridRect.height/2;
        context.strokeRect(optionGridRect.x, optionGridRect.y, optionGridRect.width, optionGridRect.height);
        //too lazy to write a loop for this, copy paste saves the day ;)
        if(option.string.charAt(0)=="1"){
            context.fillRect(optionGridRect.x, optionGridRect.y, optionGridRect.width/2, optionGridRect.height/2 );
        }
        if(option.string.charAt(1)=="1"){
            context.fillRect(optionGridRect.x+optionGridRect.width/2, optionGridRect.y, optionGridRect.width/2, optionGridRect.height/2 );
        }
        if(option.string.charAt(2)=="1"){
            context.fillRect(optionGridRect.x, optionGridRect.y+optionGridRect.height/2, optionGridRect.width/2, optionGridRect.height/2 );
        }
        if(option.string.charAt(3)=="1"){
            context.fillRect(optionGridRect.x+optionGridRect.width/2, optionGridRect.y+optionGridRect.height/2, optionGridRect.width/2, optionGridRect.height/2 );
        }
        optionGridRect.x = rect.getCenterX();
        context.save();
        context.lineWidth = 3;
        context.translate(optionGridRect.getCenterX()+optionGridRect.width/10, optionGridRect.getCenterY());
        switch(option.point.x+""+option.point.y){
            case "01":
                context.rotate(Math.PI/2);
                break;
            case "-10":
                context.rotate(Math.PI);
                break;
            case "0-1":
                context.rotate(Math.PI+Math.PI/2);
                break;
        }
        renderArrow(optionGridRect.width/2, optionGridRect.height/2);
        context.restore();
    }

    function renderArrow(width,height){
        //line
        context.beginPath();
        context.moveTo(width/10, 0);
        context.lineTo(width-width/10, 0);
        context.stroke();
        context.closePath();

        //arrowhead
        context.beginPath();
        context.moveTo(width/2, -height/2+height/10);
        context.lineTo(width-width/10, 0);
        context.lineTo(width/2, height/2-height/10);
        context.stroke();
        context.closePath();
    }


    var readyStateCheckInterval = setInterval( function() {
        if (document.readyState === "complete") {
            clearInterval(readyStateCheckInterval);
            init();
        }
    }, 10);


            
          
!
999px
🕑 One or more of the npm packages you are using needs to be built. You're the first person to ever need it! We're building it right now and your preview will start updating again when it's ready.

Console