| 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) | |
| ); | |
| } | |
| } |