/** * SVG Generator mit SVG.js * Erzeugt Text-Symbole mit Rahmen und Pfeilen */ var SvgGenerator = { getDashArray: function(style, weight) { switch(style) { case 'dashed': return (weight * 4) + ',' + (weight * 2); case 'dotted': return weight + ',' + (weight * 1.5); default: return null; } }, measureText: function(text, fontSize) { var lines = text.split('\n'); var charWidth = fontSize * 0.6; var lineHeight = fontSize * 1.3; var maxLength = 0; for (var i = 0; i < lines.length; i++) { if (lines[i].length > maxLength) maxLength = lines[i].length; } if (maxLength === 0) maxLength = 4; return { lines: lines, width: maxLength * charWidth, height: lines.length * lineHeight, lineHeight: lineHeight, charWidth: charWidth }; }, createShape: function(draw, shape, x, y, width, height, style) { var shapeEl; var halfW = width / 2; var halfH = height / 2; var cx = x + halfW; var cy = y + halfH; switch(shape) { case 'rect': shapeEl = draw.rect(width, height).move(x, y).radius(4); break; case 'square': var squareSize = Math.max(width, height); var squareX = x - (squareSize - width) / 2; var squareY = y - (squareSize - height) / 2; shapeEl = draw.rect(squareSize, squareSize).move(squareX, squareY).radius(4); break; case 'rounded': shapeEl = draw.rect(width, height).move(x, y).radius(Math.min(halfW, halfH) * 0.5); break; case 'circle': var circleRadius = Math.max(halfW, halfH); shapeEl = draw.circle(circleRadius * 2).center(cx, cy); break; case 'oval': shapeEl = draw.ellipse(width, height).center(cx, cy); break; case 'diamond': shapeEl = draw.polygon([ [cx, y], [x + width, cy], [cx, y + height], [x, cy] ]); break; default: return null; } if (shapeEl) { shapeEl.fill('none').stroke({ color: style.color, width: style.weight }); if (style.dashArray) { shapeEl.attr('stroke-dasharray', style.dashArray); } } return shapeEl; }, calculateArrowHead: function(endX, endY, prevX, prevY, size, tipLength) { // Berechne den Winkel der Pfeilrichtung var angle = Math.atan2(endY - prevY, endX - prevX); // Die zwei hinteren Punkte der Pfeilspitze: // - tipLength zurueck in Pfeilrichtung // - size/2 seitlich (links und rechts) var baseX = endX - tipLength * Math.cos(angle); var baseY = endY - tipLength * Math.sin(angle); // Senkrecht zur Pfeilrichtung (90 Grad = PI/2) var perpAngle = angle + Math.PI / 2; var halfWidth = size / 2; return [ [endX, endY], // Spitze [baseX + halfWidth * Math.cos(perpAngle), baseY + halfWidth * Math.sin(perpAngle)], // Links [baseX - halfWidth * Math.cos(perpAngle), baseY - halfWidth * Math.sin(perpAngle)] // Rechts ]; }, createArrow: function(draw, direction, frameRect, options) { var length = options.length; var angle = options.angle; var bendPos = options.bendPos; var color = options.color; var weight = options.weight; var dashArray = options.dashArray; var arrowSize = options.arrowSize; var tipLength = options.tipLength; var angleRad = angle * Math.PI / 180; var bend = bendPos / 100; var startX, startY, midX, midY, endX, endY; var fx = frameRect.x; var fy = frameRect.y; var fw = frameRect.width; var fh = frameRect.height; // Fuer Standalone-Pfeile (width=0, height=0) ist fx,fy der Startpunkt var isStandalone = (fw === 0 && fh === 0); var cx = isStandalone ? fx : fx + fw / 2; var cy = isStandalone ? fy : fy + fh / 2; switch(direction) { case 'top': startX = cx; startY = isStandalone ? fy : fy; midX = startX; midY = startY - length * bend; endX = midX + Math.sin(angleRad) * (length * (1 - bend)); endY = midY - Math.cos(angleRad) * (length * (1 - bend)); break; case 'bottom': startX = cx; startY = isStandalone ? fy : fy + fh; midX = startX; midY = startY + length * bend; endX = midX + Math.sin(angleRad) * (length * (1 - bend)); endY = midY + Math.cos(angleRad) * (length * (1 - bend)); break; case 'left': startX = isStandalone ? fx : fx; startY = cy; midX = startX - length * bend; midY = startY; endX = midX - Math.cos(angleRad) * (length * (1 - bend)); endY = midY + Math.sin(angleRad) * (length * (1 - bend)); break; case 'right': startX = fx + fw; startY = cy; midX = startX + length * bend; midY = startY; endX = midX + Math.cos(angleRad) * (length * (1 - bend)); endY = midY + Math.sin(angleRad) * (length * (1 - bend)); break; default: return null; } var points = (angle !== 0 && bend > 0 && bend < 1) ? [[startX, startY], [midX, midY], [endX, endY]] : [[startX, startY], [endX, endY]]; var pathData = ''; for (var i = 0; i < points.length; i++) { pathData += (i === 0 ? 'M' : 'L') + points[i][0] + ' ' + points[i][1] + ' '; } var line = draw.path(pathData).fill('none').stroke({ color: color, width: weight }); if (dashArray) line.attr('stroke-dasharray', dashArray); var lastPoint = points[points.length - 1]; var prevPoint = points[points.length - 2]; var headPoints = this.calculateArrowHead(lastPoint[0], lastPoint[1], prevPoint[0], prevPoint[1], arrowSize, tipLength); var arrowHead = draw.polygon(headPoints).fill(color).stroke({ color: color, width: 1 }); return draw.group().add(line).add(arrowHead); }, createText: function(draw, text, x, y, options) { var fontSize = options.fontSize; var color = options.color; var lines = options.lines; var lineHeight = options.lineHeight; var group = draw.group(); var totalHeight = lines.length * lineHeight; var startY = y - (totalHeight / 2) + (fontSize * 0.8); for (var i = 0; i < lines.length; i++) { var lineY = startY + (i * lineHeight); var lineText = lines[i] || ' '; // Verwende plain SVG text element fuer bessere Kontrolle var textEl = draw.plain(lineText) .font({ family: 'Arial, sans-serif', size: fontSize, anchor: 'middle' }) .fill(color) .attr('x', x) .attr('y', lineY) .attr('text-anchor', 'middle'); group.add(textEl); } return group; }, generate: function(state) { var s = state; var textMetrics = this.measureText(s.text || 'Text', s.fontSize); var frameScale = s.frameScale / 100; var frameWidth = (textMetrics.width + s.paddingLeft + s.paddingRight) * frameScale; var frameHeight = (textMetrics.height + s.paddingTop + s.paddingBottom) * frameScale; var minWidth = Math.max(frameWidth, 40); var minHeight = Math.max(frameHeight, 30); // Pfeil-Raum berechnen inkl. Winkel-Abweichung var arrowSpace = 0; var arrowSideSpace = 0; if (s.arrow !== 'none') { var angleRad = Math.abs(s.arrowAngle) * Math.PI / 180; var bendFactor = s.arrowBend / 100; var effectiveLength = s.arrowLength * (1 - bendFactor); // Hauptrichtung: volle Laenge + Spitze + Puffer arrowSpace = s.arrowLength + s.arrowTipLength + 20; // Seitliche Abweichung durch Winkel arrowSideSpace = Math.abs(Math.sin(angleRad) * effectiveLength) + s.arrowSize + 10; } var svgWidth = minWidth; var svgHeight = minHeight; var offsetX = 0, offsetY = 0; switch(s.arrow) { case 'left': svgWidth += arrowSpace; svgHeight = Math.max(svgHeight, minHeight + arrowSideSpace * 2); offsetX = arrowSpace; offsetY = (svgHeight - minHeight) / 2; break; case 'right': svgWidth += arrowSpace; svgHeight = Math.max(svgHeight, minHeight + arrowSideSpace * 2); offsetY = (svgHeight - minHeight) / 2; break; case 'top': svgHeight += arrowSpace; svgWidth = Math.max(svgWidth, minWidth + arrowSideSpace * 2); offsetY = arrowSpace; offsetX = (svgWidth - minWidth) / 2; break; case 'bottom': svgHeight += arrowSpace; svgWidth = Math.max(svgWidth, minWidth + arrowSideSpace * 2); offsetX = (svgWidth - minWidth) / 2; break; } var draw = SVG().size(svgWidth, svgHeight); var frameX = offsetX; var frameY = offsetY; var frameCx = frameX + minWidth / 2; var frameCy = frameY + minHeight / 2; // Text-Verschiebung basierend auf asymmetrischem Padding // Wenn links mehr Padding ist, verschiebt sich der Text nach rechts var textOffsetX = (s.paddingLeft - s.paddingRight) / 2; var textOffsetY = (s.paddingTop - s.paddingBottom) / 2; var textCx = frameCx + textOffsetX; var textCy = frameCy + textOffsetY; var dashArray = this.getDashArray(s.lineStyle, s.lineWeight); var strokeStyle = { color: s.frameColor, weight: s.lineWeight, dashArray: dashArray }; if (s.shape !== 'none') { this.createShape(draw, s.shape, frameX, frameY, minWidth, minHeight, strokeStyle); } if (s.arrow !== 'none') { this.createArrow(draw, s.arrow, { x: frameX, y: frameY, width: minWidth, height: minHeight }, { length: s.arrowLength, angle: s.arrowAngle, bendPos: s.arrowBend, color: s.frameColor, weight: s.lineWeight, dashArray: dashArray, arrowSize: s.arrowSize, tipLength: s.arrowTipLength }); } this.createText(draw, s.text, textCx, textCy, { fontSize: s.fontSize, color: s.textColor, lines: textMetrics.lines, lineHeight: textMetrics.lineHeight }); return draw.svg(); }, generateArrowOnly: function(state) { var s = state; if (s.arrow === 'none') return null; var length = s.arrowLength; var tipLength = s.arrowTipLength; var arrowSize = s.arrowSize; var padding = 40; // Berechne die tatsaechlichen Endpunkte des Pfeils var angleRad = s.arrowAngle * Math.PI / 180; var bendFactor = s.arrowBend / 100; var straightPart = length * bendFactor; var angledPart = length * (1 - bendFactor); // Seitliche Ausdehnung durch den Winkel (mit Vorzeichen) var sideExtent = Math.sin(angleRad) * angledPart; var mainExtent = Math.cos(angleRad) * angledPart; // Mindestgroesse fuer die seitliche Ausdehnung var minSideSpace = Math.abs(sideExtent) + arrowSize + tipLength + padding; var width, height, startX, startY; switch(s.arrow) { case 'right': width = straightPart + mainExtent + tipLength + padding * 2; height = Math.max(arrowSize * 4, minSideSpace * 2); startX = padding; // Bei positivem Winkel geht der Pfeil nach unten, also Start weiter oben startY = height / 2 - sideExtent / 2; break; case 'left': width = straightPart + mainExtent + tipLength + padding * 2; height = Math.max(arrowSize * 4, minSideSpace * 2); startX = width - padding; startY = height / 2 + sideExtent / 2; break; case 'bottom': width = Math.max(arrowSize * 4, minSideSpace * 2); height = straightPart + mainExtent + tipLength + padding * 2; startX = width / 2 - sideExtent / 2; startY = padding; break; case 'top': width = Math.max(arrowSize * 4, minSideSpace * 2); height = straightPart + mainExtent + tipLength + padding * 2; startX = width / 2 + sideExtent / 2; startY = height - padding; break; } // Stelle sicher, dass alle Werte positiv sind width = Math.max(width, 80); height = Math.max(height, 60); var draw = SVG().size(width, height); // Simuliere einen Rahmen-Punkt als Startpunkt var frameRect = { x: startX, y: startY, width: 0, height: 0 }; this.createArrow(draw, s.arrow, frameRect, { length: s.arrowLength, angle: s.arrowAngle, bendPos: s.arrowBend, color: s.frameColor, weight: s.lineWeight, dashArray: this.getDashArray(s.lineStyle, s.lineWeight), arrowSize: s.arrowSize, tipLength: s.arrowTipLength }); return draw.svg(); } }; window.SvgGenerator = SvgGenerator;