@webwriter/flowchart
Version:
Create programming flowcharts with interactive tasks. Use standardized Elements such as loops and Branchings.
203 lines (186 loc) • 6.94 kB
text/typescript
import { GraphNode } from "../../definitions/GraphNode";
import { measureTextSize } from "../helper/utilities";
import { getAnchors } from "../helper/anchorHelper";
import { ThemeManager } from "../styles/ThemeManager";
// Funktion zum Zeichnen von GraphNode-Elementen
export function drawGraphNode(ctx: CanvasRenderingContext2D, element: GraphNode, settings: { font: string; fontSize: number; theme: string }, selectedNodes: GraphNode[], selectedSequence: any[]) {
const themeManager: ThemeManager = new ThemeManager();
const theme = themeManager.getTheme(settings.theme);
// Setze die Schriftart des Textes, dies muss vorher gesetzt werden, damit die größe des Textes richtig berechnet werden kann.
if (settings.font === 'Courier New') {
ctx.font = `bold ${settings.fontSize}px ${settings.font}`;
} else {
ctx.font = `${settings.fontSize}px ${settings.font}`;
}
const { node, text, x, y } = element;
let { width, height } = measureTextSize(ctx, text);
// Zeichne die passenden Knoten je nach Typ
switch (node) {
case 'start':
case 'end':
// abgerundestes Rechteck
ctx.fillStyle = theme.startEndColor;
const radius = 25;
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.lineTo(x + width - radius, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
ctx.lineTo(x + width, y + height - radius);
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
ctx.lineTo(x + radius, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
ctx.lineTo(x, y + radius);
ctx.quadraticCurveTo(x, y, x + radius, y);
ctx.closePath();
ctx.fill();
break;
case 'op':
// Rechteck
ctx.fillStyle = theme.opColor;
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x + width, y);
ctx.lineTo(x + width, y + height);
ctx.lineTo(x, y + height);
ctx.closePath();
ctx.fill();
break;
case 'decision':
// Diamant
ctx.fillStyle = theme.decisionColor;
ctx.beginPath();
ctx.moveTo(x + width / 2, y);
ctx.lineTo(x + width, y + height / 2);
ctx.lineTo(x + width / 2, y + height);
ctx.lineTo(x, y + height / 2);
ctx.closePath();
ctx.fill();
break;
case 'connector':
// Kreis
ctx.fillStyle = theme.connectorColor;
const circleRadius = Math.min(width, height) / 3;
ctx.beginPath();
ctx.arc(x + width / 2, y + height / 2, circleRadius, 0, 2 * Math.PI);
ctx.fill();
break;
case 'i/o':
// Parallelogramm
ctx.fillStyle = theme.ioColor;
const skew = 20;
ctx.beginPath();
ctx.moveTo(x + skew, y);
ctx.lineTo(x + width, y);
ctx.lineTo(x + width - skew, y + height);
ctx.lineTo(x, y + height);
ctx.closePath();
ctx.fill();
break;
case 'sub':
// Rechteck mit 2 vertikalen Linien
ctx.fillStyle = theme.subColor;
const d = 6; // Abstand der Linien
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x + width, y);
ctx.lineTo(x + width, y + height);
ctx.lineTo(x, y + height);
ctx.lineTo(x, y);
ctx.moveTo(x + d, y);
ctx.lineTo(x + d, y + height);
ctx.moveTo(x + width - d, y);
ctx.lineTo(x + width - d, y + height);
ctx.closePath();
ctx.fill();
break;
default:
ctx.fillStyle = '';
}
// Fügt den schwarzen Umriss hinzu
if (element.node !== 'text') {
ctx.strokeStyle = 'black';
ctx.setLineDash([]);
ctx.lineWidth = 2;
ctx.stroke();
}
// Text zum Element hinzufügen
ctx.fillStyle = 'black';
ctx.textAlign = 'center'
ctx.textBaseline = 'middle';
const textX = x + width / 2;
const textY = y + height / 2;
ctx.fillText(text, textX, textY);
// Hervorhebung eines ausgewählten Knoten
if (selectedNodes.some(node => node.id === element.id)) {
ctx.strokeStyle = '#87cefa';
ctx.setLineDash([5, 10]);
ctx.lineWidth = 2;
ctx.strokeRect(x, y, width, height);
}
//Hervorhebung der ausgewählten Sequenz und Anzeige des Counters
if (selectedSequence.length > 0) {
// Bestimmt die Indizes, an denen die Knoten-ID in der selectedSequence vorkommt.
const indices = selectedSequence.map((item, index) => item.id === element.id && item.type === 'node' ? index : -1).filter(index => index !== -1);
// Zeichnet die Zahlen basierend auf den berechneten Indizes.
indices.forEach((index, i) => {
ctx.save();
ctx.strokeStyle = '#990000';
ctx.lineWidth = 2;
ctx.stroke();
ctx.fillStyle = '#990000';
ctx.font = 'bold 16px Arial';
ctx.fillText(
(index + 1).toString(),
x + width + ((i + 1) * 20),
y - 6 - (i * 3)
);
ctx.restore();
});
}
}
export function drawNodeAnchors(ctx: CanvasRenderingContext2D, element: GraphNode, hoveredAnchor: { element: GraphNode; anchor: number } | undefined) {
if (element.node !== 'text') {
ctx.fillStyle = '#5CACEE';
const distance = 25;
const anchors = getAnchors(ctx, element, distance);
// Zeichne die Ankerpunkte
anchors.forEach((position, index) => {
// Falls ein Ankerpunkt gehovert wird, wird die Transparenz auf 1 gesetzt.
(hoveredAnchor && hoveredAnchor.element === element && hoveredAnchor.anchor === index) ? ctx.globalAlpha = 1 : ctx.globalAlpha = 0.4;
let angle: number;
switch (index) {
case 0:
angle = (3 * Math.PI) / 2;
break;
case 1:
angle = 0;
break;
case 2:
angle = Math.PI / 2;
break;
case 3:
angle = Math.PI;
break;
default:
angle = 0;
}
drawArrowHead(ctx, position.x, position.y, angle);
});
ctx.globalAlpha = 1;
}
}
// Zeichne den Ankerpunkt als Pfeilspitze für die Knoten
function drawArrowHead(ctx: CanvasRenderingContext2D, x: number, y: number, angle: number) {
const length = 15;
const width = 10;
ctx.save();
ctx.translate(x, y);
ctx.rotate(angle);
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(-length, -width);
ctx.lineTo(-length, width);
ctx.closePath();
ctx.fill();
ctx.restore();
}