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: Reines SVG + JS (kein JSXGraph) -->
<!-- VARIANTE B – KORRIGIERT -->
<html>
<html>
<style>
<style>
   .gozinto-wrap { width:95vw; max-width:1000px; height:70vw; max-height:600px; border:0px solid #ccc; }
   .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:13px; fill:#000; pointer-events:none; }
    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:12px; text-anchor:middle; dominant-baseline:middle; pointer-events:none; }
   .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 700" preserveAspectRatio="xMidYMid meet">
<svg id="gozinto_svg" viewBox="0 0 1400 600" preserveAspectRatio="xMinYMin meet">
  <!-- edges will be inserted dynamically -->
</svg>
</svg>
</div>
</div>
Zeile 44: Zeile 88:
<script>
<script>
(function(){
(function(){
   const svg = document.getElementById('gozinto_svg');
   const svg = document.getElementById("gozinto_svg");
 
   const scale = 100;
  // Layout scale (convert logical coords to svg px)
   const yOffset = 20;
   const scale = 100; // 1 unit = 100px
   const yOffset = 50;


  // Utility: create SVG element
   function svgEl(name, attrs){
   function svgEl(name, attrs){
     const el = document.createElementNS('http://www.w3.org/2000/svg', name);
     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;
   }
   }


   // Node factory: creates a rect group with fixed size but movable
   // ---- 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('g', {class:'node', 'data-id':id});
     const g = svgEl("g", {"data-id":id});
     const rect = svgEl('rect', {
     const rect = svgEl("rect", {
       class:'node-rect',
       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('text', {class:'node-text', x: cx*scale, y: cy*scale + yOffset, 'text-anchor':'middle', 'dominant-baseline':'middle'});
     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);


     // state
     let node = {id,cx,cy,w,h,rect,text,g};
    const node = {
      id, cx, cy, w, h, g, rect, text
    };


     // dragging
     // ---- Dragging ----
     let dragging = false;
     let dragging=false, start={};
    let start = null;


     rect.addEventListener('pointerdown', function(e){
     rect.addEventListener("pointerdown", e=>{
       rect.setPointerCapture(e.pointerId);
       rect.setPointerCapture(e.pointerId);
       dragging = true;
       dragging=true;
       start = {x:e.clientX, y:e.clientY, cx:node.cx, cy:node.cy};
      const p = getSVGcoords(e);
       start = {px:p.x, py:p.y, cx:node.cx, cy:node.cy};
     });
     });
     rect.addEventListener('pointermove', function(e){
 
     rect.addEventListener("pointermove", e=>{
       if(!dragging) return;
       if(!dragging) return;
       const dx = (e.clientX - start.x)/scale;
       const p = getSVGcoords(e);
       const dy = (e.clientY - start.y)/scale;
       node.cx = start.cx + (p.x - start.px)/scale;
       node.cx = start.cx + dx;
       node.cy = start.cy + (p.y - start.py)/scale;
      node.cy = start.cy + dy;
       updateNode(node);
       updateNode(node);
       updateAllEdges();
       updateAllEdges();
     });
     });
     rect.addEventListener('pointerup', function(e){
 
       dragging = false;
     rect.addEventListener("pointerup", e=>{
       dragging=false;
       rect.releasePointerCapture(e.pointerId);
       rect.releasePointerCapture(e.pointerId);
     });
     });
    rect.addEventListener('pointercancel', function(e){ dragging=false; });


     return node;
     return node;
   }
   }


   function updateNode(node){
   function updateNode(n){
     node.rect.setAttribute('x', (node.cx - node.w/2)*scale);
     n.rect.setAttribute("x",(n.cx-n.w/2)*scale);
     node.rect.setAttribute('y', (node.cy - node.h/2)*scale + yOffset);
     n.rect.setAttribute("y",(n.cy-n.h/2)*scale+yOffset);
     node.text.setAttribute('x', node.cx*scale);
     n.text.setAttribute("x",n.cx*scale);
     node.text.setAttribute('y', node.cy*scale + yOffset);
     n.text.setAttribute("y",n.cy*scale+yOffset);
   }
   }


   // compute intersection of ray center->target with rectangle border (axis-aligned)
   // --- Geometrie Hilfsfunktionen ---
   function intersectRectBorder(node, targetX, targetY){
   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 = targetX - cx, dy = targetY - cy;
     const dx=tx-cx, dy=ty-cy;
    const xMin = node.cx - node.w/2, xMax = node.cx + node.w/2;
     let pts=[];
     const yMin = node.cy - node.h/2, yMax = node.cy + node.h/2;
 
     let tCandidates = [];
     if(Math.abs(dx)>1e-9){
     if(Math.abs(dx) > 1e-9){
       let t1=(-w2)/dx;
       let t1 = (xMin - cx)/dx;
       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>=yMin-1e-9 && y1<=yMax+1e-9) tCandidates.push({t:t1,x:xMin,y:y1});
       let t2=(w2)/dx;
       let t2 = (xMax - cx)/dx;
       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>=yMin-1e-9 && y2<=yMax+1e-9) tCandidates.push({t:t2,x:xMax,y:y2});
     }
     }
     if(Math.abs(dy) > 1e-9){
     if(Math.abs(dy)>1e-9){
       let t3 = (yMin - cy)/dy;
       let t3=(-h2)/dy;
       let x3 = cx + t3*dx;
       let x3=cx+t3*dx;
       if(t3>0 && x3>=xMin-1e-9 && x3<=xMax+1e-9) tCandidates.push({t:t3,x:x3,y:yMin});
       if(t3>0 && x3>=cx-w2 && x3<=cx+w2) pts.push({x:x3,y:cy-h2,t:t3});
       let t4 = (yMax - cy)/dy;
       let t4=(h2)/dy;
       let x4 = cx + t4*dx;
       let x4=cx+t4*dx;
       if(t4>0 && x4>=xMin-1e-9 && x4<=xMax+1e-9) tCandidates.push({t:t4,x:x4,y:yMax});
       if(t4>0 && x4>=cx-w2 && x4<=cx+w2) pts.push({x:x4,y:cy+h2,t:t4});
     }
     }
    if(tCandidates.length===0) return {x:cx,y:cy};
 
     tCandidates.sort((a,b)=>a.t-b.t);
     pts.sort((a,b)=>a.t-b.t);
     return {x:tCandidates[0].x, y:tCandidates[0].y};
     return pts[0] || {x:cx,y:cy};
   }
   }


  // circle boundary point toward target
   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};
   }
   }


  // arrowhead path (triangle) at (x,y) pointing to direction (ux,uy)
   function makeArrowHead(x,y,ux,uy,size){
   function makeArrowHead(x, y, ux, uy, size){
     let px=-uy, py=ux;
     // perpendicular
     return `M ${x} ${y}
    const px = -uy, py = ux;
            L ${x-ux*size+px*size*0.5} ${y-uy*size+py*size*0.5}
     const p1x = x, p1y = y;
            L ${x-ux*size-px*size*0.5} ${y-uy*size-py*size*0.5} Z`;
    const p2x = x - ux*size + px*size*0.5;
    const p2y = y - uy*size + py*size*0.5;
    const p3x = x - ux*size - px*size*0.5;
    const p3y = y - uy*size - py*size*0.5;
    return `M ${p1x} ${p1y} L ${p2x} ${p2y} L ${p3x} ${p3y} Z`;
   }
   }


   // Edge structure: {fromNode, toNode, amount, circle, lineA, lineB, arrow}
   const edges=[];
  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);


  function makeConnection(fromNode, toNode, amount, yMid, xOffset){
     svg.appendChild(g);
    const group = svgEl('g', {});
    const circle = svgEl('circle', {class:'count-circle'});
    const text = svgEl('text', {class:'count-text'});
    const lineA = svgEl('path', {class:'edge-line', fill:'none'}); // from rect -> circle (path to allow potential future styling)
    const lineB = svgEl('path', {class:'edge-line', fill:'none'});
    const arrow = svgEl('path', {class:'edge-arrow'});
    group.appendChild(lineA);
    group.appendChild(lineB);
    group.appendChild(circle);
    group.appendChild(text);
    group.appendChild(arrow);
     svg.appendChild(group);


     const e = {fromNode, toNode, amount, circle, text, lineA, lineB, arrow, yMid, xOffset};
     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){
    // circle center is midpoint between centers with offset
     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.35;
 
    // compute in logical coords
     const pF=intersectRectBorder(e.fromNode,cx,cy);
     const pFrom = intersectRectBorder(e.fromNode, cx, cy);
     const pT=intersectRectBorder(e.toNode,cx,cy);
     const pTo  = intersectRectBorder(e.toNode, cx, cy);
 
     const cIn = pointOnCircle(cx, cy, R, pFrom.x, pFrom.y);
     const pCircleIn=pointOnCircle(cx,cy,R,pF.x,pF.y);
     const cOut = pointOnCircle(cx, cy, R, pTo.x, pTo.y);
     const pCircleOut=pointOnCircle(cx,cy,R,pT.x,pT.y);


     // convert to px
     const px=p=>[p.x*scale, p.y*scale+yOffset];
    function px(p){ return [p.x*scale, p.y*scale + yOffset]; }
    const pf = px(pFrom), pcIn = px(cIn), pcOut = px(cOut), pt = px(pTo);


     // line A: from rect edge -> circle edge (no arrow)
     const F = px(pF);
     lineAPath = `M ${pf[0]} ${pf[1]} L ${pcIn[0]} ${pcIn[1]}`;
     const Ci = px(pCircleIn);
     e.lineA.setAttribute('d', lineAPath);
     const Co = px(pCircleOut);
    const T = px(pT);


     // line B: circle edge -> rect edge (arrowhead drawn separately)
     e.lineA.setAttribute("d",`M ${F[0]} ${F[1]} L ${Ci[0]} ${Ci[1]}`);
     e.lineB.setAttribute('d', `M ${pcOut[0]} ${pcOut[1]} L ${pt[0]} ${pt[1]}`);
     e.lineB.setAttribute("d",`M ${Co[0]} ${Co[1]} L ${T[0]} ${T[1]}`);


    // circle
     e.circle.setAttribute("cx",cx*scale);
     e.circle.setAttribute('cx', (cx*scale));
     e.circle.setAttribute("cy",cy*scale+yOffset);
     e.circle.setAttribute('cy', (cy*scale + yOffset));
     e.circle.setAttribute("r",R*scale);
     e.circle.setAttribute('r', R*scale);
    e.circle.setAttribute('class','count-circle');


    // text
     e.text.setAttribute("x",cx*scale);
     e.text.setAttribute('x', cx*scale);
     e.text.setAttribute("y",cy*scale+yOffset);
     e.text.setAttribute('y', cy*scale + yOffset);
    e.text.textContent = e.amount;


     // arrow head: compute unit vector from cOut -> pTo
     let ux=T[0]-Co[0], uy=T[1]-Co[1];
    var ux = (pt[0]-pcOut[0]), uy = (pt[1]-pcOut[1]);
     let L=Math.sqrt(ux*ux+uy*uy);
     var 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;
    const arrowSize = 12;
 
     const arrowPath = makeArrowHead(pt[0], pt[1], ux, uy, arrowSize);
     e.arrow.setAttribute("d",makeArrowHead(T[0],T[1],ux,uy,10));
    e.arrow.setAttribute('d', arrowPath);
    e.arrow.setAttribute('class','edge-arrow');
   }
   }


   // create nodes (logical coords)
   function updateAllEdges(){ edges.forEach(updateEdge); }
  const nodes = {};
 
  nodes.E1 = createNode('E1', 0, 6.8, 1.0, 0.6, 'E1');
   // ---- NODES ----
  nodes.E2 = createNode('E2', 2.5, 6.8, 1.0, 0.6, 'E2');
   const nodes={};
   nodes.E3 = createNode('E3', 5, 6.8, 1.0, 0.6, 'E3');
   nodes.E4 = createNode('E4', 7.5, 6.8, 1.0, 0.6, 'E4');


   nodes.B1 = createNode('B1', 0.75, 3, 1.0, 0.6, 'B1');
  // Einzelteile oben
   nodes.B2 = createNode('B2', 2.5, 3, 1.0, 0.6, 'B2');
   nodes.E1=createNode("E1",0,1.0,1.0,0.5,"E1");
   nodes.B3 = createNode('B3', 5, 3, 1.0, 0.6, 'B3');
   nodes.E2=createNode("E2",2.5,1.0,1.0,0.5,"E2");
   nodes.B4 = createNode('B4', 7.5, 3, 1.0, 0.6, 'B4');
   nodes.E3=createNode("E3",5.0,1.0,1.0,0.5,"E3");
  nodes.B5 = createNode('B5', 10, 3, 1.0, 0.6, 'B5');
   nodes.E4=createNode("E4",7.5,1.0,1.0,0.5,"E4");


   // create edges (with small xOffsets to avoid overlap)
   // Bauteile darunter
   makeConnection(nodes.E1, nodes.B1, '2', 5.6, -0.2);
  nodes.B1=createNode("B1",0.75,3.3,1.0,0.5,"B1");
   makeConnection(nodes.E2, nodes.B1, '1', 5.6, 0.2);
   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.B2, '2', 5.6, -0.2);
  // ---- EDGES ----
   makeConnection(nodes.E2, nodes.B2, '1', 5.6, 0.2);
   makeConnection(nodes.E1,nodes.B1,"2",2.2,-0.2);
   makeConnection(nodes.E2,nodes.B1,"1",2.2, 0.2);


   makeConnection(nodes.E1, nodes.B3, '1', 5.6, -0.3);
   makeConnection(nodes.E1,nodes.B2,"2",2.2,-0.2);
   makeConnection(nodes.E2, nodes.B3, '1', 5.6,  0.0);
   makeConnection(nodes.E2,nodes.B2,"1",2.2, 0.2);
  makeConnection(nodes.E3, nodes.B3, '1', 5.6, 0.3);


   makeConnection(nodes.E1, nodes.B4, '2', 5.6, -0.3);
   makeConnection(nodes.E1,nodes.B3,"1",2.2,-0.25);
   makeConnection(nodes.E3, nodes.B4, '1', 5.6, 0.0);
   makeConnection(nodes.E2,nodes.B3,"1",2.2, 0.0);
   makeConnection(nodes.E4, nodes.B4, '1', 5.6, 0.3);
   makeConnection(nodes.E3,nodes.B3,"1",2.2, 0.25);


   makeConnection(nodes.E1, nodes.B5, '1', 5.6, -0.2);
   makeConnection(nodes.E1,nodes.B4,"2",2.2,-0.3);
   makeConnection(nodes.E4, nodes.B5, '2', 5.6, 0.2);
  makeConnection(nodes.E3,nodes.B4,"1",2.2, 0.0);
   makeConnection(nodes.E4,nodes.B4,"1",2.2, 0.3);


   // update all edges
   makeConnection(nodes.E1,nodes.B5,"1",2.2,-0.2);
   function updateAllEdges(){ edges.forEach(e=>updateEdge(e)); }
   makeConnection(nodes.E4,nodes.B5,"2",2.2, 0.2);


  // initial update
   updateAllEdges();
   updateAllEdges();
  // re-update edges also when window resizes (visual)
  window.addEventListener('resize', updateAllEdges);
})();
})();
</script>
</script>
</html>
</html>