Gozintograph: Unterschied zwischen den Versionen

Aus FLBK-Wiki
Zur Navigation springen Zur Suche springen
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>



Version vom 14. November 2025, 11:15 Uhr

Ein Gozintograph (von engl. *goes into* = „geht hinein“) ist ein gerichteter Graph, der die Zerlegung eines Endprodukts in seine Einzelteile oder Komponenten beschreibt. Jede Kante stellt dabei eine „Gozinto“-Beziehung dar: Sie zeigt von einer Komponente (Teil) auf das Produkt, in das sie eingeht. Der Gozintograph ist ein zentrales Hilfsmittel in der Produktionsplanung und Stücklistenverwaltung.

Definition

Ein Gozintograph ist ein gerichteter, azyklischer Graph \( G = (V, E) \), wobei:

  • \( V \) die Menge der Knoten darstellt (Produkte oder Teile),
  • \( E \subseteq V \times V \) die gerichteten Kanten darstellt, welche „geht-in“-Beziehungen symbolisieren.

Eine Kante \( (v_i, v_j, a_{ij}) \) mit der Beschriftung \( a_{ij} \) zeigt an, dass zur Herstellung eines Teils \( v_j \) genau \( a_{ij} \) Einheiten von Teil \( v_i \) benötigt werden.

Zusammenhang zu Matrizen

Die Informationen eines Gozintographen lassen sich in einer sogenannten Gozintomatrix darstellen. Diese ist eine Matrix \( A = (a_{ij}) \), bei der das Element \( a_{ij} \) die Anzahl der Einheiten von Komponente \( i \) angibt, die für die Herstellung von Produkt \( j \) benötigt wird. In der Produktionsplanung kann die benötigte Gesamtmenge aller Einzelteile über die Gleichung

\[ \mathbf{x} = (I - A)^{-1} \mathbf{y} \]

bestimmt werden, wobei \( \mathbf{y} \) den Vektor der Endprodukte und \( \mathbf{x} \) den Vektor der benötigten Teilemengen beschreibt.

Beispiele

Produktion eines Produkts aus Einzelteilen

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.

Beispiel 2: Rezeptstruktur eines Gerichts

Rezeptstruktur eines Gerichts

Im nächsten Beispiel wird der Gozintograph genutzt, um die Zutatenstruktur eines Rezepts zu zeigen. Das Endprodukt „Pizza“ besteht aus mehreren Zwischenprodukten („Teig“, „Soße“) und Basiszutaten. Auch hier zeigen Pfeile mit Zahlen, welche Mengen von Zutaten in die jeweiligen Komponenten eingehen.