<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style type="text/css">
.active{
background-color: green;
color: black;
}
.no_active{
background-color: orange;
color: black;
cursor: pointer;
}
</style>
</head>
<body id="body">
<button id="btn_step1" disabled class="active" title="畫節點模式:點擊紅框內任意位置就會在該位置生成一個節點!">畫圓</button>
<button id="btn_step2" class="no_active" title="畫路徑模式: 前後點擊任意兩個節點,則會在這兩個節點間生成一個連線,併產生一個隨機的路徑距離!">畫線</button>
<button id="btn_step3" class="no_active" title="計算最短路徑模式: 前後點擊任意兩個節點,則會計算最短路徑並用紅線標出最短路徑!">求最短距離</button>
<br/>
<!-- <canvas id="myCanvas" width="200" height="100" style="border:1px solid red;"></canvas> -->
</body>
<script type="text/javascript">
// 初始化一些常量
var canvas_width = 500; // 畫布寬度
var canvas_height = 300; //畫布高度
var circleR=20;// 圓的半徑
// 記錄一些創建的對象信息
var circleCenterCoordinateArr = []; //記錄創建的圓的圓心座標的數組
var firstClickCircleIndex = -1;
var secondClickCircleIndex = -1;
var infinite = 999999;
var defaultColor = "black";
var step1 =true; // 點擊生成節點
var step2=false; //點擊生成節點連線
var step3=false; //點擊計算兩點之間最短路徑
var graph = [];// 圖結構的存儲,是個二位數組,graph[ i ][ j ]=10 表示 第i個節點和第j個節點的距離是10,如果這兩個節點沒有連線,則值爲正無窮
// 初始化 canvas 畫布元素
var dom_canvas = document.createElement( "canvas" );
dom_canvas.setAttribute( "id","myCanvas" );
dom_canvas.setAttribute( "width",canvas_width );
dom_canvas.setAttribute( "height",canvas_height );
dom_canvas.setAttribute( "style","border:1px solid red;" );
document.getElementById( "body" ).appendChild( dom_canvas );
// 初始化 畫筆對象 ( 得到畫布的畫筆對象 )
var pen = dom_canvas.getContext( "2d" );
// 圓的角標
var circleIndex = 0;
// 爲 畫布 添加點擊事件,點擊一下就畫一個圓,圓心就是點擊的位置
dom_canvas.onclick = function( event ){
if( step1 ){
handleDrawCircleEvent( event );
}else if( step2 ){
handleDrawLineEvent( event );
}else if( step3 ){
handleComputeShortestPathEvent( event );
}
}
function handleDrawCircleEvent( event ){
var x = event.clientX - dom_canvas.getBoundingClientRect().left;
var y = event.clientY - dom_canvas.getBoundingClientRect().top;
// console.log( "( " + x + "," + y + " )" );
// 畫一個圓形邊框
drawCircleWithText( x,y,circleR,"V" + circleIndex,20,defaultColor );
// 將圓心座標存儲起來
circleCenterCoordinateArr[ circleIndex ] = [ x,y ];
// 將該圓的編號存儲至圖結構中
/*
| | V0| V1| V2| V3| V4|
| VO| 0 | ∞ | ∞ | ∞ | ∞ |
| V1| ∞ | 0 | ∞ | ∞ | ∞ |
| V2| ∞ | ∞ | 0 | ∞ | ∞ |
| V3| ∞ | ∞ | ∞ | 0 | ∞ |
| V4| ∞ | ∞ | ∞ | ∞ | 0 |
*/
graph[ circleIndex ]=[];
for( var i=0;i<=circleIndex;i++ ){
if( i == circleIndex ){
graph[ circleIndex ][ i ] = 0;
}else{
graph[ circleIndex ][ i ] = infinite;
graph[ i ][ circleIndex ] = infinite;
}
}
circleIndex++;
printGraph();
}
function printGraph(){
var titleRow = "| |";
for( var i=0;i<graph.length;i++ ){
titleRow+=( " V" + i + "|" );
}
console.log( titleRow );
for( var i=0;i<graph.length;i++ ){
var row = "| V" + i + "|";
for( var j=0;j<graph[ i ].length;j++ ){
if( graph[ i ][ j ] == infinite ){
row +=( " ∞ |" );
}else{
row +=( " " + graph[ i ][ j ] + " |" );
}
}
console.log( row );
}
console.log( "" );
}
function handleDrawLineEvent( event ){
// 判斷當前點擊的點在哪一個 圓的內部
var x = event.clientX - dom_canvas.getBoundingClientRect().left;
var y = event.clientY - dom_canvas.getBoundingClientRect().top;
var clickedCircleIndex = -1;
for( var i=0;i<circleCenterCoordinateArr.length;i++ ){
var x_circle = circleCenterCoordinateArr[ i ][ 0 ];
var y_circle = circleCenterCoordinateArr[ i ][ 1 ];
if( checkInCircle( x,y,x_circle,y_circle,circleR ) ){
clickedCircleIndex = i;
break;
}
}
if( clickedCircleIndex == -1 && firstClickCircleIndex == -1 ){
alert( "請選擇連線開始節點!" );
return;
}
if( clickedCircleIndex == -1 && secondClickCircleIndex == -1 ){
alert( "請選擇連線結束節點!" );
return;
}
if( firstClickCircleIndex == -1 ){
firstClickCircleIndex = clickedCircleIndex;
console.log("點擊的開始節點是 V" + firstClickCircleIndex );
return;
}
if( secondClickCircleIndex == -1 ){
secondClickCircleIndex = clickedCircleIndex;
console.log( "點擊的結束節點是 V" + secondClickCircleIndex );
console.log( "" );
// 畫2個圓之間的連線
var randomDistance = Math.ceil( Math.random() * 10 );
drawConnectionTwoCircle( firstClickCircleIndex,secondClickCircleIndex,defaultColor,randomDistance );
// 更新圖結構
graph[ firstClickCircleIndex ][ secondClickCircleIndex ] = randomDistance;
graph[ secondClickCircleIndex ][ firstClickCircleIndex ] = randomDistance;
printGraph();
firstClickCircleIndex = -1;
secondClickCircleIndex = -1;
return;
}
}
// 畫2個圓之間的連線
function drawConnectionTwoCircle( circleIndex1,circleIndex2,color,distance ){
// circleIndex1 和 circleIndex2 都有了,開始連線
var x1 = circleCenterCoordinateArr[ circleIndex1 ][ 0 ];
var y1 = circleCenterCoordinateArr[ circleIndex1 ][ 1 ];
var x2 = circleCenterCoordinateArr[ circleIndex2 ][ 0 ];
var y2 = circleCenterCoordinateArr[ circleIndex2 ][ 1 ];
var l=Math.sqrt( ( x1 - x2 ) * ( x1 - x2 ) + ( y1 - y2 ) * ( y1 - y2 ) );
var x_from = ( x2 - x1 ) * circleR / l + x1;
var y_from = ( y2 - y1 ) * circleR / l + y1;
var x_to = x2 - ( x2 - x1 ) * circleR / l;
var y_to = y2 - ( y2 - y1 ) * circleR / l;
drawLine( x_from,y_from,x_to,y_to,color );
// 爲此連線生成一個隨機的距離,並寫到 此連線中點的位置
var x_middle = ( x1 + x2 )/2;
var y_middle = ( y1 + y2 )/2;
if( !distance ){
distance = Math.ceil( Math.random() * 10 );
}
if( color ){
pen.fillStyle = color;
}else{
pen.fillStyle = defaultColor;
}
pen.fillText( distance,x_middle,y_middle );
}
// 畫一個圓形邊框,並在圓心寫文字
function drawCircleWithText( x,y,r,text,fontSize,color ){
if( color ){
pen.strokeStyle = color;
pen.fillStyle = color;
}else{
pen.strokeStyle = defaultColor;
pen.fillStyle = defaultColor;
}
pen.beginPath();
pen.arc(x,y,r,0,2*Math.PI);
pen.stroke();
pen.closePath();
pen.font = fontSize + "px Microsoft JhengHei";
pen.fillText( text,x - fontSize/2,y + fontSize/2 );
}
// 畫一根線
function drawLine( x1,y1,x2,y2,color ){
if( color ){
pen.strokeStyle = color;
}else{
pen.strokeStyle = defaultColor;
}
pen.beginPath();
pen.moveTo( x1,y1 );
pen.lineTo( x2,y2 );
pen.stroke();
pen.closePath();
}
document.getElementById( "btn_step1" ).onclick=function(){
step1=true;
step2=false;
step3=false;
document.getElementById( "btn_step1" ).setAttribute( "disabled","disabled" );
document.getElementById( "btn_step1" ).setAttribute( "class","active" )
document.getElementById( "btn_step2" ).removeAttribute( "disabled" );
document.getElementById( "btn_step2" ).setAttribute( "class","no_active" );
document.getElementById( "btn_step3" ).removeAttribute( "disabled" );
document.getElementById( "btn_step3" ).setAttribute( "class","no_active" );
}
document.getElementById( "btn_step2" ).onclick=function(){
step2=true;
step1=false;
step3=false;
document.getElementById( "btn_step2" ).setAttribute( "disabled","disabled" );
document.getElementById( "btn_step2" ).setAttribute( "class","active" )
document.getElementById( "btn_step1" ).removeAttribute( "disabled" );
document.getElementById( "btn_step1" ).setAttribute( "class","no_active" );
document.getElementById( "btn_step3" ).removeAttribute( "disabled" );
document.getElementById( "btn_step3" ).setAttribute( "class","no_active" );
}
document.getElementById( "btn_step3" ).onclick=function(){
step3=true;
step1=false;
step2=false;
document.getElementById( "btn_step3" ).setAttribute( "disabled","disabled" );
document.getElementById( "btn_step3" ).setAttribute( "class","active" )
document.getElementById( "btn_step1" ).removeAttribute( "disabled" );
document.getElementById( "btn_step1" ).setAttribute( "class","no_active" );
document.getElementById( "btn_step2" ).removeAttribute( "disabled" );
document.getElementById( "btn_step2" ).setAttribute( "class","no_active" );
}
// 檢測 點 x,y 是否在 圓內部( 圓心座標爲x_circle,y_circle,圓半徑爲 r )
function checkInCircle( x,y,x_circle,y_circle,r ){
//求點到圓心的距離,用到了勾股定理
var dis = Math.sqrt( ( x - x_circle ) * ( x - x_circle ) + ( y - y_circle ) * ( y - y_circle ) );//Math.sqrt()求平方跟
if(dis <= r){
return true;
}
return false;
}
var startCircleIndex = -1;
var endCircleIndex = -1;
function handleComputeShortestPathEvent( event ){
// 判斷當前點擊的點在哪一個 圓的內部
var x = event.clientX - dom_canvas.getBoundingClientRect().left;
var y = event.clientY - dom_canvas.getBoundingClientRect().top;
var clickedCircleIndex = -1;
for( var i=0;i<circleCenterCoordinateArr.length;i++ ){
var x_circle = circleCenterCoordinateArr[ i ][ 0 ];
var y_circle = circleCenterCoordinateArr[ i ][ 1 ];
if( checkInCircle( x,y,x_circle,y_circle,circleR ) ){
clickedCircleIndex = i;
break;
}
}
if( clickedCircleIndex == -1 && startCircleIndex == -1 ){
alert( "請選擇出發節點!" );
return;
}
if( clickedCircleIndex == -1 && endCircleIndex == -1 ){
alert( "請選擇到達節點!" );
return;
}
if( startCircleIndex == -1 ){
startCircleIndex = clickedCircleIndex;
console.log("選擇的出發節點是 V" + startCircleIndex );
return;
}
if( endCircleIndex == -1 ){
endCircleIndex = clickedCircleIndex;
console.log( "選擇的到達節點是 V" + endCircleIndex );
console.log( "" );
// startCircleIndex 和 endCircleIndex 都有了,開始計算最短路徑
/*
| | V0| V1| V2| V3| V4| V5|
| V0| 0 | 8 | 6 | ∞ | ∞ | 10 |
| V1| 8 | 0 | ∞ | 1 | ∞ | 3 |
| V2| 6 | ∞ | 0 | ∞ | 3 | 5 |
| V3| ∞ | 1 | ∞ | 0 | 1 | 10 |
| V4| ∞ | ∞ | 3 | 1 | 0 | 2 |
| V5| 10 | 3 | 5 | 10 | 2 | 0 |
*/
//todo 最難的部分來了
// 解析 graph 二維數組
// 例如: 計算 v0到 v4的最短路徑
// 分別 計算 v0 到 v1 v2 v3 v4 v5的最短路徑
var distanceObjArr= [];
var uncheckedMinIndex=-1;
var minDistance=infinite;
for( var i = 0; i < graph.length; i++ ){
if( i == startCircleIndex ){
continue;
}
var currDistance= graph[ startCircleIndex ][ i ];
if( currDistance < minDistance ){
minDistance = currDistance;
uncheckedMinIndex = i;
}
distanceObjArr[ i ] = {
"checked":false,
"path": startCircleIndex + "," + i + ",",
"distance":currDistance
}
}
printDistanceObjArr( distanceObjArr );
while( true ){
// if( distanceObjArr[ endCircleIndex ].checked ){
if( uncheckedMinIndex== -1 ){
break;
}
// V0 到 uncheckedMinIndex 節點的最短距離是 distanceObjArr[ uncheckedMinIndex ].distance
// 所以嘗試計算 V0 途徑 uncheckedMinIndex 節點會不會使得到達其他節點的距離縮短
minDistance = infinite;
var nextUncheckedMinIndex = -1;
for( var i = 0; i < distanceObjArr.length; i++ ){
if( i == startCircleIndex ){
continue;
}
// 檢查過就不處理了,比如當前 uncheckedMinIndex 節點是未檢查狀態,則此 for循環結束後則是 已檢查狀態,循環時就不會處理它了
if( distanceObjArr[ i ].checked ){
continue;
}
if( i == uncheckedMinIndex ){
continue;
}
var tryMinDistance = distanceObjArr[ uncheckedMinIndex ].distance + graph[ uncheckedMinIndex ][ i ];
if( tryMinDistance < distanceObjArr[ i ].distance ){
distanceObjArr[ i ].distance = tryMinDistance;
distanceObjArr[ i ].path = distanceObjArr[ uncheckedMinIndex ].path + i + ",";
}
if( distanceObjArr[ i ].distance < minDistance ){
minDistance=distanceObjArr[ i ].distance;
nextUncheckedMinIndex = i;
}
}
distanceObjArr[ uncheckedMinIndex ].checked=true;
uncheckedMinIndex=nextUncheckedMinIndex;
printDistanceObjArr( distanceObjArr );
}
// V4 到 v6 的最短距離是11,路線是0,4,5,6,
console.log( "V" + startCircleIndex + " 到 v" + endCircleIndex + " 的最短距離是"
+ distanceObjArr[ endCircleIndex ].distance
+ ",路線是"
+ distanceObjArr[ endCircleIndex ].path );
// 最短路徑標註紅色
var trailIndexArr = distanceObjArr[ endCircleIndex ].path.split( "," );
var prevTrailIndex = -1;
var currTrailIndex = -1;
for( var i=0;i<trailIndexArr.length;i++ ){
var trailIndex = trailIndexArr[ i ];
if( !trailIndex || trailIndex.length == 0 ){
continue;
}
trailIndex = parseInt( trailIndex );
if( prevTrailIndex == -1 ){
prevTrailIndex = trailIndex;
continue;
}
currTrailIndex = trailIndex;
// 畫連線
drawConnectionTwoCircle( prevTrailIndex,currTrailIndex,"red",graph[ prevTrailIndex ][ currTrailIndex ] );
prevTrailIndex = currTrailIndex;
currTrailIndex = -1;
}
}
startCircleIndex = -1;
endCircleIndex = -1;
}
function printDistanceObjArr( distanceObjArr ){
for( var i = 0;i<distanceObjArr.length;i++ ){
if( distanceObjArr[ i ] ){
console.log( "checked:" + distanceObjArr[ i ].checked + ", path:" + distanceObjArr[ i ].path + ", distance:" + distanceObjArr[ i ].distance );
}
}
console.log( "" );
}
</script>
</html>
html5 canvas 模擬 迪傑斯特拉算法( Dijkstra )求最短路徑
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.