| class Visualizer{ |
| static drawNetwork(ctx,network){ |
| const margin=50; |
| const left=margin; |
| const top=margin; |
| const width=ctx.canvas.width-margin*2; |
| const height=ctx.canvas.height-margin*2; |
|
|
| const levelHeight=height/network.levels.length; |
|
|
| for(let i=network.levels.length-1;i>=0;i--){ |
| const levelTop=top+ |
| lerp( |
| height-levelHeight, |
| 0, |
| network.levels.length==1 |
| ?0.5 |
| :i/(network.levels.length-1) |
| ); |
|
|
| Visualizer.drawLevel(ctx,network.levels[i], |
| left,levelTop, |
| width,levelHeight, |
| i==network.levels.length-1 |
| ?['Up','Left','Right','Down'] |
| :[] |
| ); |
| } |
| } |
|
|
| static drawLevel(ctx,level,left,top,width,height,outputLabels){ |
| const right=left+width; |
| const bottom=top+height; |
|
|
| const {inputs,outputs,weights,biases}=level; |
| const nodeRadius=14; |
|
|
| for(let i=0;i<inputs.length;i++){ |
| for(let j=0;j<outputs.length;j++){ |
| ctx.beginPath(); |
| ctx.moveTo( |
| Visualizer.#getNodeX(inputs,i,left,right), |
| bottom |
| ); |
| ctx.lineTo( |
| Visualizer.#getNodeX(outputs,j,left,right), |
| top |
| ); |
| ctx.lineWidth=2; |
| ctx.strokeStyle=getRGBA(weights[i][j]); |
| ctx.stroke(); |
| } |
| } |
| for(let i=0;i<inputs.length;i++){ |
| const x=Visualizer.#getNodeX(inputs,i,left,right); |
| ctx.beginPath(); |
| ctx.arc(x,bottom,nodeRadius,0,Math.PI*2); |
| ctx.fillStyle="black"; |
| ctx.fill(); |
| ctx.beginPath(); |
| ctx.arc(x,bottom,nodeRadius*0.6,0,Math.PI*2); |
| ctx.fillStyle=getRGBA(inputs[i]); |
| ctx.fill(); |
| } |
| for(let i=0;i<outputs.length;i++){ |
| const x=Visualizer.#getNodeX(outputs,i,left,right); |
| ctx.beginPath(); |
| ctx.arc(x,top,nodeRadius,0,Math.PI*2); |
| ctx.fillStyle="black"; |
| ctx.fill(); |
| ctx.beginPath(); |
| ctx.arc(x,top,nodeRadius*0.6,0,Math.PI*2); |
| ctx.fillStyle=getRGBA(outputs[i]); |
| ctx.fill(); |
| ctx.beginPath(); |
| ctx.lineWidth=2; |
| ctx.arc(x,top,nodeRadius*0.8,0,Math.PI*2); |
| ctx.strokeStyle=getRGBA(biases[i]); |
| ctx.stroke(); |
| if(outputLabels[i]){ |
| ctx.beginPath(); |
| ctx.textAlign="center"; |
| ctx.textBaseline="middle"; |
| ctx.fillStyle="black"; |
| ctx.strokeStyle="white"; |
| ctx.font=(nodeRadius*1.5)+"px Arial"; |
| ctx.fillText(outputLabels[i],x, |
| top+nodeRadius*0.1); |
| ctx.lineWidth=0.5; |
| ctx.strokeText(outputLabels[i],x, |
| top+nodeRadius*0.1); |
| } |
| } |
| } |
|
|
| static #getNodeX(nodes,index,left,right){ |
| return lerp( |
| left, |
| right, |
| nodes.length==1 |
| ?0.5 |
| :index/(nodes.length-1) |
| ); |
| } |
| } |