A basic wrapper for writing SVG files with Plotchart

Difference between version 0 and 1 - Previous - Next
[Arjen Markus] (14 september 2026) After a discussion on the Tclers' chat I started experimenting with SVG files. While there are solutions 
to turn the contents of a canvas widget into an SVG file. I realised that it might be relatively simple to replace the canvas commands inside Plotchart. That way, you can directly create SVG files without Tk. The proof of concept below is far from complete and there are a few tricky bits that will require more work (the clipping in Plotchart is arranged via raise/lower commands), but it does look promising. 

The trick is very simple: do not pass a Tk canvas widget, but an SvgWrapper object to the plot creation command. It intercepts the canvas commands issued by Plotchart and writes SVG objects to a file of your choice. Details that are not taken care of: aligning text, clipping data etc. But it is a start.

Here is the code with a simple test case:

======tcl
# svg_wrapper.tcl --
#     Wrapper for the Tk canvas widget to produce SVG files
#

oo::class create SvgWrapper {
    variable width
    variable height
    variable svgfile

    constructor {args} {
        variable width
        variable height
        variable svgfile

        set width  600
        set height 600
        set filename "output.svg"

        foreach {key value} $args {
            switch -- $key {
                "-width" {
                    set width $value
                }
                "-height" {
                    set height $value
                }
                "-filename" {
                    set filename $value
                }
                default {
                    puts "Unknown option: $key"
                }
            }
        }

        set svgfile [open $filename w]
        puts $svgfile "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 $width $height\" width=\"$width\" height=\"$height\">"
    }

    destructor {
        puts $svgfile "</svg>"
        close $svgfile
    }

    # You need to close the SVG file explicitly
    method close {} {
        puts $svgfile "</svg>"
        close $svgfile
    }

    method create {type args} {
        variable svgfile

        set options [my getOptions $args]

        switch -- $type {
            "line" {
                set coords  [my getCoordinates $args]
                puts $coords

                puts $svgfile "    <polyline points=\"$coords\" stroke=\"[my getColour $args]\"/>"
            }
            "oval" {
                lassign [lrange $args 0 3] x1 y1 x2 y2
                set xc [expr {($x1 + $x2) / 2.0}]
                set yc [expr {($y1 + $y2) / 2.0}]
                set r  [expr {abs($x1 - $x2) / 2.0}]

                puts $svgfile "    <circle cx=\"$xc\" cy=\"$yc\" r=\"$r\" fill=\"none\" stroke=\"[my getColour $args]\"/>"
            }
            "text" {
                set text [my getText $args]
                lassign [lrange $args 0 1] x y

                if { $x != 0 || $y != 0 } {
                    puts $svgfile "    <text x=\"$x\" y=\"$y\">$text</text>"
                }
            }


            default {
                puts "Type not handled: $type"
            }
        }
    }

    # Inspection methods and unimportant subcommands

    method bbox {args} {
        return {0 0 10 10}
    }
    method delete {args} {
        # Ignore
    }
    method cget {type} {
        variable width
        variable height

        switch -- $type {
            "-borderwidth" {
                return 1
            }
            "-width" {
                return $width
            }
            "-height" {
                return $height
            }
        }
    }

    method bind {args} {
        # Ignore
    }
    method raise {args} {
        # Ignore
    }
    method lower {args} {
        # Ignore
    }

    # Auxiliary methods

    method getText {input} {
        foreach {key value} $input {
            if { $key == "-text" } {
                return $value
            }
        }
    }

    method getColour {input} {
        set colour black
        foreach {key value} $input {
            if { $key == "-fill" } {
                set colour $value
            }
        }
        return $colour
    }

    method getOptions {input} {
        set cnt 0
        foreach arg $input {
            if { [regexp -- {-[a-zA-Z]} $arg] } {
                return [lrange $input $cnt end]
            }
            incr cnt
        }
    }

    method getCoordinates {input} {
        set cnt 0
        foreach arg $input {
            if { [regexp -- {-[a-zA-Z]} $arg] } {
                return [lrange $input 0 [expr {$cnt-1}]]
            }
            incr cnt
        }
    }
}

#
# test the wrapper
#
package require Plotchart

# Override the winfo command from Tk
proc winfo {command window args} {
    switch -- $command {
        "pixels" {
            return [lindex $args 0]
        }
    }
}

SvgWrapper create c -width 500 -height 400 -filename example.svg

set p [::Plotchart::createXYPlot c {0 100 20} {0 200 50}]

$p dataconfig data -type both -colour red

$p plot data   0  10
$p plot data  20  10
$p plot data  50 100
$p plot data  70 100
$p plot data  99  10

# Important: close the wrapper object/SVG file!

c close

exit ;# Avoid the Tk default window
======

And here is the picture it produces in a browser:
[Example of SVG plot created by Plotchart]