<script src="https://unpkg.com/konva@^3/konva.min.js"></script>
<div id="container"></div>
body {
  margin: 10;
  padding: 10;
  overflow: hidden;
  background-color: #ffffff;
}
#container {
  border: 1px solid silver;
}
/** Developing a graphing component based on Konva.
*/

// Set up a stage
let stage = new Konva.Stage({
  container: "container",
  width: window.innerWidth,
  height: 400,
  draggable: true
  }),
    
  // add a layer to draw on
  layer = new Konva.Layer();
   
stage.add(layer);
stage.draw();

// Instantiate a vwGraph object and pass it the initial options to draw the grid.
let d = new vwGraph({
  stage: stage,
  origin: {x: 50, y: 250},
  range: {posX: 500, posY: 1000, negX: -50, negY: -300},
  direction: 'y-up',
  xPoints: ['', 1975, 1980, 1985, 1990, 1995, 2000, 2005, 2010, 2015, 2020, 2025],
  title: {x: 50, y: 240, text: 'Avg Tasmanian Wombat IQ'}
  })

d.drawGrid(50);

d.plotData({
  tp: 'path', 
  clr: 'magenta', 
  pts: [
    {x: 50, y: 10},
    {x: 100, y: 30},
    {x: 150, y: 100},
    {x: 200, y: 180},
    {x: 250, y: 160},
    {x: 300, y: 140},
    {x: 350, y: 90},
    {x: 400, y: 40},
    {x: 450, y: 20},
    ]
  })

function vwGraph(opts){
  
  let origin = opts.origin,
      range = opts.range, stage = opts.stage,
      direction = opts.direction === 'y-up' ? -1 : 1,
      xPoints = opts.xPoints,
      title = opts.title,
      gridLayer = null;

  // calculate the width and height of the range
  range.width = range.posX + Math.abs(range.negX);
  range.height = range.posY + Math.abs(range.negY);
  
  // Get a reliable top Y position regardless of direction 
  range.minY = direction === 1 ? range.negY : range.posY;

  
  // turn a logical x into a physical x
  function getX(x){
    return x + origin.x;
  }
  
  // turn a logical x into a physical x
  function getY(y){
    return origin.y + (direction * y);
  }

  // turn a logical x into a physical x but without the offset !
  function getRelativeY(y){
    return (direction * y);
  }  
  
  // draw the grid
  this.drawGrid = function drawGrid(step){
    gridLayer = new Konva.Layer({listening: false}),
        ln = new Konva.Line({x: 0, y: 0, points: [], stroke: 'black', strokeWidth: 0.5, listening: false, perfectDrawEnabled: false, transformsEnabled: 'position', shadowEnabled: false, shadowForStrokeEnabled: false }),
        txt = new Konva.Text({x:0, y: 0, width: 40, height: 10, fontSize: 10, fontFamily: 'Calibri', align: 'left', fill: 'black', listening: false, perfectDrawEnabled: false, transformsEnabled: 'position', shadowEnabled: false, shadowForStrokeEnabled: false}),
        ln1 = null, 
        t1 = null;

    // output the x-axis
    ln1 = ln.clone({x: getX(range.negX), y: getY(0), points: [0, 0, range.width, 0], strokeWidth: 1.5});
    ln1.cache();
    gridLayer.add(ln1)
    
    // output the Y axis
    ln1 = ln.clone({x: getX(0), y: getY(range.minY), points: [0, 0, 0, range.height], strokeWidth: 1.5});
    ln1.cache();
    gridLayer.add(ln1)

    // X axis lines & text
    let ptCnt = 0;
    for (let i = 0; i <= range.width; i = i + step ){
      ln1 = ln.clone({ x:  getX(range.negX) + i, y: getY(range.minY), points: [0, 0, 0, range.height]});
      t1 = txt.clone({x:  getX(range.negX) + i - 20, y: getY(0) + 5, text:  range.negX + i, align: 'center'});
      t1.text('xxx')
      if (xPoints && xPoints.length >= ptCnt){
        t1.text(xPoints[ptCnt])
      }
      ln1.cache();
      t1.cache();
      gridLayer.add(ln1, t1);      
      ptCnt = ptCnt + 1;
    }    
 
    // Y axis lines & text
    for (let i = 0; i <= range.height; i = i + step ){
      ln1 = ln.clone({x: getX(range.negX), y: getY(range.minY) + i, points: [0, 0, range.width, 0]});
      t1 = txt.clone({x: getX(10), y: getY(range.minY) + i - 5, text:   range.minY + (direction * i)});      
      ln1.cache();
      t1.cache();
      gridLayer.add(ln1, t1);      
    }    
   
    if (title){
      t1 =  txt.clone({x: getX(title.x), y: 0, text: title.text, fontSize: 32, width: 2000, height: 40});  
      t1.cache();
      gridLayer.add(t1);        
    }
    
    
    
    stage.add(gridLayer);
    gridLayer.batchDraw();
  }  

  this.plotData = function(opts){
    
    switch (opts.tp){
        
      case 'path' : // a Konva path.
        
        // A path has a set of data points. We process these to flip them if required, and make the path commands.
        let data = '', sep = '';
        for (let i = 0; i < opts.pts.length; i++){
          if (i === 0){ // first path command is M
            data = 'M' + opts.pts[i].x + ',' + getRelativeY(opts.pts[i].y)
          }
          else { // following path commands all L
          data = data + sep + 'L' + opts.pts[i].x + ',' + getRelativeY(opts.pts[i].y)
          }
          sep = ','
        }
        console.log(data)
        
        
        let p = new Konva.Path({
          x: getX(0),
          y: getY(0),
          stroke: opts.clr,
          strokeWidth: 2,
          data: data
          
        })
        gridLayer.add(p);
        gridLayer.batchDraw();
        
        break;
        
     // case '??' : // extend as required....
        
    }
  }
  return this;
}
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.