docs: function drawToContext(){
        startTime = +new Date;
        var nodes = r.getCachedNodes(); var edges = r.getCachedEdges();
        var coreStyle = cy.style()._private.coreStyle;
        
        // if( !forcedContext ){
        //   r.matchCanvasSize(data.container);
        // }
  
        var zoom = cy.zoom();
        var effectiveZoom = forcedZoom !== undefined ? forcedZoom : zoom;
        var pan = cy.pan();
        var effectivePan = {
          x: pan.x,
          y: pan.y
        };
  
        if( forcedPan ){
          effectivePan = forcedPan;
        }
  
        // apply pixel ratio
  
        effectiveZoom *= pixelRatio;
        effectivePan.x *= pixelRatio;
        effectivePan.y *= pixelRatio;
        
        var eles = {
          drag: {
            nodes: [],
            edges: []
          },
          nondrag: {
            nodes: [],
            edges: []
          }
        };
  
        function setContextTransform(context, clear){
          context.setTransform(1, 0, 0, 1, 0, 0);
          !forcedContext && (clear === undefined || clear) && context.clearRect(0, 0, r.canvasWidth, r.canvasHeight);
          
          if( !drawAllLayers ){
            context.translate(effectivePan.x, effectivePan.y);
            context.scale(effectiveZoom, effectiveZoom);
          }
          if( forcedPan ){
            context.translate(forcedPan.x, forcedPan.y);
          } 
          if( forcedZoom ){
            context.scale(forcedZoom, forcedZoom);
          }
        }
  
        var textureDraw = r.textureOnViewport && !forcedContext && (r.pinching || r.hoverData.dragging || r.swipePanning || r.data.wheelZooming);
        var scale;
  
        if( textureDraw ){
  
          var bb;
  
          if( !r.textureCache ){
            r.textureCache = {};
  
            bb = r.textureCache.bb = cy.boundingBox();
  
            var canvas = r.textureCache.texture = r.data.bufferCanvases[0];
  
            var cxt = canvas.getContext('2d');
  
            cxt.setTransform(1, 0, 0, 1, 0, 0);
            cxt.clearRect(0, 0, r.canvasWidth, r.canvasHeight);
            
            r.redraw({
              forcedContext: cxt,
              drawOnlyNodeLayer: true
            });
  
            var vp = r.textureCache.viewport = {
              zoom: cy.zoom(),
              pan: cy.pan(),
              width: r.canvasWidth,
              height: r.canvasHeight
            };
  
            vp.mpan = {
              x: (0 - vp.pan.x)/vp.zoom,
              y: (0 - vp.pan.y)/vp.zoom
            };
          }
  
          needDraw[CanvasRenderer.DRAG] = false;
          needDraw[CanvasRenderer.NODE] = false;
  
          var context = data.canvases[CanvasRenderer.NODE].getContext('2d');
  
          var texture = r.textureCache.texture;
          var z = cy.zoom();
          var p = cy.pan();
          var buffCanvas = r.textureCache.buffCanvas;
          var buffCxt = r.textureCache.buffCxt;
          var texture = r.textureCache.texture;
          var vp = r.textureCache.viewport;
          bb = r.textureCache.bb;
  
          context.setTransform(1, 0, 0, 1, 0, 0);
          context.clearRect(0, 0, vp.width, vp.height);
  
          var outsideBgColor = coreStyle['outside-texture-bg-color'].value;
          var outsideBgOpacity = coreStyle['outside-texture-bg-opacity'].value;
          r.fillStyle( context, outsideBgColor[0], outsideBgColor[1], outsideBgColor[2], outsideBgOpacity );
          context.fillRect( 0, 0, vp.width, vp.height );
  
          var pan = cy.pan();
          var zoom = cy.zoom();
          
          setContextTransform( context, false );
  
          context.clearRect( vp.mpan.x, vp.mpan.y, vp.width/vp.zoom/pixelRatio, vp.height/vp.zoom/pixelRatio );
          context.drawImage( texture, vp.mpan.x, vp.mpan.y, vp.width/vp.zoom/pixelRatio, vp.height/vp.zoom/pixelRatio );
  
        } else if( !forcedContext ){ // clear the cache since we don't need it
          r.textureCache = null;
        }
  
        var vpManip = (r.pinching || r.hoverData.dragging || r.swipePanning || r.data.wheelZooming || r.hoverData.draggingEles);
        var hideEdges = r.hideEdgesOnViewport && vpManip;
        var hideLabels = r.hideLabelsOnViewport && vpManip;
  
        if (needDraw[CanvasRenderer.DRAG] || needDraw[CanvasRenderer.NODE] || drawAllLayers || drawOnlyNodeLayer) {
          //NB : VERY EXPENSIVE
          //console.time('edgectlpts'); for( var looper = 0; looper <= looperMax; looper++ ){
  
          if( hideEdges ){ 
          } else {
            r.findEdgeControlPoints(edges);
          }
  
          //} console.timeEnd('edgectlpts')
  
          // console.time('sort'); for( var looper = 0; looper <= looperMax; looper++ ){
          var zEles = r.getCachedZSortedEles();
          // } console.timeEnd('sort')
  
          for (var i = 0; i < zEles.length; i++) {
            var ele = zEles[i];
            var list;
  
            if ( ele._private.rscratch.inDragLayer ) {
              list = eles.drag;
            } else {
              list = eles.nondrag;
            }
  
            list[ ele._private.group ].push( ele );
          }
  
          // console.time('updatecompounds'); for( var looper = 0; looper <= looperMax; looper++ ){
          // no need to update graph if there is no compound node
          // if ( cy.hasCompoundNodes() )
          // {
          //   r.updateAllCompounds(elements);
          // }
          // } console.timeEnd('updatecompounds')
        }
        
        
        function drawElements( list, context ){
          var edges = list.edges;
          var nodes = list.nodes;
  
          for (var i = 0; i < edges.length && !hideEdges; i++) {
            ele = edges[i];
            
            r.drawEdge(context, ele);
          }
  
          for (var i = 0; i < edges.length && !hideEdges && !hideLabels; i++) {
            ele = edges[i];
            
            r.drawEdgeText(context, ele);
          }
  
          for (var i = 0; i < edges.length && !hideEdges; i++) {
            ele = edges[i];
            
            r.drawEdge(context, ele, true);
          }
  
          for( var i = 0; i < nodes.length; i++ ){
            var ele = nodes[i];
  
            r.drawNode(context, ele);
            
            if( !hideLabels ){
              r.drawNodeText(context, ele);
            }
            
            r.drawNode(context, ele, true);
          }
  
        }
  
  
        // console.time('drawing'); for( var looper = 0; looper <= looperMax; looper++ ){
        if (needDraw[CanvasRenderer.NODE] || drawAllLayers || drawOnlyNodeLayer) {
          // console.log('redrawing node layer');
          
          var context = forcedContext || data.canvases[CanvasRenderer.NODE].getContext('2d');
  
          setContextTransform( context );
          drawElements(eles.nondrag, context);
          
          if( !drawAllLayers ){
            needDraw[CanvasRenderer.NODE] = false; 
          }
        }
        
        if ( !drawOnlyNodeLayer && (needDraw[CanvasRenderer.DRAG] || drawAllLayers) ) {
          
          var context = forcedContext || data.canvases[CanvasRenderer.DRAG].getContext('2d');
          
          setContextTransform( context );
          drawElements(eles.drag, context);
          
          if( !drawAllLayers ){
            needDraw[CanvasRenderer.DRAG] = false;
          }
        }
        
        if ( !drawOnlyNodeLayer && (needDraw[CanvasRenderer.SELECT_BOX] && !drawAllLayers) ) {
          // console.log('redrawing selection box');
          
          var context = forcedContext || data.canvases[CanvasRenderer.SELECT_BOX].getContext('2d');
          
          setContextTransform( context );
  
          if (data.select[4] == 1) {
            var zoom = data.cy.zoom();
            var borderWidth = coreStyle['selection-box-border-width'].value / zoom;
            
            context.lineWidth = borderWidth;
            context.fillStyle = "rgba(" 
              + coreStyle['selection-box-color'].value[0] + ","
              + coreStyle['selection-box-color'].value[1] + ","
              + coreStyle['selection-box-color'].value[2] + ","
              + coreStyle['selection-box-opacity'].value + ")";
            
            context.fillRect(
              data.select[0],
              data.select[1],
              data.select[2] - data.select[0],
              data.select[3] - data.select[1]);
            
            if (borderWidth > 0) {
              context.strokeStyle = "rgba(" 
                + coreStyle['selection-box-border-color'].value[0] + ","
                + coreStyle['selection-box-border-color'].value[1] + ","
                + coreStyle['selection-box-border-color'].value[2] + ","
                + coreStyle['selection-box-opacity'].value + ")";
              
              context.strokeRect(
                data.select[0],
                data.select[1],
                data.select[2] - data.select[0],
                data.select[3] - data.select[1]);
            }
          }
  
          if( data.bgActivePosistion ){
            var zoom = data.cy.zoom();
            var pos = data.bgActivePosistion;
  
            context.fillStyle = "rgba(" 
              + coreStyle['active-bg-color'].value[0] + ","
              + coreStyle['active-bg-color'].value[1] + ","
              + coreStyle['active-bg-color'].value[2] + ","
              + coreStyle['active-bg-opacity'].value + ")";
  
            context.beginPath();
            context.arc(pos.x, pos.y, coreStyle['active-bg-size'].pxValue / zoom, 0, 2 * Math.PI); 
            context.fill();
          }
          
          if( !drawAllLayers ){
            needDraw[CanvasRenderer.SELECT_BOX] = false; 
          }
        }
  
        // } console.timeEnd('drawing')
  
        var endTime = +new Date;
  
        if( r.averageRedrawTime === undefined ){
          r.averageRedrawTime = endTime - startTime;
        }
  
        if( r.redrawCount === undefined ){
          r.redrawCount = 0;
        }
  
        r.redrawCount++;
  
        if( r.redrawTotalTime === undefined ){
          r.redrawTotalTime = 0;
        }
  
        r.redrawTotalTime += endTime - startTime;
  
        // use a weighted average with a bias from the previous average so we don't spike so easily
        r.averageRedrawTime = r.averageRedrawTime/2 + (endTime - startTime)/2;
        //console.log('actual: %i, average: %i', endTime - startTime, this.averageRedrawTime);
  
        r.currentlyDrawing = false;
      }