UNPKG

veheeavis

Version:

Vehicle Electrical/Electronic Architecture Visualizer

2 lines (1 loc) 11.9 kB
!function(t){"function"==typeof define&&define.amd?define(t):t()}((function(){"use strict";var t,e;t="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:void 0,e=function(){class Ploteeavis{constructor(t){this.container=document.querySelector(t.container),this.data=t.data,this.scale=1,this.draggingModule=null,this.dragOffsetX=0,this.dragOffsetY=0,this.startPort=null,this.endPort=null,this.selectedModule=null,this.canvas=document.createElement("canvas"),this.container.appendChild(this.canvas),this.ctx=this.canvas.getContext("2d"),this.dpr=window.devicePixelRatio||1,this.canvas.style.width=this.container.clientWidth+"px",this.canvas.style.height=this.container.clientHeight+"px",this.canvas.width=this.container.clientWidth*this.dpr,this.canvas.height=this.container.clientHeight*this.dpr,this.ctx.scale(this.dpr,this.dpr),this.view={x:0,y:0,zoom:1},this.snapDistance=20,this.snapPort=null,this.contextMenu=this.createContextMenu(),this.container.appendChild(this.contextMenu),this.activeModule=null,this.validateData(this.data),this.addEventListeners(),this.render()}createContextMenu(){const t=document.createElement("div");t.style.cssText="\n position: absolute;\n background: white;\n border: 1px solid #ccc;\n padding: 5px 0;\n box-shadow: 2px 2px 5px rgba(0,0,0,0.2);\n display: none;\n z-index: 1000;\n ";const e=void 0;return["Copy","Delete","Add Top Port","Add Left Port","Add Bottom Port","Add Right Port"].forEach((e=>{const o=document.createElement("div");o.textContent=e,o.style.cssText="\n padding: 5px 20px;\n cursor: pointer;\n &:hover { background: #f0f0f0; }\n ",t.appendChild(o)})),t}render(){this.ctx.save(),this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height),this.portRects=[],this.ctx.translate(this.view.x*this.dpr,this.view.y*this.dpr),this.ctx.scale(this.view.zoom,this.view.zoom),this.data.modules.forEach((t=>{this.drawModule(t),this.drawPorts(t)})),this.data.modules.every((t=>t.leftArray&&t.rightArray&&t.topArray&&t.bottomArray))&&this.data.connections.forEach((t=>{this.drawConnection(t)})),this.startPort&&this.drawTempConnectionToMouse(this.startPort,this.mouseX,this.mouseY),this.ctx.restore()}drawPorts(t){const{x:e,y:o,width:i,height:s}=t.position,n=t.leftArray,r=s/(n.length+2),h=6,a=e-3;n.forEach(((i,s)=>{const n=o+(s+1)*r;this.ctx.fillStyle=`rgb(${100*i.portcolor}, 0, 0)`,this.ctx.fillRect(a,n,6,10),i.offsetX=a-e,i.offsetY=n-o,i.width=6,i.height=10;const h=new Rectangle(a,n,6,10);h.port=i,h.module=t,this.portRects.push(h)}));const c=t.rightArray,d=s/(c.length+2),l=6,f=e+i-3;c.forEach(((i,s)=>{const n=o+(s+1)*d;this.ctx.fillStyle=`rgb(${100*i.portcolor}, 0, 0)`,this.ctx.fillRect(f,n,6,10),i.offsetX=f-e,i.offsetY=n-o,i.width=6,i.height=10;const r=new Rectangle(f,n,6,10);r.port=i,r.module=t,this.portRects.push(r)}));const p=t.topArray,u=i/(p.length+2),x=6,y=o-3;p.forEach(((i,s)=>{const n=e+(s+1)*u;this.ctx.fillStyle=`rgb(${100*i.portcolor}, 0, 0)`,this.ctx.fillRect(n,y,10,6),i.offsetX=n-e,i.offsetY=y-o,i.width=10,i.height=6;const r=new Rectangle(n,y,10,6);r.port=i,r.module=t,this.portRects.push(r)}));const g=t.bottomArray,m=i/(g.length+2),w=6,v=o+s-3;g.forEach(((i,s)=>{const n=e+(s+1)*m;this.ctx.fillStyle=`rgb(${100*i.portcolor}, 0, 0)`,this.ctx.fillRect(n,v,10,6),i.offsetX=n-e,i.offsetY=v-o,i.width=10,i.height=6;const r=new Rectangle(n,v,10,6);r.port=i,r.module=t,this.portRects.push(r)}))}validateData(t){const e=t.modules.map((t=>t.name));t.connections.forEach((t=>{if(!e.includes(t.from)||!e.includes(t.to))throw new Error(`Invalid connection: ${t.from} -> ${t.to}`)}))}addEventListeners(){this.portRects=[],this.canvas.addEventListener("mousedown",(t=>{const e=this.getCanvasCoords(t.clientX,t.clientY),o=this.portRects.find((t=>t.contains(e.x,e.y)));o?(this.startPortRect=o,this.startPort=o.port,this.startPort.module=o.module):this.data.modules.forEach((t=>{const{x:o,y:i,width:s,height:n}=t.position;e.x>=o&&e.x<=o+s&&e.y>=i&&e.y<=i+n&&(this.selectedModule=t,this.draggingModule=t,this.dragOffsetX=e.x-o,this.dragOffsetY=e.y-i,this.render())}))})),this.canvas.addEventListener("mousemove",(t=>{const e=this.getCanvasCoords(t.clientX,t.clientY);this.mouseX=e.x,this.mouseY=e.y;const o=this.portRects.find((t=>t.contains(e.x,e.y)));if(this.canvas.style.cursor=o?"pointer":"",this.startPort){const t=this.findNearestPort(e.x,e.y);t?(this.snapPort=t,this.mouseX=t.x+t.width/2,this.mouseY=t.y+t.height/2):this.snapPort=null,this.render()}else this.draggingModule&&(this.draggingModule.position.x=e.x-this.dragOffsetX,this.draggingModule.position.y=e.y-this.dragOffsetY,this.updatePorts(this.draggingModule),this.render())})),this.canvas.addEventListener("mouseup",(t=>{if(this.startPort){if(this.snapPort){const t={from:this.startPortRect.module.name,to:this.snapPort.module.name,fromPort:this.startPort.portid,toPort:this.snapPort.port.portid};this.data.connections.push(t),this.drawConnection(t)}this.startPort=null,this.snapPort=null,this.render()}else this.draggingModule&&(this.draggingModule=null,this.render())})),this.canvas.addEventListener("wheel",(t=>{if(t.ctrlKey){t.preventDefault();const e=this.canvas.getBoundingClientRect(),o=(t.clientX-e.left)/this.dpr,i=(t.clientY-e.top)/this.dpr,s=this.view.zoom;this.view.zoom*=t.deltaY>0?.9:1.1,this.view.zoom=Math.max(.1,Math.min(this.view.zoom,5)),this.view.x-=(o-this.view.x)*(this.view.zoom/s-1),this.view.y-=(i-this.view.y)*(this.view.zoom/s-1),this.render()}})),this.canvas.addEventListener("contextmenu",(t=>{t.preventDefault();const e=this.getCanvasCoords(t.clientX,t.clientY),o=this.data.modules.find((t=>{const{x:o,y:i,width:s,height:n}=t.position;return e.x>=o&&e.x<=o+s&&e.y>=i&&e.y<=i+n}));if(o){this.activeModule=o;const t=this.contextMenu.offsetWidth,i=this.contextMenu.offsetHeight,s=window.innerWidth,n=window.innerHeight;let r=e.x,h=e.y;r+t>s&&(r=s-t),h+i>n&&(h=n-i),this.contextMenu.style.display="block",this.contextMenu.style.left=r+"px",this.contextMenu.style.top=h+"px"}})),this.contextMenu.addEventListener("click",(t=>{const e=void 0;switch(t.target.textContent){case"Copy":this.copyModule(this.activeModule);break;case"Delete":this.deleteModule(this.activeModule);break;case"Add Top Port":this.addPort(this.activeModule,"top");break;case"Add Left Port":this.addPort(this.activeModule,"left");break;case"Add Bottom Port":this.addPort(this.activeModule,"bottom");break;case"Add Right Port":this.addPort(this.activeModule,"right")}this.contextMenu.style.display="none",this.render()})),document.addEventListener("click",(()=>{this.contextMenu.style.display="none"}))}copyModule(t){const e=JSON.parse(JSON.stringify(t));e.name=t.name+"_copy",e.position={...t.position,x:t.position.x+50,y:t.position.y+50},this.data.modules.push(e)}deleteModule(t){const e=this.data.modules.indexOf(t);e>-1&&(this.data.modules.splice(e,1),this.data.connections=this.data.connections.filter((e=>e.from!==t.name&&e.to!==t.name)))}addPort(t,e){const o={portid:`${e}_${Date.now()}`,portcolor:1};switch(e){case"top":t.topArray.push(o);break;case"left":t.leftArray.push(o);break;case"bottom":t.bottomArray.push(o);break;case"right":t.rightArray.push(o)}}updatePorts(t){const{x:e,y:o,width:i,height:s}=t.position,n=t.leftArray,r=s/(n.length+2),h=6,a=e-3;n.forEach(((t,i)=>{const s=o+(i+1)*r;t.offsetX=a-e,t.offsetY=s-o}));const c=t.rightArray,d=s/(c.length+2),l=6,f=e+i-3;c.forEach(((t,i)=>{const s=o+(i+1)*d;t.offsetX=f-e,t.offsetY=s-o}));const p=t.topArray,u=i/(p.length+2),x=6,y=o-3;p.forEach(((t,i)=>{const s=e+(i+1)*u;t.offsetX=s-e,t.offsetY=y-o}));const g=t.bottomArray,m=i/(g.length+2),w=6,v=o+s-3;g.forEach(((t,i)=>{const s=e+(i+1)*m;t.offsetX=s-e,t.offsetY=v-o}))}getCanvasCoords(t,e){const o=this.canvas.getBoundingClientRect();return{x:(t-o.left-this.view.x)/this.view.zoom,y:(e-o.top-this.view.y)/this.view.zoom}}drawModule(t){const{x:e,y:o,width:i,height:s,color:n}=t.position;t!==this.selectedModule&&t!==this.draggingModule||(this.ctx.strokeStyle="rgba(179, 177, 177, 0.5)",this.ctx.lineWidth=4,this.ctx.strokeRect(e-4,o-4,i+8,s+8)),this.ctx.fillStyle=n,this.ctx.fillRect(e,o,i,s),this.ctx.strokeStyle="#000",this.ctx.lineWidth=2,this.ctx.strokeRect(e,o,i,s),this.ctx.shadowColor="rgba(0, 0, 0, 0.1)",this.ctx.shadowBlur=10,this.ctx.shadowOffsetX=5,this.ctx.shadowOffsetY=5,this.ctx.fillStyle="#000",this.ctx.font="12px Microsoft YaHei",this.ctx.fillText(t.name,e+10,o+20)}drawPolylineLink(t,e,o,i,s){t.beginPath(),t.moveTo(e,o),t.lineTo(e,o),t.lineTo(i,s),t.lineTo(i,s),t.strokeStyle="black",t.lineWidth=1,t.stroke()}drawPolylineLink_new(t,e,o,i,s,n,r){t.beginPath(),t.moveTo(e,o);const h=void 0;this.calculatePathPoints(e,o,i,s,n,r).forEach((e=>t.lineTo(e.x,e.y))),t.strokeStyle="black",t.lineWidth=1,t.stroke()}calculatePathPoints(t,e,o,i,s,n){const r=[{x:t,y:e}],h=20;switch(s){case"right":r.push({x:t+h,y:e});break;case"left":r.push({x:t-h,y:e});break;case"bottom":r.push({x:t,y:e+h});break;case"top":r.push({x:t,y:e-h})}const a=r[1];let c;switch(n){case"left":c={x:o-h,y:i};break;case"right":c={x:o+h,y:i};break;case"top":c={x:o,y:i-h};break;case"bottom":c={x:o,y:i+h}}return"left"===s||"right"===s?(r.push({x:a.x,y:c.y}),r.push(c)):(r.push({x:c.x,y:a.y}),r.push(c)),r.push({x:o,y:i}),r}drawConnection(t){const e=this.data.modules.find((e=>e.name===t.from)),o=this.data.modules.find((e=>e.name===t.to));if(!e||!o)return;const i=(t,e)=>t.leftArray.includes(e)?"left":t.rightArray.includes(e)?"right":t.topArray.includes(e)?"top":t.bottomArray.includes(e)?"bottom":void 0,s=e.leftArray.find((e=>e.portid===t.fromPort))||e.rightArray.find((e=>e.portid===t.fromPort))||e.topArray.find((e=>e.portid===t.fromPort))||e.bottomArray.find((e=>e.portid===t.fromPort)),n=o.leftArray.find((e=>e.portid===t.toPort))||o.rightArray.find((e=>e.portid===t.toPort))||o.topArray.find((e=>e.portid===t.toPort))||o.bottomArray.find((e=>e.portid===t.toPort));if(!s||!n)return;const r=i(e,s),h=i(o,n),a=e.position.x+s.offsetX+s.width/2,c=e.position.y+s.offsetY+s.height/2,d=o.position.x+n.offsetX+n.width/2,l=o.position.y+n.offsetY+n.height/2;this.drawPolylineLink_new(this.ctx,a,c,d,l,r,h)}drawArrow(t,e,o,i){const s=10;this.ctx.save(),this.ctx.translate(t,e),this.ctx.rotate(o),this.ctx.beginPath(),this.ctx.moveTo(-10,-5),this.ctx.lineTo(0,0),this.ctx.lineTo(-10,5),this.ctx.strokeStyle=i,this.ctx.lineWidth=2,this.ctx.stroke(),this.ctx.restore()}drawTempConnectionToMouse(t,e,o){if(!t||!t.module||!t.module.position)return;const i=t.module.position.x+t.offsetX+t.width/2,s=t.module.position.y+t.offsetY+t.height/2;this.ctx.beginPath(),this.ctx.moveTo(i,s),this.ctx.lineTo(e,o);const n=this.snapPort?"#00ff00":"#ff0000";this.ctx.strokeStyle=n,this.ctx.lineWidth=this.snapPort?2:1,this.ctx.stroke();const r=Math.atan2(o-s,e-i);this.drawArrow(e,o,r,n)}findNearestPort(t,e){let o=null,i=this.snapDistance;for(const s of this.portRects){if(this.startPort&&s.port===this.startPort)continue;const n=s.x+s.width/2,r=s.y+s.height/2,h=Math.sqrt(Math.pow(t-n,2)+Math.pow(e-r,2));h<i&&(i=h,o=s)}return o}getCurrentState(){const t=void 0,e=void 0;return{modules:this.data.modules.map((t=>{const e=t=>({portid:t.portid,portcolor:t.portcolor,offsetX:t.offsetX,offsetY:t.offsetY,width:t.width,height:t.height});return{id:t.id,name:t.name,type:t.type,position:{...t.position},leftArray:t.leftArray?t.leftArray.map(e):[],rightArray:t.rightArray?t.rightArray.map(e):[],topArray:t.topArray?t.topArray.map(e):[],bottomArray:t.bottomArray?t.bottomArray.map(e):[]}})),connections:this.data.connections.map((t=>({from:t.from,to:t.to,fromPort:t.fromPort,toPort:t.toPort,points:t.points||[]})))}}}class Rectangle{constructor(t,e,o,i){this.x=t,this.y=e,this.width=o,this.height=i}contains(t,e){return t>=this.x&&t<=this.x+this.width&&e>=this.y&&e<=this.y+this.height}}return{Ploteeavis:Ploteeavis}},"function"==typeof define&&define.amd?define([],e):"object"==typeof module&&module.exports?module.exports=e():t.veheeavis=e()}));