Brk
Brk

Reputation: 1297

How to zoom in/out from a complex triangle in canvas

Hey I have created some kind of very complex triangle which contains 7 segments and labels for guidance. I am trying to add zoom in/out capabilities to current shape. I have wrote the zoom in out methods using scale method, the problem is that when I zoom in or out of the triangle, every thing is falling apart and I can't zoom properly. My suggestion to this problem is: 1.Group up all the elements into one element, and then zoom in or out on the united element.can I do it on canvas? 2.Zoom into a specific middle absolute point, how can I do it? Any help will be blessed, thanks in advance :-).

The zoom in/out methods:

$('#zoomIn').click(function() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.scale(1/0.9, 1/0.9);
  drawDuvalShape();
});

$('#zoomOut').click(function() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.scale(0.90, 0.90);
  drawDuvalShape();
});

Zoom In: enter image description here

$(function(){
  
//offset : 150+ : x & 50+ : y.  
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");

// https://www.researchgate.net/publication/4345236_A_Software_Implementation_of_the_Duval_Triangle_Method

var v0={x:154,y:344};
var v1={x:302,y:69};
var v2={x:450,y:344};
var triangle=[v0,v1,v2];

// Define all your segments here
var segments=[
  { 
    points:[{x:154,y:344},{x:184,y:344},{x:236,y:246},{x:222,y:219}],
    fill:'rgb(172,236,222)',
    label:{text:'O',cx:200,cy:290,withLine:false,endX:null,endY:null}
  },
  { 
    points:[{x:236,y:246},{x:222,y:219},{x:280,y:111},{x:295,y:136}],
    fill:'deepskyblue',
    label:{text:'S',cx:258,cy:180,withLine:false,endX:null,endY:null}
  },
  { 
    points:[{x:280,y:111},{x:295,y:136},{x:317,y:96},{x:302,y:69}],
    fill:'rgb(172,236,222)',
    label:{text:'O',cx:300,cy:110,withLine:false,endX:305,endY:120}
  },
  { 
    points:[{x:184,y:344},{x:258,y:344},{x:310,y:248},{x:273,y:179},],
    fill:'navajoWhite',
    label:{text:'ND',cx:260,cy:280,withLine:false,endX:null,endY:null}
  },
  { 
    points:[{x:258,y:344},{x:310,y:248},{x:360,y:344}],
    fill:'#8CAAD2',
    label:{text:'T3',cx:310,cy:310,withLine:false,endX:null,endY:null}
  },
  { 
    points:[{x:360,y:344},{x:273,y:179},{x:300,y:130},{x:355,y:232},{x:353,y:236},{x:386,y:300}],
    fill:'peru',
    label:{text:'C',cx:320,cy:220,withLine:false,endX:null,endY:null}
  },
   { 
    points:[{x:360,y:344},{x:450,y:344},{x:353,y:167},{x:337,y:198},{x:355,y:232},{x:353,y:236},{x:386,y:300}],
    fill:'#8CAAD2',
    label:{text:'T3',cx:400,cy:310,withLine:false,endX:null,endY:null}
  },
   { 
    points:[{x:337,y:198},{x:353,y:167},{x:317,y:96},{x:300,y:128}],
    fill:'#918CD2',
    label:{text:'T2',cx:328,cy:150,withLine:false,endX:null,endY:null}
  },
  { 
    points:[{x:280,y:111},{x:283,y:113},{x:291,y:96},{x:288,y:96}],
    fill:'#d2b48c',
    label:{text:'PD',cx:250,cy:86,withLine:true,endX:295,endY:96}
  },
  { 
    points:[{x:291,y:96},{x:288,y:96},{x:299,y:75},{x:302,y:78}],
    fill:'#d2b48c',
    label:{text:'PD',cx:250,cy:86,withLine:true,endX:295,endY:96}
  }
  
];

  // label styles
  var labelfontsize=12;
  var labelfontface='verdana';
  var labelpadding=3;

  // pre-create a canvas-image of the arrowhead
  var arrowheadLength=10;
  var arrowheadWidth=8;
  var arrowhead=document.createElement('canvas');
  premakeArrowhead();

  var legendTexts=['PD   =  Partial Discharge',
                   'T2    =  Thermal fault 300 ℃ < T < 700 ℃',
                   'T3    =  Thermal fault  T > 700 ℃',
                    'O     =  Overheating < 250ºC',
                    'S     =   Stray Gassing of Oil',
                    'ND  =  Not Determined',
                    'C     =  Carbonization'];

drawDuvalShape();
  
function drawDuvalShape(){
// start drawing
/////////////////////

// draw colored segments inside triangle
for(var i=0;i<segments.length;i++){
  drawSegment(segments[i]);
}
// draw ticklines
ticklines(v0,v1,9,0,20);
ticklines(v1,v2,9,Math.PI*3/4,20);
ticklines(v2,v0,9,Math.PI*5/4,20);
// molecules
moleculeLabel(v0,v1,100,Math.PI,'% CH4');
moleculeLabel(v1,v2,100,0,'% C2H4');
moleculeLabel(v2,v0,75,Math.PI/2,'% C2H6');
// draw outer triangle
drawTriangle(triangle);
// draw legend
drawLegend(legendTexts,10,10,12.86);

  // end drawing
/////////////////////
}

function drawSegment(s){
  // draw and fill the segment path
  ctx.beginPath();
  ctx.moveTo(s.points[0].x,s.points[0].y);
  for(var i=1;i<s.points.length;i++){
    ctx.lineTo(s.points[i].x,s.points[i].y);
  }
  ctx.closePath();
  ctx.fillStyle=s.fill;
  ctx.fill();
  ctx.lineWidth=2;
  ctx.strokeStyle='black';
  ctx.stroke();
  // draw segment's box label
  if(s.label.withLine){
    lineBoxedLabel(s,labelfontsize,labelfontface,labelpadding);
  }else{
    boxedLabel(s,labelfontsize,labelfontface,labelpadding);
  }
}


function moleculeLabel(start,end,offsetLength,angle,text){
  ctx.textAlign='center';
  ctx.textBaseline='middle'
  ctx.font='14px verdana';
  var dx=end.x-start.x;
  var dy=end.y-start.y;
  var x0=parseInt(start.x+dx*0.50);
  var y0=parseInt(start.y+dy*0.50);
  var x1=parseInt(x0+offsetLength*Math.cos(angle));
  var y1=parseInt(y0+offsetLength*Math.sin(angle));
  ctx.fillStyle='black';
  ctx.fillText(text,x1,y1);
  // arrow
  var x0=parseInt(start.x+dx*0.35);
  var y0=parseInt(start.y+dy*0.35);
  var x1=parseInt(x0+50*Math.cos(angle));
  var y1=parseInt(y0+50*Math.sin(angle));
  var x2=parseInt(start.x+dx*0.65);
  var y2=parseInt(start.y+dy*0.65);
  var x3=parseInt(x2+50*Math.cos(angle));
  var y3=parseInt(y2+50*Math.sin(angle));
  ctx.beginPath();
  ctx.moveTo(x1,y1);
  ctx.lineTo(x3,y3);
  ctx.strokeStyle='black';
  ctx.lineWidth=1;
  ctx.stroke();
  var angle=Math.atan2(dy,dx);
  ctx.translate(x3,y3);
  ctx.rotate(angle);
  ctx.drawImage(arrowhead,-arrowheadLength,-arrowheadWidth/2);
  ctx.setTransform(1,0,0,1,0,0);
}


function boxedLabel(s,fontsize,fontface,padding){
  var centerX=s.label.cx;
  var centerY=s.label.cy;
  var text=s.label.text;
  ctx.textAlign='center';
  ctx.textBaseline='middle'
  ctx.font=fontsize+'px '+fontface
  var textwidth=ctx.measureText(text).width;
  var textheight=fontsize*1.286;
  var leftX=centerX-textwidth/2-padding;
  var topY=centerY-textheight/2-padding;
  ctx.fillStyle='white';
  ctx.fillRect(leftX,topY,textwidth+padding*2,textheight+padding*2);
  ctx.lineWidth=1;
  ctx.strokeRect(leftX,topY,textwidth+padding*2,textheight+padding*2);
  ctx.fillStyle='black';
  ctx.fillText(text,centerX,centerY);
}


function lineBoxedLabel(s,fontsize,fontface,padding){
  var centerX=s.label.cx;
  var centerY=s.label.cy;
  var text=s.label.text;
  var lineToX=s.label.endX;
  var lineToY=s.label.endY;
  ctx.textAlign='center';
  ctx.textBaseline='middle'
  ctx.font=fontsize+'px '+fontface
  var textwidth=ctx.measureText(text).width;
  var textheight=fontsize*1.286;
  var leftX=centerX-textwidth/2-padding;
  var topY=centerY-textheight/2-padding;
  // the line
  ctx.beginPath();
  ctx.moveTo(leftX,topY+textheight/2);
  ctx.lineTo(lineToX,topY+textheight/2);
  ctx.strokeStyle='black';
  ctx.lineWidth=1;
  ctx.stroke();
  // the boxed text
  ctx.fillStyle='white';
  ctx.fillRect(leftX,topY,textwidth+padding*2,textheight+padding*2);
  ctx.strokeRect(leftX,topY,textwidth+padding*2,textheight+padding*2);
  ctx.fillStyle='black';
  ctx.fillText(text,centerX,centerY);
}


function ticklines(start,end,count,angle,length){
  var dx=end.x-start.x;
  var dy=end.y-start.y;
  ctx.lineWidth=1;
  for(var i=1;i<count;i++){
    var x0=parseInt(start.x+dx*i/count);
    var y0=parseInt(start.y+dy*i/count);
    var x1=parseInt(x0+length*Math.cos(angle));
    var y1=parseInt(y0+length*Math.sin(angle));
    ctx.beginPath();
    ctx.moveTo(x0,y0);
    ctx.lineTo(x1,y1);
    ctx.stroke();
    if(i==2 || i==4 || i==6 || i==8){
      var labelOffset=length*3/4;
      var x1=parseInt(x0-labelOffset*Math.cos(angle));
      var y1=parseInt(y0-labelOffset*Math.sin(angle));
      ctx.fillStyle='black';
      ctx.fillText(parseInt(i*10),x1,y1);
    }
  }
}


function premakeArrowhead(){
  var actx=arrowhead.getContext('2d');
  arrowhead.width=arrowheadLength;
  arrowhead.height=arrowheadWidth;
  actx.beginPath();
  actx.moveTo(0,0);
  actx.lineTo(arrowheadLength,arrowheadWidth/2);
  actx.lineTo(0,arrowheadWidth);
  actx.closePath();
  actx.fillStyle='black';
  actx.fill();
}


function drawTriangle(t){
  ctx.beginPath();
  ctx.moveTo(t[0].x,t[0].y);
  ctx.lineTo(t[1].x,t[1].y);
  ctx.lineTo(t[2].x,t[2].y);
  ctx.closePath();
  ctx.strokeStyle='black';
  ctx.lineWidth=2;
  ctx.stroke();
}


function drawLegend(texts,x,y,lineheight){
  ctx.textAlign='left';
  ctx.textBaseline='top';
  ctx.fillStyle='black';
  ctx.font='12px arial';
  for(var i=0;i<texts.length;i++){
    ctx.fillText(texts[i],x,y+i*lineheight);
  }
}
  $('#zoomIn').click(function() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.scale(1/0.9, 1/0.9);
  drawDuvalShape();
});

$('#zoomOut').click(function() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.scale(0.90, 0.90);
  drawDuvalShape();
});
})
body{ background-color: ivory; padding:10px; }
#canvas{border:1px solid red; margin:0 auto; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="canvas" width=650 height=500></canvas>
 <button id="zoomIn">+</button>
 <button id="zoomOut">-</button>

