Gozintograph: Unterschied zwischen den Versionen
Markierung: Manuelle Zurücksetzung |
|||
| Zeile 23: | Zeile 23: | ||
Im folgenden Beispiel werden fünf Bauteile \( B_1, B_2, B_3, B_4, B_5 \) aus vier Einzelteilen \( E_1, E_2, E_3, E_4 \) gefertigt. | Im folgenden Beispiel werden fünf Bauteile \( B_1, B_2, B_3, B_4, B_5 \) aus vier Einzelteilen \( E_1, E_2, E_3, E_4 \) gefertigt. | ||
Die Pfeile zeigen, welche Einzelteile in welches Bauteil eingehen. Die Zahlen an den Pfeilen geben die Stückzahl an. | Die Pfeile zeigen, welche Einzelteile in welches Bauteil eingehen. Die Zahlen an den Pfeilen geben die Stückzahl an. | ||
<!-- | <!-- VARIANTE B – KORRIGIERT --> | ||
<html> | <html> | ||
<style> | <style> | ||
.gozinto-wrap { width:95vw; max-width: | .gozinto-wrap { | ||
svg { width:100%; height:100%; touch-action:none; user-select:none; } | width:95vw; | ||
.node-rect { fill:#3498db; stroke:#1f4e78; stroke-width:2; cursor:grab; } | height:55vw; | ||
.node-text { font-family: sans-serif; font-size: | max-width:1100px; | ||
.edge-line { stroke:#000; stroke-width:2; fill:none; } | max-height:500px; | ||
border:0; | |||
margin:0; | |||
padding:0; | |||
} | |||
svg { | |||
width:100%; | |||
height:100%; | |||
touch-action:none; | |||
user-select:none; | |||
background:white; | |||
} | |||
.node-rect { | |||
fill:#3498db; | |||
stroke:#1f4e78; | |||
stroke-width:2; | |||
cursor:grab; | |||
} | |||
.node-text { | |||
font-family: sans-serif; | |||
font-size:14px; | |||
fill:#000; | |||
pointer-events:none; | |||
} | |||
.edge-line { | |||
stroke:#000; | |||
stroke-width:2; | |||
fill:none; | |||
} | |||
.edge-arrow { fill:#000; } | .edge-arrow { fill:#000; } | ||
.count-circle { fill:#fff; stroke:#000; stroke-width:1.5; } | |||
.count-text { font-family:sans-serif; font-size: | .count-circle { | ||
fill:#fff; | |||
stroke:#000; | |||
stroke-width:1.5; | |||
} | |||
.count-text { | |||
font-family:sans-serif; | |||
font-size:11px; | |||
text-anchor:middle; | |||
dominant-baseline:middle; | |||
pointer-events:none; | |||
} | |||
</style> | </style> | ||
<div class="gozinto-wrap"> | <div class="gozinto-wrap"> | ||
<svg id="gozinto_svg" viewBox="0 0 1400 | <svg id="gozinto_svg" viewBox="0 0 1400 600" preserveAspectRatio="xMinYMin meet"> | ||
</svg> | </svg> | ||
</div> | </div> | ||
| Zeile 44: | Zeile 88: | ||
<script> | <script> | ||
(function(){ | (function(){ | ||
const svg = document.getElementById( | const svg = document.getElementById("gozinto_svg"); | ||
const scale = 100; | |||
const yOffset = 20; | |||
const scale = 100; | |||
const yOffset = | |||
function svgEl(name, attrs){ | function svgEl(name, attrs){ | ||
const el = document.createElementNS( | const el = document.createElementNS("http://www.w3.org/2000/svg", name); | ||
for(const k in (attrs||{})) el.setAttribute(k, attrs[k]); | for(const k in (attrs||{})) el.setAttribute(k, attrs[k]); | ||
return el; | return el; | ||
} | } | ||
// | // ---- SVG-Koordinaten aus Mausposition ---- | ||
function getSVGcoords(evt){ | |||
const pt = svg.createSVGPoint(); | |||
pt.x = evt.clientX; | |||
pt.y = evt.clientY; | |||
return pt.matrixTransform(svg.getScreenCTM().inverse()); | |||
} | |||
// ---- NODE (rechteck) ---- | |||
function createNode(id, cx, cy, w, h, label){ | function createNode(id, cx, cy, w, h, label){ | ||
const g = svgEl( | const g = svgEl("g", {"data-id":id}); | ||
const rect = svgEl( | const rect = svgEl("rect", { | ||
class: | class:"node-rect", | ||
x: (cx - w/2)*scale, y: (cy - h/2)*scale + yOffset, | x:(cx-w/2)*scale, y:(cy-h/2)*scale + yOffset, | ||
width: w*scale, height: h*scale, | width:w*scale, height:h*scale, | ||
rx:6, ry:6 | rx:6, ry:6 | ||
}); | }); | ||
const text = svgEl( | const text = svgEl("text", { | ||
class:"node-text", | |||
x:cx*scale, y:cy*scale+yOffset, | |||
"text-anchor":"middle", "dominant-baseline":"middle" | |||
}); | |||
text.textContent = label; | text.textContent = label; | ||
| Zeile 73: | Zeile 126: | ||
svg.appendChild(g); | svg.appendChild(g); | ||
let node = {id,cx,cy,w,h,rect,text,g}; | |||
// | // ---- Dragging ---- | ||
let dragging = false | let dragging=false, start={}; | ||
rect.addEventListener( | rect.addEventListener("pointerdown", e=>{ | ||
rect.setPointerCapture(e.pointerId); | rect.setPointerCapture(e.pointerId); | ||
dragging = true; | dragging=true; | ||
start = { | const p = getSVGcoords(e); | ||
start = {px:p.x, py:p.y, cx:node.cx, cy:node.cy}; | |||
}); | }); | ||
rect.addEventListener( | |||
rect.addEventListener("pointermove", e=>{ | |||
if(!dragging) return; | if(!dragging) return; | ||
const | const p = getSVGcoords(e); | ||
node.cx = start.cx + (p.x - start.px)/scale; | |||
node. | node.cy = start.cy + (p.y - start.py)/scale; | ||
updateNode(node); | updateNode(node); | ||
updateAllEdges(); | updateAllEdges(); | ||
}); | }); | ||
rect.addEventListener( | |||
dragging = false; | rect.addEventListener("pointerup", e=>{ | ||
dragging=false; | |||
rect.releasePointerCapture(e.pointerId); | rect.releasePointerCapture(e.pointerId); | ||
}); | }); | ||
return node; | return node; | ||
} | } | ||
function updateNode( | function updateNode(n){ | ||
n.rect.setAttribute("x",(n.cx-n.w/2)*scale); | |||
n.rect.setAttribute("y",(n.cy-n.h/2)*scale+yOffset); | |||
n.text.setAttribute("x",n.cx*scale); | |||
n.text.setAttribute("y",n.cy*scale+yOffset); | |||
} | } | ||
// | // --- Geometrie Hilfsfunktionen --- | ||
function intersectRectBorder(node, | function intersectRectBorder(node, tx, ty){ | ||
const cx = node.cx, cy = node.cy | const cx=node.cx, cy=node.cy, w2=node.w/2, h2=node.h/2; | ||
const dx=tx-cx, dy=ty-cy; | |||
let pts=[]; | |||
const | |||
let | if(Math.abs(dx)>1e-9){ | ||
if(Math.abs(dx) > 1e-9){ | let t1=(-w2)/dx; | ||
let t1 = ( | let y1=cy+t1*dy; | ||
let y1 = cy + t1*dy; | if(t1>0 && y1>=cy-h2 && y1<=cy+h2) pts.push({x:cx-w2,y:y1,t:t1}); | ||
if(t1>0 && y1>= | let t2=(w2)/dx; | ||
let t2 = ( | let y2=cy+t2*dy; | ||
let y2 = cy + t2*dy; | if(t2>0 && y2>=cy-h2 && y2<=cy+h2) pts.push({x:cx+w2,y:y2,t:t2}); | ||
if(t2>0 && y2>= | |||
} | } | ||
if(Math.abs(dy) > 1e-9){ | if(Math.abs(dy)>1e-9){ | ||
let t3 = ( | let t3=(-h2)/dy; | ||
let x3 = cx + t3*dx; | let x3=cx+t3*dx; | ||
if(t3>0 && x3>= | if(t3>0 && x3>=cx-w2 && x3<=cx+w2) pts.push({x:x3,y:cy-h2,t:t3}); | ||
let t4 = ( | let t4=(h2)/dy; | ||
let x4 = cx + t4*dx; | let x4=cx+t4*dx; | ||
if(t4>0 && x4>= | if(t4>0 && x4>=cx-w2 && x4<=cx+w2) pts.push({x:x4,y:cy+h2,t:t4}); | ||
} | } | ||
pts.sort((a,b)=>a.t-b.t); | |||
return | return pts[0] || {x:cx,y:cy}; | ||
} | } | ||
function pointOnCircle(cx,cy,R,tx,ty){ | |||
function pointOnCircle(cx, cy, R, tx, ty){ | const dx=tx-cx, dy=ty-cy; | ||
const dx = tx - cx, dy = ty - cy; | const d=Math.sqrt(dx*dx+dy*dy); | ||
const d = Math.sqrt(dx*dx + dy*dy); | if(d<1e-9) return {x:cx,y:cy}; | ||
if(d < 1e-9) return {x:cx, y:cy}; | return {x:cx+R*dx/d, y:cy+R*dy/d}; | ||
return {x: cx + R*dx/d, y: cy + R*dy/d}; | |||
} | } | ||
function makeArrowHead(x,y,ux,uy,size){ | |||
function makeArrowHead(x, y, ux, uy, size){ | let px=-uy, py=ux; | ||
return `M ${x} ${y} | |||
L ${x-ux*size+px*size*0.5} ${y-uy*size+py*size*0.5} | |||
L ${x-ux*size-px*size*0.5} ${y-uy*size-py*size*0.5} Z`; | |||
} | } | ||
const edges=[]; | |||
function makeConnection(fromNode,toNode,amount,yMid,xOffset){ | |||
const g=svgEl("g",{}); | |||
const lineA=svgEl("path",{class:"edge-line"}); | |||
const lineB=svgEl("path",{class:"edge-line"}); | |||
const circle=svgEl("circle",{class:"count-circle"}); | |||
const text=svgEl("text",{class:"count-text"}); | |||
const arrow=svgEl("path",{class:"edge-arrow"}); | |||
text.textContent=amount; | |||
g.appendChild(lineA); | |||
g.appendChild(lineB); | |||
g.appendChild(circle); | |||
g.appendChild(text); | |||
g.appendChild(arrow); | |||
svg.appendChild(g); | |||
svg.appendChild( | |||
let e={fromNode,toNode,amount,yMid,xOffset,circle,text,lineA,lineB,arrow}; | |||
edges.push(e); | edges.push(e); | ||
updateEdge(e); | updateEdge(e); | ||
| Zeile 183: | Zeile 229: | ||
function updateEdge(e){ | function updateEdge(e){ | ||
const cx=(e.fromNode.cx+e.toNode.cx)/2+(e.xOffset||0); | |||
const cx = (e.fromNode.cx + e.toNode.cx)/2 + (e.xOffset||0); | const cy=e.yMid; | ||
const cy = e.yMid; | const R=0.25; | ||
const R = 0. | |||
const pF=intersectRectBorder(e.fromNode,cx,cy); | |||
const | const pT=intersectRectBorder(e.toNode,cx,cy); | ||
const | |||
const | const pCircleIn=pointOnCircle(cx,cy,R,pF.x,pF.y); | ||
const | const pCircleOut=pointOnCircle(cx,cy,R,pT.x,pT.y); | ||
const px=p=>[p.x*scale, p.y*scale+yOffset]; | |||
const F = px(pF); | |||
const Ci = px(pCircleIn); | |||
const Co = px(pCircleOut); | |||
const T = px(pT); | |||
e.lineA.setAttribute("d",`M ${F[0]} ${F[1]} L ${Ci[0]} ${Ci[1]}`); | |||
e.lineB.setAttribute( | e.lineB.setAttribute("d",`M ${Co[0]} ${Co[1]} L ${T[0]} ${T[1]}`); | ||
e.circle.setAttribute("cx",cx*scale); | |||
e.circle.setAttribute( | e.circle.setAttribute("cy",cy*scale+yOffset); | ||
e.circle.setAttribute( | e.circle.setAttribute("r",R*scale); | ||
e.circle.setAttribute( | |||
e.text.setAttribute("x",cx*scale); | |||
e.text.setAttribute( | e.text.setAttribute("y",cy*scale+yOffset); | ||
e.text.setAttribute( | |||
let ux=T[0]-Co[0], uy=T[1]-Co[1]; | |||
let L=Math.sqrt(ux*ux+uy*uy); | |||
if(L<1e-6) L=1; | if(L<1e-6) L=1; | ||
ux/=L; uy/=L; | ux/=L; uy/=L; | ||
e.arrow.setAttribute("d",makeArrowHead(T[0],T[1],ux,uy,10)); | |||
} | } | ||
function updateAllEdges(){ edges.forEach(updateEdge); } | |||
// ---- NODES ---- | |||
const nodes={}; | |||
nodes | |||
nodes. | // Einzelteile oben | ||
nodes. | nodes.E1=createNode("E1",0,1.0,1.0,0.5,"E1"); | ||
nodes. | nodes.E2=createNode("E2",2.5,1.0,1.0,0.5,"E2"); | ||
nodes. | nodes.E3=createNode("E3",5.0,1.0,1.0,0.5,"E3"); | ||
nodes.E4=createNode("E4",7.5,1.0,1.0,0.5,"E4"); | |||
// | // Bauteile darunter | ||
nodes.B1=createNode("B1",0.75,3.3,1.0,0.5,"B1"); | |||
nodes.B2=createNode("B2",2.5,3.3,1.0,0.5,"B2"); | |||
nodes.B3=createNode("B3",5.0,3.3,1.0,0.5,"B3"); | |||
nodes.B4=createNode("B4",7.5,3.3,1.0,0.5,"B4"); | |||
nodes.B5=createNode("B5",10,3.3,1.0,0.5,"B5"); | |||
makeConnection(nodes.E1, nodes. | // ---- EDGES ---- | ||
makeConnection(nodes.E2, nodes. | makeConnection(nodes.E1,nodes.B1,"2",2.2,-0.2); | ||
makeConnection(nodes.E2,nodes.B1,"1",2.2, 0.2); | |||
makeConnection(nodes.E1, nodes. | makeConnection(nodes.E1,nodes.B2,"2",2.2,-0.2); | ||
makeConnection(nodes.E2, nodes. | makeConnection(nodes.E2,nodes.B2,"1",2.2, 0.2); | ||
makeConnection(nodes.E1, nodes. | makeConnection(nodes.E1,nodes.B3,"1",2.2,-0.25); | ||
makeConnection(nodes. | makeConnection(nodes.E2,nodes.B3,"1",2.2, 0.0); | ||
makeConnection(nodes. | makeConnection(nodes.E3,nodes.B3,"1",2.2, 0.25); | ||
makeConnection(nodes.E1, nodes. | makeConnection(nodes.E1,nodes.B4,"2",2.2,-0.3); | ||
makeConnection(nodes.E4, nodes. | makeConnection(nodes.E3,nodes.B4,"1",2.2, 0.0); | ||
makeConnection(nodes.E4,nodes.B4,"1",2.2, 0.3); | |||
makeConnection(nodes.E1,nodes.B5,"1",2.2,-0.2); | |||
makeConnection(nodes.E4,nodes.B5,"2",2.2, 0.2); | |||
updateAllEdges(); | updateAllEdges(); | ||
})(); | })(); | ||
</script> | </script> | ||
</html> | </html> | ||