Pen Settings

HTML

CSS

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. You can use the CSS from another Pen by using it's URL and the proper URL extention.

+ add another resource

JavaScript

Babel includes JSX processing.

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

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

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.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <script src="https://cdnjs.cloudflare.com/ajax/libs/konva/2.5.1/konva.min.js"></script>
<p>Click to add a point, click to add another, etc.
</p>
<div id='container'></div>
  
              
            
!

CSS

              
                p
{
  padding: 4px;
}
#container {
  background-color: silver;
}
              
            
!

JS

              
                // Set up the canvas / stage
var s1 = new Konva.Stage({container: 'container', width: window.innerWidth, height: window.innerHeight});

// Add a layer for line
var lineLayer = new Konva.Layer();
var mapLayer = new Konva.Layer();
s1.add(mapLayer);
s1.add(lineLayer);

// Add a layer for drag points
var pointLayer = new Konva.Layer({draggable: false});
s1.add(pointLayer);

// get a map to use as the background
var imageObj = new Image();
imageObj.onload = function () {
  var mappic = new Konva.Image({
    x: 0,
    y: 0,
    image: imageObj,
    width: imageObj.width,
    height: imageObj.height
  });
  mapLayer.add(mappic);
  mapLayer.batchDraw();
}
imageObj.src = 'https://assets.codepen.io/255591/2021-01-29_09-56-01.png';

// Everything is ready so draw the canvas objects set up so far.
s1.draw()

// generic canvas end


// Class for the draggable point
// Params: route = the parent object, opts = position info, doPush = should we just make it or make it AND store it
var DragPoint = function(route, opts, doPush){
  var route = route;

  this.x = opts.x;
  this.y = opts.y;
  this.fixed = opts.fixed;
  this.id = randId();  // random id.

  if (doPush){  // in some cases we want to create the pt then insert it in the run of the array and not always at the end
    route.pts.push(this);  
  }

  // random id generator
  function randId() {
     return Math.random().toString(36).replace(/[^a-z]+/g, '').substr(2, 10);
  }

  // mark the pt as fixed - important state, shown by filled point
  this.makeFixed = function(){
    this.fixed = true;
    s1.find('#' + this.id)
        .fill(route.fillColor);      
  }
  
  this.draw = function(){
    // Add point & pt
    var circleId = this.id;
 
    var pt = new Konva.Circle({
      id: circleId,
      x: this.x, 
      y: this.y, 
      radius: route.pointRadius,
      opacity: route.pointOpacity,
      strokeWidth: 1, 
      stroke: route.strokeColor,
      fill: 'transparent',
      draggable: 'true'    
    })
    pt.on('dragstart', function(){
        route.drawState = 'dragging';
    })
    pt.on('dragmove', function(){
      var pos = this.getPosition();
      route.updatePt(this.id(), pos)
      route.calc(this.id());
      route.draw();
    })
    pt.on('dragend', function(){

      route.drawState = 'drawing';
      var pos = this.getPosition();

      route.updatePt(this.getId(), pos);

      route.splitPts(this.getId());
      
      route.draw();
    })

    if (this.fixed){
      this.makeFixed();
    }
    
    
    route.ptLayer.add(pt);
    route.draw();

  }  
  
}

