[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]