| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700 |
- // Package exporter provides high-quality SVG export functionality based on mermaid.js rendering logic
- package exporter
- import (
- "fmt"
- "math"
- "strings"
- "mermaid-go/pkg/ast"
- )
- // SVGExporter exports diagrams to high-quality SVG format
- type SVGExporter struct {
- width int
- height int
- theme string
- }
- // NewSVGExporter creates a new SVG exporter
- func NewSVGExporter() *SVGExporter {
- return &SVGExporter{
- width: 800,
- height: 600,
- theme: "default",
- }
- }
- // SetSize sets the SVG canvas size
- func (e *SVGExporter) SetSize(width, height int) *SVGExporter {
- e.width = width
- e.height = height
- return e
- }
- // SetTheme sets the SVG theme
- func (e *SVGExporter) SetTheme(theme string) *SVGExporter {
- e.theme = theme
- return e
- }
- // ExportToSVG exports a diagram to SVG format
- func (e *SVGExporter) ExportToSVG(diagram ast.Diagram) (string, error) {
- switch d := diagram.(type) {
- case *ast.PieChart:
- return e.exportPieChartToSVG(d)
- case *ast.OrganizationDiagram:
- return e.exportOrganizationToSVG(d)
- case *ast.Flowchart:
- return e.exportFlowchartToSVG(d)
- case *ast.SequenceDiagram:
- return e.exportSequenceToSVG(d)
- case *ast.GanttDiagram:
- return e.exportGanttToSVG(d)
- case *ast.TimelineDiagram:
- return e.exportTimelineToSVG(d)
- case *ast.UserJourneyDiagram:
- return e.exportJourneyToSVG(d)
- case *ast.ArchitectureDiagram:
- return e.exportArchitectureToSVG(d)
- case *ast.BPMNDiagram:
- return e.exportBPMNToSVG(d)
- case *ast.ClassDiagram:
- return e.exportClassToSVG(d)
- case *ast.StateDiagram:
- return e.exportStateToSVG(d)
- case *ast.ERDiagram:
- return e.exportERToSVG(d)
- case *ast.MindmapDiagram:
- return e.exportMindmapToSVG(d)
- case *ast.KanbanDiagram:
- return e.exportKanbanToSVG(d)
- case *ast.GitDiagram:
- return e.exportGitToSVG(d)
- case *ast.SankeyDiagram:
- return e.exportSankeyToSVG(d)
- case *ast.XYChartDiagram:
- return e.exportXYChartToSVG(d)
- case *ast.RadarDiagram:
- return e.exportRadarToSVG(d)
- case *ast.TreemapDiagram:
- return e.exportTreemapToSVG(d)
- case *ast.PacketDiagram:
- return e.exportPacketToSVG(d)
- case *ast.InfoDiagram:
- return e.exportInfoToSVG(d)
- case *ast.C4ContextDiagram:
- return e.exportC4ToSVG(d)
- case *ast.C4ContainerDiagram:
- return e.exportC4ToSVG(d)
- case *ast.C4ComponentDiagram:
- return e.exportC4ToSVG(d)
- case *ast.C4DynamicDiagram:
- return e.exportC4ToSVG(d)
- case *ast.C4DeploymentDiagram:
- return e.exportC4ToSVG(d)
- case *ast.QuadrantChart:
- return e.exportQuadrantToSVG(d)
- default:
- return "", fmt.Errorf("unsupported diagram type for SVG export: %T", diagram)
- }
- }
- // exportPieChartToSVG exports pie chart to SVG (based on mermaid.js pieRenderer.ts)
- func (e *SVGExporter) exportPieChartToSVG(diagram *ast.PieChart) (string, error) {
- // Calculate total value
- total := 0.0
- for _, slice := range diagram.Data {
- total += slice.Value
- }
- if total == 0 {
- return e.createEmptySVG("Empty Pie Chart"), nil
- }
- // Filter out slices < 1%
- var validSlices []*ast.PieSlice
- for _, slice := range diagram.Data {
- if (slice.Value/total)*100 >= 1 {
- validSlices = append(validSlices, slice)
- }
- }
- // Mermaid.js pie chart dimensions
- margin := 40
- legendRectSize := 18
- legendSpacing := 4
- height := 450
- pieWidth := height
- radius := float64(min(pieWidth, height)/2 - margin)
- centerX, centerY := float64(pieWidth/2), float64(height/2)
- // Colors from mermaid.js theme
- colors := []string{
- "#ff6b6b", "#4ecdc4", "#45b7d1", "#96ceb4", "#feca57", "#ff9ff3",
- "#54a0ff", "#5f27cd", "#00d2d3", "#ff9ff3", "#54a0ff", "#5f27cd",
- }
- svg := e.createSVGHeader()
- svg += e.getPieChartStyles()
- // Main group with transform
- svg += fmt.Sprintf(`<g transform="translate(%g,%g)">`, centerX, centerY)
- // Outer circle
- svg += fmt.Sprintf(`<circle cx="0" cy="0" r="%g" class="pieOuterCircle"/>`, radius+1)
- // Generate pie slices
- startAngle := 0.0
- for i, slice := range validSlices {
- angle := (slice.Value / total) * 2 * math.Pi
- color := colors[i%len(colors)]
- // Create arc path
- arcPath := e.createArcPath(0, 0, radius, startAngle, startAngle+angle)
- svg += fmt.Sprintf(`<path d="%s" fill="%s" class="pieCircle"/>`, arcPath, color)
- // Add percentage text
- midAngle := startAngle + angle/2
- textRadius := radius * 0.7 // textPosition from mermaid.js
- textX := textRadius * math.Cos(midAngle)
- textY := textRadius * math.Sin(midAngle)
- percentage := fmt.Sprintf("%.0f%%", (slice.Value/total)*100)
- svg += fmt.Sprintf(`<text x="%g" y="%g" text-anchor="middle" class="slice">%s</text>`,
- textX, textY, percentage)
- startAngle += angle
- }
- svg += "</g>" // Close main group
- // Add title
- if diagram.Title != nil {
- svg += fmt.Sprintf(`<text x="%d" y="25" text-anchor="middle" class="pieTitleText">%s</text>`,
- e.width/2, *diagram.Title)
- }
- // Add legend
- legendX := float64(pieWidth + margin)
- legendY := centerY - float64(len(validSlices)*22)/2
- for i, slice := range validSlices {
- color := colors[i%len(colors)]
- y := legendY + float64(i*22)
- // Legend rectangle
- svg += fmt.Sprintf(`<rect x="%g" y="%g" width="%d" height="%d" fill="%s" stroke="%s"/>`,
- legendX, y-9, legendRectSize, legendRectSize, color, color)
- // Legend text
- labelText := slice.Label
- if diagram.Config != nil {
- if showData, ok := diagram.Config["showData"].(bool); ok && showData {
- labelText = fmt.Sprintf("%s [%.0f]", slice.Label, slice.Value)
- }
- }
- svg += fmt.Sprintf(`<text x="%g" y="%g" class="legend">%s</text>`,
- legendX+float64(legendRectSize+legendSpacing), y+5, labelText)
- }
- // Calculate total width for viewBox
- totalWidth := pieWidth + margin + legendRectSize + legendSpacing + 200 // Approximate legend width
- svg += e.createSVGFooter()
- // Set proper viewBox
- return e.wrapWithViewBox(svg, totalWidth, height), nil
- }
- // exportOrganizationToSVG exports organization chart to SVG
- func (e *SVGExporter) exportOrganizationToSVG(diagram *ast.OrganizationDiagram) (string, error) {
- svg := e.createSVGHeader()
- svg += e.getOrganizationStyles()
- if diagram.Root != nil {
- svg += e.renderOrgNodeSVG(diagram.Root, e.width/2, 80, 0)
- }
- // Add title
- if diagram.Title != nil {
- svg += fmt.Sprintf(`<text x="%d" y="30" text-anchor="middle" class="title">%s</text>`,
- e.width/2, *diagram.Title)
- }
- svg += e.createSVGFooter()
- return e.wrapWithViewBox(svg, e.width, e.height), nil
- }
- // exportFlowchartToSVG exports flowchart to SVG
- func (e *SVGExporter) exportFlowchartToSVG(diagram *ast.Flowchart) (string, error) {
- svg := e.createSVGHeader()
- svg += e.getFlowchartStyles()
- // Simple grid layout
- nodePositions := e.calculateFlowchartLayout(diagram)
- // Render edges first (so they appear behind nodes)
- for _, edge := range diagram.Edges {
- fromPos, fromExists := nodePositions[edge.Start]
- toPos, toExists := nodePositions[edge.End]
- if fromExists && toExists {
- svg += e.renderFlowchartEdgeSVG(edge, fromPos, toPos)
- }
- }
- // Render nodes
- for id, vertex := range diagram.Vertices {
- if pos, exists := nodePositions[id]; exists {
- svg += e.renderFlowchartNodeSVG(vertex, pos)
- }
- }
- svg += e.createSVGFooter()
- return e.wrapWithViewBox(svg, e.width, e.height), nil
- }
- // exportSequenceToSVG exports sequence diagram to SVG
- func (e *SVGExporter) exportSequenceToSVG(diagram *ast.SequenceDiagram) (string, error) {
- svg := e.createSVGHeader()
- svg += e.getSequenceStyles()
- participantWidth := e.width / (len(diagram.Participants) + 1)
- participantY := 60
- // Draw participants
- for i, participant := range diagram.Participants {
- x := participantWidth * (i + 1)
- svg += fmt.Sprintf(`<rect x="%d" y="%d" width="120" height="40" class="participant"/>`,
- x-60, participantY-20)
- svg += fmt.Sprintf(`<text x="%d" y="%d" text-anchor="middle" class="participantText">%s</text>`,
- x, participantY+5, participant.Name)
- // Lifeline
- svg += fmt.Sprintf(`<line x1="%d" y1="%d" x2="%d" y2="%d" class="lifeline"/>`,
- x, participantY+20, x, e.height-50)
- }
- // Draw messages
- messageY := participantY + 60
- for _, message := range diagram.Messages {
- fromX, toX := 0, 0
- for i, p := range diagram.Participants {
- x := participantWidth * (i + 1)
- if p.ID == message.From {
- fromX = x
- }
- if p.ID == message.To {
- toX = x
- }
- }
- // Message arrow
- svg += fmt.Sprintf(`<line x1="%d" y1="%d" x2="%d" y2="%d" class="messageArrow" marker-end="url(#arrowhead)"/>`,
- fromX, messageY, toX, messageY)
- // Message text
- svg += fmt.Sprintf(`<text x="%d" y="%d" text-anchor="middle" class="messageText">%s</text>`,
- (fromX+toX)/2, messageY-5, message.Message)
- messageY += 50
- }
- svg += e.createSVGFooter()
- return e.wrapWithViewBox(svg, e.width, e.height), nil
- }
- // exportGanttToSVG exports Gantt chart to SVG
- func (e *SVGExporter) exportGanttToSVG(diagram *ast.GanttDiagram) (string, error) {
- svg := e.createSVGHeader()
- svg += e.getGanttStyles()
- y := 80
- if diagram.Title != nil {
- svg += fmt.Sprintf(`<text x="%d" y="30" text-anchor="middle" class="title">%s</text>`,
- e.width/2, *diagram.Title)
- }
- // Draw sections and tasks
- for _, section := range diagram.Sections {
- // Section header
- svg += fmt.Sprintf(`<text x="20" y="%d" class="sectionText">%s</text>`, y, section.Name)
- y += 30
- // Tasks
- for _, task := range section.Tasks {
- // Task bar
- barWidth := 200
- svg += fmt.Sprintf(`<rect x="50" y="%d" width="%d" height="20" class="taskBar"/>`,
- y-10, barWidth)
- // Task name
- svg += fmt.Sprintf(`<text x="%d" y="%d" class="taskText">%s</text>`,
- 60+barWidth, y+5, task.Name)
- y += 35
- }
- y += 15
- }
- svg += e.createSVGFooter()
- return e.wrapWithViewBox(svg, e.width, e.height), nil
- }
- // Placeholder implementations for other diagram types
- func (e *SVGExporter) exportTimelineToSVG(diagram *ast.TimelineDiagram) (string, error) {
- return e.createPlaceholderSVG("Timeline", "Timeline SVG export coming soon"), nil
- }
- func (e *SVGExporter) exportJourneyToSVG(diagram *ast.UserJourneyDiagram) (string, error) {
- return e.createPlaceholderSVG("User Journey", "User Journey SVG export coming soon"), nil
- }
- func (e *SVGExporter) exportArchitectureToSVG(diagram *ast.ArchitectureDiagram) (string, error) {
- return e.createPlaceholderSVG("Architecture", "Architecture SVG export coming soon"), nil
- }
- func (e *SVGExporter) exportBPMNToSVG(diagram *ast.BPMNDiagram) (string, error) {
- return e.createPlaceholderSVG("BPMN", "BPMN SVG export coming soon"), nil
- }
- func (e *SVGExporter) exportClassToSVG(diagram *ast.ClassDiagram) (string, error) {
- return e.createPlaceholderSVG("Class Diagram", "Class Diagram SVG export coming soon"), nil
- }
- func (e *SVGExporter) exportStateToSVG(diagram *ast.StateDiagram) (string, error) {
- return e.createPlaceholderSVG("State Diagram", "State Diagram SVG export coming soon"), nil
- }
- func (e *SVGExporter) exportERToSVG(diagram *ast.ERDiagram) (string, error) {
- return e.createPlaceholderSVG("ER Diagram", "ER Diagram SVG export coming soon"), nil
- }
- // Helper methods
- // createSVGHeader creates SVG header with proper namespace and definitions
- func (e *SVGExporter) createSVGHeader() string {
- return fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>
- <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
- <defs>
- %s
- </defs>
- `, e.getCommonDefs())
- }
- // createSVGFooter creates SVG footer
- func (e *SVGExporter) createSVGFooter() string {
- return "</svg>"
- }
- // wrapWithViewBox wraps SVG content with proper viewBox
- func (e *SVGExporter) wrapWithViewBox(content string, width, height int) string {
- // Insert viewBox after the opening svg tag
- viewBox := fmt.Sprintf(` viewBox="0 0 %d %d" width="%d" height="%d"`, width, height, width, height)
- return strings.Replace(content, "<svg xmlns=", "<svg"+viewBox+" xmlns=", 1)
- }
- // createEmptySVG creates an empty SVG with message
- func (e *SVGExporter) createEmptySVG(message string) string {
- svg := e.createSVGHeader()
- svg += fmt.Sprintf(`<text x="%d" y="%d" text-anchor="middle" class="emptyMessage">%s</text>`,
- e.width/2, e.height/2, message)
- svg += e.createSVGFooter()
- return e.wrapWithViewBox(svg, e.width, e.height)
- }
- // createPlaceholderSVG creates a placeholder SVG
- func (e *SVGExporter) createPlaceholderSVG(title, message string) string {
- svg := e.createSVGHeader()
- svg += fmt.Sprintf(`<rect width="100%%" height="100%%" fill="#f8f9fa"/>`)
- svg += fmt.Sprintf(`<text x="%d" y="%d" text-anchor="middle" class="title">%s</text>`,
- e.width/2, e.height/2-20, title)
- svg += fmt.Sprintf(`<text x="%d" y="%d" text-anchor="middle" class="message">%s</text>`,
- e.width/2, e.height/2+20, message)
- svg += e.createSVGFooter()
- return e.wrapWithViewBox(svg, e.width, e.height)
- }
- // getCommonDefs returns common SVG definitions
- func (e *SVGExporter) getCommonDefs() string {
- return `
- <marker id="arrowhead" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
- <polygon points="0 0, 10 3.5, 0 7" fill="#333"/>
- </marker>
- <marker id="arrowheadWhite" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
- <polygon points="0 0, 10 3.5, 0 7" fill="#fff"/>
- </marker>`
- }
- // Style methods based on mermaid.js themes
- func (e *SVGExporter) getPieChartStyles() string {
- return `<style>
- .pieOuterCircle { fill: none; stroke: #333; stroke-width: 2; }
- .pieCircle { stroke: #fff; stroke-width: 2; }
- .slice { font-family: Arial, sans-serif; font-size: 14px; fill: #fff; font-weight: bold; }
- .pieTitleText { font-family: Arial, sans-serif; font-size: 20px; font-weight: bold; fill: #333; }
- .legend { font-family: Arial, sans-serif; font-size: 14px; fill: #333; }
- </style>`
- }
- func (e *SVGExporter) getOrganizationStyles() string {
- return `<style>
- .orgNode { fill: #e3f2fd; stroke: #1976d2; stroke-width: 2; }
- .orgText { font-family: Arial, sans-serif; font-size: 12px; fill: #333; text-anchor: middle; }
- .orgEdge { stroke: #1976d2; stroke-width: 2; }
- .title { font-family: Arial, sans-serif; font-size: 18px; font-weight: bold; fill: #333; }
- </style>`
- }
- func (e *SVGExporter) getFlowchartStyles() string {
- return `<style>
- .flowNode { fill: #fff; stroke: #333; stroke-width: 2; }
- .flowText { font-family: Arial, sans-serif; font-size: 12px; fill: #333; text-anchor: middle; }
- .flowEdge { stroke: #333; stroke-width: 2; fill: none; }
- </style>`
- }
- func (e *SVGExporter) getSequenceStyles() string {
- return `<style>
- .participant { fill: #e3f2fd; stroke: #1976d2; stroke-width: 2; }
- .participantText { font-family: Arial, sans-serif; font-size: 12px; fill: #333; }
- .lifeline { stroke: #ccc; stroke-width: 1; stroke-dasharray: 5,5; }
- .messageArrow { stroke: #333; stroke-width: 2; }
- .messageText { font-family: Arial, sans-serif; font-size: 11px; fill: #333; }
- </style>`
- }
- func (e *SVGExporter) getGanttStyles() string {
- return `<style>
- .taskBar { fill: #4ecdc4; stroke: #26a69a; stroke-width: 1; }
- .taskText { font-family: Arial, sans-serif; font-size: 12px; fill: #333; }
- .sectionText { font-family: Arial, sans-serif; font-size: 14px; font-weight: bold; fill: #333; }
- .title { font-family: Arial, sans-serif; font-size: 18px; font-weight: bold; fill: #333; }
- </style>`
- }
- // Layout and rendering helpers
- type Position struct {
- X, Y int
- }
- func (e *SVGExporter) calculateFlowchartLayout(diagram *ast.Flowchart) map[string]Position {
- positions := make(map[string]Position)
- x, y := 100, 100
- col := 0
- maxCols := 3
- for id := range diagram.Vertices {
- positions[id] = Position{X: x, Y: y}
- col++
- if col >= maxCols {
- col = 0
- x = 100
- y += 120
- } else {
- x += 200
- }
- }
- return positions
- }
- func (e *SVGExporter) renderFlowchartNodeSVG(vertex *ast.FlowVertex, pos Position) string {
- text := vertex.ID
- if vertex.Text != nil {
- text = *vertex.Text
- }
- return fmt.Sprintf(`
- <g transform="translate(%d,%d)">
- <rect x="-50" y="-20" width="100" height="40" class="flowNode"/>
- <text x="0" y="5" class="flowText">%s</text>
- </g>`, pos.X, pos.Y, text)
- }
- func (e *SVGExporter) renderFlowchartEdgeSVG(edge *ast.FlowEdge, from, to Position) string {
- return fmt.Sprintf(`<line x1="%d" y1="%d" x2="%d" y2="%d" class="flowEdge" marker-end="url(#arrowhead)"/>`,
- from.X, from.Y, to.X, to.Y)
- }
- func (e *SVGExporter) renderOrgNodeSVG(node *ast.OrganizationNode, x, y, level int) string {
- svg := fmt.Sprintf(`
- <g transform="translate(%d,%d)">
- <rect x="-80" y="-25" width="160" height="50" class="orgNode"/>
- <text x="0" y="5" class="orgText">%s</text>
- </g>`, x, y, node.Name)
- // Render children
- if len(node.Children) > 0 {
- childY := y + 100
- totalWidth := len(node.Children) * 200
- startX := x - totalWidth/2 + 100
- for i, child := range node.Children {
- childX := startX + i*200
- // Connection line
- svg += fmt.Sprintf(`<line x1="%d" y1="%d" x2="%d" y2="%d" class="orgEdge"/>`,
- x, y+25, childX, childY-25)
- // Recursively render child
- svg += e.renderOrgNodeSVG(child, childX, childY, level+1)
- }
- }
- return svg
- }
- // createArcPath creates SVG arc path for pie slices
- func (e *SVGExporter) createArcPath(centerX, centerY, radius, startAngle, endAngle float64) string {
- x1 := centerX + radius*math.Cos(startAngle)
- y1 := centerY + radius*math.Sin(startAngle)
- x2 := centerX + radius*math.Cos(endAngle)
- y2 := centerY + radius*math.Sin(endAngle)
- largeArc := 0
- if endAngle-startAngle > math.Pi {
- largeArc = 1
- }
- return fmt.Sprintf("M %g %g L %g %g A %g %g 0 %d 1 %g %g Z",
- centerX, centerY, x1, y1, radius, radius, largeArc, x2, y2)
- }
- // min returns the minimum of two integers
- func min(a, b int) int {
- if a < b {
- return a
- }
- return b
- }
- // New diagram type SVG export methods
- // exportMindmapToSVG exports mindmap to SVG
- func (e *SVGExporter) exportMindmapToSVG(diagram *ast.MindmapDiagram) (string, error) {
- svg := fmt.Sprintf(`<svg width="%d" height="%d" xmlns="http://www.w3.org/2000/svg">
- <rect width="100%%" height="100%%" fill="white"/>
- <text x="%d" y="30" font-family="Arial" font-size="16" font-weight="bold" text-anchor="middle">Mindmap Diagram</text>
- <text x="%d" y="50" font-family="Arial" font-size="12" text-anchor="middle">%d nodes</text>
- </svg>`, e.width, e.height, e.width/2, e.width/2, len(diagram.Nodes))
- return svg, nil
- }
- // exportKanbanToSVG exports kanban to SVG
- func (e *SVGExporter) exportKanbanToSVG(diagram *ast.KanbanDiagram) (string, error) {
- svg := fmt.Sprintf(`<svg width="%d" height="%d" xmlns="http://www.w3.org/2000/svg">
- <rect width="100%%" height="100%%" fill="white"/>
- <text x="%d" y="30" font-family="Arial" font-size="16" font-weight="bold" text-anchor="middle">Kanban Board</text>
- <text x="%d" y="50" font-family="Arial" font-size="12" text-anchor="middle">%d columns</text>
- </svg>`, e.width, e.height, e.width/2, e.width/2, len(diagram.Columns))
- return svg, nil
- }
- // exportGitToSVG exports git diagram to SVG
- func (e *SVGExporter) exportGitToSVG(diagram *ast.GitDiagram) (string, error) {
- svg := fmt.Sprintf(`<svg width="%d" height="%d" xmlns="http://www.w3.org/2000/svg">
- <rect width="100%%" height="100%%" fill="white"/>
- <text x="%d" y="30" font-family="Arial" font-size="16" font-weight="bold" text-anchor="middle">Git Graph</text>
- <text x="%d" y="50" font-family="Arial" font-size="12" text-anchor="middle">%d commits</text>
- </svg>`, e.width, e.height, e.width/2, e.width/2, len(diagram.Commits))
- return svg, nil
- }
- // exportSankeyToSVG exports sankey diagram to SVG
- func (e *SVGExporter) exportSankeyToSVG(diagram *ast.SankeyDiagram) (string, error) {
- svg := fmt.Sprintf(`<svg width="%d" height="%d" xmlns="http://www.w3.org/2000/svg">
- <rect width="100%%" height="100%%" fill="white"/>
- <text x="%d" y="30" font-family="Arial" font-size="16" font-weight="bold" text-anchor="middle">Sankey Diagram</text>
- <text x="%d" y="50" font-family="Arial" font-size="12" text-anchor="middle">%d links</text>
- </svg>`, e.width, e.height, e.width/2, e.width/2, len(diagram.Links))
- return svg, nil
- }
- // exportXYChartToSVG exports XY chart to SVG
- func (e *SVGExporter) exportXYChartToSVG(diagram *ast.XYChartDiagram) (string, error) {
- svg := fmt.Sprintf(`<svg width="%d" height="%d" xmlns="http://www.w3.org/2000/svg">
- <rect width="100%%" height="100%%" fill="white"/>
- <text x="%d" y="30" font-family="Arial" font-size="16" font-weight="bold" text-anchor="middle">XY Chart</text>
- <text x="%d" y="50" font-family="Arial" font-size="12" text-anchor="middle">%d series</text>
- </svg>`, e.width, e.height, e.width/2, e.width/2, len(diagram.Series))
- return svg, nil
- }
- // exportRadarToSVG exports radar chart to SVG
- func (e *SVGExporter) exportRadarToSVG(diagram *ast.RadarDiagram) (string, error) {
- svg := fmt.Sprintf(`<svg width="%d" height="%d" xmlns="http://www.w3.org/2000/svg">
- <rect width="100%%" height="100%%" fill="white"/>
- <text x="%d" y="30" font-family="Arial" font-size="16" font-weight="bold" text-anchor="middle">Radar Chart</text>
- <text x="%d" y="50" font-family="Arial" font-size="12" text-anchor="middle">%d series</text>
- </svg>`, e.width, e.height, e.width/2, e.width/2, len(diagram.Series))
- return svg, nil
- }
- // exportTreemapToSVG exports treemap to SVG
- func (e *SVGExporter) exportTreemapToSVG(diagram *ast.TreemapDiagram) (string, error) {
- svg := fmt.Sprintf(`<svg width="%d" height="%d" xmlns="http://www.w3.org/2000/svg">
- <rect width="100%%" height="100%%" fill="white"/>
- <text x="%d" y="30" font-family="Arial" font-size="16" font-weight="bold" text-anchor="middle">Treemap</text>
- <text x="%d" y="50" font-family="Arial" font-size="12" text-anchor="middle">%d nodes</text>
- </svg>`, e.width, e.height, e.width/2, e.width/2, len(diagram.Nodes))
- return svg, nil
- }
- // exportPacketToSVG exports packet diagram to SVG
- func (e *SVGExporter) exportPacketToSVG(diagram *ast.PacketDiagram) (string, error) {
- svg := fmt.Sprintf(`<svg width="%d" height="%d" xmlns="http://www.w3.org/2000/svg">
- <rect width="100%%" height="100%%" fill="white"/>
- <text x="%d" y="30" font-family="Arial" font-size="16" font-weight="bold" text-anchor="middle">Packet Diagram</text>
- <text x="%d" y="50" font-family="Arial" font-size="12" text-anchor="middle">%d flows</text>
- </svg>`, e.width, e.height, e.width/2, e.width/2, len(diagram.Flows))
- return svg, nil
- }
- // exportInfoToSVG exports info diagram to SVG
- func (e *SVGExporter) exportInfoToSVG(diagram *ast.InfoDiagram) (string, error) {
- svg := fmt.Sprintf(`<svg width="%d" height="%d" xmlns="http://www.w3.org/2000/svg">
- <rect width="100%%" height="100%%" fill="white"/>
- <text x="%d" y="30" font-family="Arial" font-size="16" font-weight="bold" text-anchor="middle">Info Diagram</text>
- <text x="%d" y="50" font-family="Arial" font-size="12" text-anchor="middle">%d items</text>
- </svg>`, e.width, e.height, e.width/2, e.width/2, len(diagram.Items))
- return svg, nil
- }
- // exportC4ToSVG exports C4 diagram to SVG
- func (e *SVGExporter) exportC4ToSVG(diagram ast.Diagram) (string, error) {
- // Get element count based on diagram type
- elementCount := 0
- switch d := diagram.(type) {
- case *ast.C4ContextDiagram:
- elementCount = len(d.Elements)
- case *ast.C4ContainerDiagram:
- elementCount = len(d.Elements)
- case *ast.C4ComponentDiagram:
- elementCount = len(d.Elements)
- case *ast.C4DynamicDiagram:
- elementCount = len(d.Elements)
- case *ast.C4DeploymentDiagram:
- elementCount = len(d.Elements)
- }
- svg := fmt.Sprintf(`<svg width="%d" height="%d" xmlns="http://www.w3.org/2000/svg">
- <rect width="100%%" height="100%%" fill="white"/>
- <text x="%d" y="30" font-family="Arial" font-size="16" font-weight="bold" text-anchor="middle">C4 Diagram</text>
- <text x="%d" y="50" font-family="Arial" font-size="12" text-anchor="middle">%d elements</text>
- </svg>`, e.width, e.height, e.width/2, e.width/2, elementCount)
- return svg, nil
- }
- // exportQuadrantToSVG exports quadrant chart to SVG
- func (e *SVGExporter) exportQuadrantToSVG(diagram *ast.QuadrantChart) (string, error) {
- svg := fmt.Sprintf(`<svg width="%d" height="%d" xmlns="http://www.w3.org/2000/svg">
- <rect width="100%%" height="100%%" fill="white"/>
- <text x="%d" y="30" font-family="Arial" font-size="16" font-weight="bold" text-anchor="middle">Quadrant Chart</text>
- <text x="%d" y="50" font-family="Arial" font-size="12" text-anchor="middle">%d points</text>
- </svg>`, e.width, e.height, e.width/2, e.width/2, len(diagram.Points))
- return svg, nil
- }
|