var Route = function() {

    this.lineLayer = null;
    this.ptLayer = null;
    this.drawState = '';

    this.fillColor = 'Gold';
    this.strokeColor = 'Gold';
    this.pointOpacity = 0.5;
    this.pointRadius = 10;
    this.color = 'LimeGreen';
    this.width = 5;
  
    this.pts = []; // array of dragging points.

    this.startPt = null;
    this.endPt = null;

    // Add a point to the route.
    this.addPt = function(pos, isFixed){ 
      
      if (this.drawState === 'dragging'){  // do not add a new point because we were just dragging another
        return null;
      }
      
      this.startPt = this.startPt || pos;
      this.endPt = pos;

      // create this new pt
      var pt = new DragPoint(this, {x: this.endPt.x, y: this.endPt.y, fixed: isFixed}, true, "A");
      pt.draw();
      pt.makeFixed(); // always fixed for manual points
      
      // if first point ignore the splitter process
      if (this.pts.length > 0){
        this.splitPts(pt.id, true);
      }    

      this.startPt = this.endPt; // remember the last point

      this.calc(); // calculate the line points from the array
      this.draw();  // draw the line 
    }

  // Position the points.  
  this.calc = function (draggingId){
    draggingId = (typeof draggingId === 'undefined' ? '---' : draggingId); // when dragging an unfilled point we have to override its automatic positioning.

    for (var i = 1; i < this.pts.length - 1; i = i + 1){

      var d2 = this.pts[i];
      if (!d2.fixed && d2.id !== draggingId){      // points that have been split are fixed, points that have not been split are repositioned mid way along their line segment.

        var d1 = this.pts[i - 1];
        var d3 = this.pts[i + 1];
        var pos = this.getHalfwayPt(d1, d3);
        
        d2.x = pos.x;
        d2.y = pos.y;
      }
      s1.find('#' + d2.id).position({x: d2.x, y: d2.y}); // tell the shape where to go
    }
  }

  // draw the line
  this.draw = function (){  

    if (this.drawingLine){
      this.drawingLine.remove();
    }
    this.drawingLine = this.newLine(); // initial line point
    
    for (var i = 0; i < this.pts.length; i = i + 1){
      this.drawingLine.points(this.drawingLine.points().concat([this.pts[i].x, this.pts[i].y]))
    }
    
    this.ptLayer.draw();
    this.lineLayer.draw();
  }

  // When dragging we need to update the position of the point
  this.updatePt = function(id, pos){

      for (var i = 0; i < this.pts.length; i = i + 1){
        if (this.pts[i].id === id){

          this.pts[i].x = pos.x;
          this.pts[i].y = pos.y;

          break;
        }    
      }
  }

  // Function to add and return a line object. We will extend this line to give the appearance of drawing.
  this.newLine = function(){
    var line = new Konva.Line({
        stroke: this.color,
        strokeWidth: this.width,
        lineCap: 'round',
        lineJoin: 'round',
        tension : .1
      });

    this.lineLayer.add(line)
    return line;
  }  


  // make pts either side of the split
  this.splitPts = function(id, force){
    var idx = -1;
    
    // find the pt in the array
    for (var i = 0; i < this.pts.length; i = i + 1){
      if (this.pts[i].id === id){
        idx = i;

        if (this.pts[i].fixed && !force){
          return null; // we only split once.
        }

        //break;
      }   
    }

    // If idx is -1 we did not find the pt id !
    if ( idx === -1){
      return null
    }
    else if (idx === 0  ) { 
      return null
    }
    else { // pt not = 0 or max 

      // We are now going to insert a new pt either side of the one we just dragged
      var d1 = this.pts[idx - 1]; // previous pt to the dragged pt
      var d2 = this.pts[idx    ]; // the pt pt
      var d3 = this.pts[idx + 1]; // the next pt after the dragged pt

      d2.makeFixed()// flag this pt as no longer splittable

      // get point midway from prev pt and dragged pt    
      var pos = this.getHalfwayPt(d1, d2);
      var pt = new DragPoint(this, {x: pos.x, y: pos.y, foxed: false}, false, "C");
      pt.draw();
      this.pts.splice(idx, 0, pt);

      if (d3){
        // get point midway from dragged pt to next     
        pos = this.getHalfwayPt(d2, d3);
        var pt = new DragPoint(this, {x: pos.x, y: pos.y, foxed: false}, false, "D");
        pt.draw();
        this.pts.splice(idx + 2, 0, pt); // note idx + 2 !

      }

    }  

  }  
  
  // convert last point array entry to handy x,y object.
  this.getPoint = function(pts){
    return {x: pts[pts.length - 2], y: pts[pts.length - 1]};
  }
  
  this.getHalfwayPt = function(d1, d2){
    var pos = {
          x: d1.x + (d2.x - d1.x)/2, 
          y: d1.y + (d2.y - d1.y)/2
      }
    return pos;
  }

  
  
}

var route = new Route();
route.lineLayer = lineLayer;
route.ptLayer = pointLayer;

route.fillColor = 'AliceBlue'; 
route.strokeColor = 'Red'; 
route.pointOpacity = 0.25;
route.pointRadius = 7;
route.color = '#2982E8'


// Listen for mouse up on the stage to know when to draw points
s1.on('mouseup touchend', function () {

  route.addPt(s1.getPointerPosition(), true);

  
  
});

















              
            
!
999px

Console