<!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 );
// todo 计算出 ( x_from,y_from ) 到点 ( x_to,y_to ) 之间的距离
distance = Math.sqrt( ( x_from - x_to ) * ( x_from - x_to ) + ( y_from - y_to ) * ( y_from - y_to ) );
distance = Math.floor( distance );
console.log( "distance = " + distance );
// }
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 )求最短路径
于 2019-09-19 20:46:19 首次发布