Upvotes: 1

Views: 92

Answers (2)

user1693593
user1693593

Reputation:

The render code is resetting transforms during drawing:

ctx.setTransform(1,0,0,1,0,0);

This means your transforms with zoom will be overridden during the drawing process.

Locate this code segment at the end of the moleculeLabel() method:

  ...
  var angle=Math.atan2(dy,dx);
  ctx.translate(x3,y3);
  ctx.rotate(angle);
  ctx.drawImage(arrowhead,-arrowheadLength,-arrowheadWidth/2);
  ctx.setTransform(1,0,0,1,0,0);
}

and change to:

  var angle=Math.atan2(dy,dx);
  ctx.save();                       // save
  ctx.translate(x3,y3);
  ctx.rotate(angle);
  ctx.drawImage(arrowhead,-arrowheadLength,-arrowheadWidth/2);
  ctx.restore();                    // restore
}

this will preserve global transforms. There may be more instances and if so, just repeat.

About center of zoom (added back from my original answer here):

Scale will always happen at the origin point (where (0,0) is, initially in the upper left corner of context) so to be able to zoom at a specific point, all you need to do is to first translate to the desired zoom center, apply scale, translate back (in this case) and draw.

One way to simplify this is to define the triangles and shapes around a predefined center point (0,0) of the shape so all points in the shape is relative to this.

Upvotes: 1

B&#225;lint
B&#225;lint

Reputation: 4039

Use ctx.scale. ctx.scale Scales everything what ypu draw on the canvas. The only down-side, that it only scales from the top-left corner.

This can be solved though, by first trabslating the canvas, so the middle of your image goes up to the left corner, scale it, then transform back, and draw yor triangle.

Upvotes: 0

Related Questions