├── svgdef.pdf ├── svgdef.png ├── am ├── gopher.jpg └── am.go ├── planets ├── earth.png ├── mars.png ├── sun.png ├── venus.png ├── jupiter.png ├── mercury.png ├── neptune.png ├── saturn.png ├── uranus.png └── planets.go ├── animate ├── gopher.jpg └── animate.go ├── gophercolor128x128.png ├── imfade ├── gophercolor128x128.png └── imfade.go ├── tumblrgrid ├── tlist └── tumblrgrid.go ├── go.mod ├── LICENSE-link.txt ├── rpd ├── thing.xml └── rpd.go ├── span └── span.go ├── funnel └── funnel.go ├── newsvg ├── svgplot ├── test.d ├── mksvgplotdef └── svgplot.go ├── richter └── richter.go ├── paths └── paths.go ├── rl └── rl.go ├── pattern └── pattern.go ├── skewabc └── skewabc.go ├── marker └── marker.go ├── vismem └── vismem.go ├── compx ├── comps.xml └── testcomp.xml ├── fontcompare └── fontcompare.go ├── amt └── amt.go ├── webfonts └── webfonts.go ├── randcomp └── randcomp.go ├── bubtrail └── bubtrail.go ├── html5logo └── html5logo.go ├── gradient └── gradient.go ├── android └── android.go ├── fe └── fe.go ├── websvg └── websvg.go ├── turbulence └── turbulence.go ├── svgopher └── svgopher.go ├── flower └── flower.go ├── cube └── cube.go ├── lewitt └── lewitt.go ├── structlayout-svg └── structlayout-svg.go ├── colortab ├── colortab.go └── svgcolors.txt ├── svgrid └── svgrid.go ├── go.sum ├── tsg └── tsg.go ├── float └── doc.go ├── doc.go ├── f50 └── f50.go ├── ltr └── ltr.go ├── shotchart └── shotchart.go ├── personal └── personal.go ├── codepic └── codepic.go ├── rr └── rr.go ├── stockproduct └── stockproduct.go ├── pmap └── pmap.go ├── picserv ├── pic256.html └── index.go ├── benchviz └── benchviz.go ├── bulletgraph └── bulletgraph.go ├── svgplay └── svgplay.go ├── LICENSE └── barchart └── barchart.go /svgdef.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/svgo/HEAD/svgdef.pdf -------------------------------------------------------------------------------- /svgdef.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/svgo/HEAD/svgdef.png -------------------------------------------------------------------------------- /am/gopher.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/svgo/HEAD/am/gopher.jpg -------------------------------------------------------------------------------- /planets/earth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/svgo/HEAD/planets/earth.png -------------------------------------------------------------------------------- /planets/mars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/svgo/HEAD/planets/mars.png -------------------------------------------------------------------------------- /planets/sun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/svgo/HEAD/planets/sun.png -------------------------------------------------------------------------------- /planets/venus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/svgo/HEAD/planets/venus.png -------------------------------------------------------------------------------- /animate/gopher.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/svgo/HEAD/animate/gopher.jpg -------------------------------------------------------------------------------- /planets/jupiter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/svgo/HEAD/planets/jupiter.png -------------------------------------------------------------------------------- /planets/mercury.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/svgo/HEAD/planets/mercury.png -------------------------------------------------------------------------------- /planets/neptune.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/svgo/HEAD/planets/neptune.png -------------------------------------------------------------------------------- /planets/saturn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/svgo/HEAD/planets/saturn.png -------------------------------------------------------------------------------- /planets/uranus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/svgo/HEAD/planets/uranus.png -------------------------------------------------------------------------------- /gophercolor128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/svgo/HEAD/gophercolor128x128.png -------------------------------------------------------------------------------- /imfade/gophercolor128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajstarks/svgo/HEAD/imfade/gophercolor128x128.png -------------------------------------------------------------------------------- /tumblrgrid/tlist: -------------------------------------------------------------------------------- 1 | f0o0od.tumblr.com 2 | ign0ranceisbliiiss.tumblr.com 3 | agilaagira.tumblr.com 4 | geek-art.tumblr.com 5 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ajstarks/svgo 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19 7 | honnef.co/go/tools v0.1.3 8 | ) 9 | -------------------------------------------------------------------------------- /LICENSE-link.txt: -------------------------------------------------------------------------------- 1 | The contents of this repository are Licensed under 2 | the Creative Commons Attribution 4.0 International license as described in 3 | https://creativecommons.org/licenses/by/4.0/legalcode 4 | -------------------------------------------------------------------------------- /rpd/thing.xml: -------------------------------------------------------------------------------- 1 | 2 | This is small 3 | This is medium 4 | This is large 5 | 6 | -------------------------------------------------------------------------------- /span/span.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | svg "github.com/ajstarks/svgo" 7 | ) 8 | 9 | func main() { 10 | width := 500 11 | height := 500 12 | canvas := svg.New(os.Stdout) 13 | canvas.Start(width, height) 14 | canvas.Circle(width/2, height/2, 100) 15 | canvas.Gstyle("text-anchor:middle;font-family:sans;fill:white") 16 | canvas.Textspan(width/2, height/2, "Hello ", "font-size:30px") 17 | canvas.Span("SVG", "font-family:serif;font-size:50px;fill:yellow") 18 | canvas.TextEnd() 19 | canvas.Gend() 20 | canvas.End() 21 | } 22 | -------------------------------------------------------------------------------- /funnel/funnel.go: -------------------------------------------------------------------------------- 1 | // funnel draws a funnel-like shape 2 | // +build !appengine 3 | 4 | package main 5 | 6 | import ( 7 | "os" 8 | 9 | "github.com/ajstarks/svgo" 10 | ) 11 | 12 | var canvas = svg.New(os.Stdout) 13 | var width = 320 14 | var height = 480 15 | 16 | func funnel(bg int, fg int, grid int, dim int) { 17 | h := dim / 2 18 | canvas.Rect(0, 0, width, height, canvas.RGB(bg, bg, bg)) 19 | for size := grid; size < width; size += grid { 20 | canvas.Ellipse(h, size, size/2, size/2, canvas.RGBA(fg, fg, fg, 0.2)) 21 | } 22 | } 23 | 24 | func main() { 25 | canvas.Start(width, height) 26 | canvas.Title("Funnel") 27 | funnel(0, 255, 25, width) 28 | canvas.End() 29 | } 30 | -------------------------------------------------------------------------------- /newsvg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if test $# -lt 1 4 | then 5 | echo "specify a file" 6 | exit 2 7 | fi 8 | 9 | if test ! -f $1 10 | then 11 | cat < $1 12 | package main 13 | 14 | import ( 15 | "github.com/ajstarks/svgo" 16 | "os" 17 | ) 18 | 19 | var ( 20 | width = 500 21 | height = 500 22 | canvas = svg.New(os.Stdout) 23 | ) 24 | 25 | func background(v int) { canvas.Rect(0, 0, width, height, canvas.RGB(v, v, v)) } 26 | 27 | 28 | func main() { 29 | canvas.Start(width, height) 30 | background(255) 31 | 32 | // your code here 33 | 34 | canvas.Grid(0, 0, width, height, 10, "stroke:black;opacity:0.1") 35 | canvas.End() 36 | } 37 | ! 38 | fi 39 | $EDITOR $1 40 | -------------------------------------------------------------------------------- /imfade/imfade.go: -------------------------------------------------------------------------------- 1 | // imfade progressively fades the Go gopher image 2 | // +build !appengine 3 | 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | 10 | "github.com/ajstarks/svgo" 11 | ) 12 | 13 | var canvas = svg.New(os.Stdout) 14 | 15 | func main() { 16 | width := 768 17 | height := 128 18 | image := "gophercolor128x128.png" 19 | if len(os.Args) > 1 { 20 | image = os.Args[1] 21 | } 22 | canvas.Start(width, height) 23 | canvas.Title("Image Fade") 24 | opacity := 1.0 25 | for i := 0; i < width-128; i += 100 { 26 | canvas.Image(i, 0, 128, 128, image, fmt.Sprintf("opacity:%.2f", opacity)) 27 | opacity -= 0.10 28 | } 29 | canvas.Grid(0, 0, width, height, 16, "stroke:gray; opacity:0.2") 30 | 31 | canvas.End() 32 | } 33 | -------------------------------------------------------------------------------- /svgplot/test.d: -------------------------------------------------------------------------------- 1 | 0 493.65 2 | 1 497.57 3 | 2 506.38 4 | 3 521.03 5 | 4 532.44 6 | 5 535.36 7 | 6 546.60 8 | 7 531.99 9 | 8 527.28 10 | 9 534.01 11 | 10 538.26 12 | 11 528.94 13 | 12 597.62 14 | 13 594.94 15 | 14 602.55 16 | 15 595.35 17 | 16 606.99 18 | 17 618.23 19 | 18 618.98 20 | 19 622.52 21 | 20 607.22 22 | 21 610.94 23 | 22 603.69 24 | 23 606.77 25 | 24 592.40 26 | 25 601.17 27 | 26 577.52 28 | 27 579.04 29 | 28 546.02 30 | 29 573.41 31 | 30 549.01 32 | 31 562.13 33 | 32 563.77 34 | 33 557.23 35 | 34 539.00 36 | 35 533.15 37 | 36 504.88 38 | 37 490.92 39 | 38 498.17 40 | 39 518.82 41 | 40 523.29 42 | 41 520.04 43 | 42 526.86 44 | 43 539.08 45 | 44 540.70 46 | 45 540.96 47 | 46 532.50 48 | 47 524.84 49 | 48 522.18 50 | 49 534.03 51 | 50 534.96 52 | -------------------------------------------------------------------------------- /richter/richter.go: -------------------------------------------------------------------------------- 1 | // richter -- inspired by Gerhard Richter's 256 colors, 1974 2 | // +build !appengine 3 | 4 | package main 5 | 6 | import ( 7 | "math/rand" 8 | "os" 9 | "time" 10 | 11 | "github.com/ajstarks/svgo" 12 | ) 13 | 14 | var canvas = svg.New(os.Stdout) 15 | 16 | var width = 700 17 | var height = 400 18 | 19 | func main() { 20 | rand.Seed(int64(time.Now().Nanosecond()) % 1e9) 21 | canvas.Start(width, height) 22 | canvas.Title("Richter") 23 | canvas.Rect(0, 0, width, height, "fill:white") 24 | rw := 32 25 | rh := 18 26 | margin := 5 27 | for i, x := 0, 20; i < 16; i++ { 28 | x += (rw + margin) 29 | for j, y := 0, 20; j < 16; j++ { 30 | canvas.Rect(x, y, rw, rh, canvas.RGB(rand.Intn(255), rand.Intn(255), rand.Intn(255))) 31 | y += (rh + margin) 32 | } 33 | } 34 | canvas.End() 35 | } 36 | -------------------------------------------------------------------------------- /paths/paths.go: -------------------------------------------------------------------------------- 1 | // paths draws the W3C logo as a paths 2 | // +build !appengine 3 | 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | 10 | "github.com/ajstarks/svgo" 11 | ) 12 | 13 | var canvas = svg.New(os.Stdout) 14 | 15 | func w3c() { 16 | w3path := `M36,5l12,41l12-41h33v4l-13,21c30,10,2,69-21,28l7-2c15,27,33,-22,3,-19v-4l12-20h-15l-17,59h-1l-13-42l-12,42h-1l-20-67h9l12,41l8-28l-4-13h9` 17 | cpath := `M94,53c15,32,30,14,35,7l-1-7c-16,26-32,3-34,0M122,16c-10-21-34,0-21,30c-5-30 16,-38 23,-21l5-10l-2-9` 18 | canvas.Path(w3path, "fill:#005A9C") 19 | canvas.Path(cpath) 20 | } 21 | 22 | func main() { 23 | canvas.Startview(700, 200, 0, 0, 700, 200) 24 | canvas.Title("Paths") 25 | for i := 0; i < 5; i++ { 26 | canvas.Gtransform(fmt.Sprintf("translate(%d,0)", i*130)) 27 | w3c() 28 | canvas.Gend() 29 | } 30 | canvas.End() 31 | } 32 | -------------------------------------------------------------------------------- /rl/rl.go: -------------------------------------------------------------------------------- 1 | // rl - draw random lines 2 | // +build !appengine 3 | 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "math/rand" 9 | "os" 10 | "time" 11 | 12 | "github.com/ajstarks/svgo" 13 | ) 14 | 15 | var canvas = svg.New(os.Stdout) 16 | 17 | func main() { 18 | width := 200 19 | height := 200 20 | canvas.Start(width, height) 21 | canvas.Title("Random Lines") 22 | canvas.Rect(0, 0, width, height, "fill:black") 23 | rand.Seed(int64(time.Now().Nanosecond()) % 1e9) 24 | canvas.Gstyle("stroke-width:10") 25 | r := 0 26 | for i := 0; i < width; i++ { 27 | r = rand.Intn(255) 28 | canvas.Line(i, 0, rand.Intn(width), height, fmt.Sprintf("stroke:rgb(%d,%d,%d); opacity:0.39", r, r, r)) 29 | } 30 | canvas.Gend() 31 | 32 | canvas.Text(width/2, height/2, "Random Lines", "fill:white; font-size:20; font-family:Calibri; text-anchor:middle") 33 | canvas.End() 34 | } 35 | -------------------------------------------------------------------------------- /am/am.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | svg "github.com/ajstarks/svgo" 7 | ) 8 | 9 | func main() { 10 | width, height := 600, 600 11 | rsize := 20 12 | csize := rsize / 2 13 | duration := 5.0 14 | repeat := 10 15 | canvas := svg.New(os.Stdout) 16 | canvas.Start(width, height) 17 | canvas.Image(0, 0, 100, 140, "gopher.jpg", `id="gopher"`) 18 | canvas.Arc(0, 250, 10, 10, 0, false, true, 500, 250, `id="top"`, `fill="none"`, `stroke="red"`) 19 | canvas.Arc(0, 250, 10, 10, 0, true, false, 500, 250, `id="bot"`, `fill="none"`, `stroke="blue"`) 20 | canvas.Circle(0, 0, csize, `fill="red"`, `id="red-dot"`) 21 | canvas.Circle(0, 0, csize, `fill="blue"`, `id="blue-dot"`) 22 | canvas.AnimateMotion("#red-dot", "#top", duration, repeat) 23 | canvas.AnimateMotion("#blue-dot", "#bot", duration, repeat) 24 | canvas.AnimateMotion("#gopher", "#top", duration, repeat) 25 | canvas.End() 26 | } 27 | -------------------------------------------------------------------------------- /animate/animate.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | svg "github.com/ajstarks/svgo" 7 | ) 8 | 9 | func main() { 10 | width, height := 500, 500 11 | rsize := 100 12 | csize := rsize / 2 13 | duration := 5.0 14 | repeat := 5 15 | imw, imh := 100, 144 16 | canvas := svg.New(os.Stdout) 17 | canvas.Start(width, height) 18 | canvas.Circle(csize, csize, csize, `fill="red"`, `id="circle"`) 19 | canvas.Image((width/2)-(imw/2), 0, imw, imh, "gopher.jpg", `id="gopher"`) 20 | canvas.Square(width-rsize, 0, rsize, `fill="blue"`, `id="square"`) 21 | canvas.Animate("#circle", "cx", 0, width, duration, repeat) 22 | canvas.Animate("#circle", "cy", 0, height, duration, repeat) 23 | canvas.Animate("#square", "x", width, 0, duration, repeat) 24 | canvas.Animate("#square", "y", height, 0, duration, repeat) 25 | canvas.Animate("#gopher", "y", 0, height, duration, repeat) 26 | canvas.End() 27 | } 28 | -------------------------------------------------------------------------------- /pattern/pattern.go: -------------------------------------------------------------------------------- 1 | // pattern: test the pattern function 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "github.com/ajstarks/svgo" 7 | "os" 8 | ) 9 | 10 | func main() { 11 | canvas := svg.New(os.Stdout) 12 | w, h := 500, 500 13 | pct := 5 14 | pw, ph := (w*pct)/100, (h*pct)/100 15 | canvas.Start(w, h) 16 | 17 | // define the pattern 18 | canvas.Def() 19 | canvas.Pattern("hatch", 0, 0, pw, ph, "user") 20 | canvas.Gstyle("fill:none;stroke-width:1") 21 | canvas.Path(fmt.Sprintf("M0,0 l%d,%d", pw, ph), "stroke:red") 22 | canvas.Path(fmt.Sprintf("M%d,0 l-%d,%d", pw, pw, ph), "stroke:blue") 23 | canvas.Gend() 24 | canvas.PatternEnd() 25 | canvas.DefEnd() 26 | 27 | // use the pattern 28 | canvas.Gstyle("stroke:black; stroke-width:2") 29 | canvas.Circle(w/2, h/2, h/8, "fill:url(#hatch)") 30 | canvas.CenterRect((w*4)/5, h/2, h/4, h/4, "fill:url(#hatch)") 31 | canvas.Gend() 32 | canvas.End() 33 | } 34 | -------------------------------------------------------------------------------- /skewabc/skewabc.go: -------------------------------------------------------------------------------- 1 | // skewabc - exercise the skew functions 2 | // +build !appengine 3 | 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | 10 | "github.com/ajstarks/svgo" 11 | ) 12 | 13 | var ( 14 | g = svg.New(os.Stdout) 15 | width = 500 16 | height = 500 17 | ) 18 | 19 | func sky(x, y, w, h int, a float64, s string) { 20 | g.Gstyle(fmt.Sprintf("font-family:sans-serif;font-size:%dpx;text-anchor:middle", w/2)) 21 | g.SkewY(a) 22 | g.Rect(x, y, w, h, `fill:black; fill-opacity:0.3`) 23 | g.Text(x+w/2, y+h/2, s, `fill:white;baseline-shift:-33%`) 24 | g.Gend() 25 | g.Gend() 26 | } 27 | 28 | func main() { 29 | g.Start(width, height) 30 | g.Title("Skew") 31 | g.Rect(0, 0, width, height, "fill:white") 32 | g.Grid(0, 0, width, height, 50, "stroke:lightblue") 33 | sky(100, 100, 100, 100, 30, "A") 34 | sky(200, 332, 100, 100, -30, "B") 35 | sky(300, -15, 100, 100, 30, "C") 36 | g.End() 37 | } 38 | -------------------------------------------------------------------------------- /marker/marker.go: -------------------------------------------------------------------------------- 1 | // marker test 2 | // +build !appengine 3 | 4 | package main 5 | 6 | import ( 7 | "github.com/ajstarks/svgo" 8 | "os" 9 | ) 10 | 11 | func main() { 12 | canvas := svg.New(os.Stdout) 13 | canvas.Start(500, 500) 14 | canvas.Title("Marker") 15 | 16 | canvas.Def() 17 | canvas.Marker("dot", 5, 5, 8, 8) 18 | canvas.Circle(5, 5, 3, "fill:black") 19 | canvas.MarkerEnd() 20 | 21 | canvas.Marker("box", 5, 5, 8, 8) 22 | canvas.CenterRect(5, 5, 6, 6, "fill:green") 23 | canvas.MarkerEnd() 24 | 25 | canvas.Marker("arrow", 2, 6, 13, 13) 26 | canvas.Path("M2,2 L2,11 L10,6 L2,2", "fill:blue") 27 | canvas.MarkerEnd() 28 | canvas.DefEnd() 29 | 30 | x := []int{100, 250, 100} 31 | y := []int{100, 250, 400} 32 | canvas.Polyline(x, y, 33 | `fill="none"`, 34 | `stroke="red"`, 35 | `marker-start="url(#dot)"`, 36 | `marker-mid="url(#arrow)"`, 37 | `marker-end="url(#box)"`) 38 | canvas.End() 39 | } 40 | -------------------------------------------------------------------------------- /vismem/vismem.go: -------------------------------------------------------------------------------- 1 | // vismem visualizes memory locations 2 | // +build !appengine 3 | 4 | package main 5 | 6 | import ( 7 | "os" 8 | 9 | "github.com/ajstarks/svgo" 10 | ) 11 | 12 | var canvas = svg.New(os.Stdout) 13 | 14 | func main() { 15 | width := 512 16 | height := 512 17 | n := 1024 18 | rowsize := 32 19 | diameter := 16 20 | var value int 21 | var source string 22 | 23 | if len(os.Args) > 1 { 24 | source = os.Args[1] 25 | } else { 26 | source = "/dev/urandom" 27 | } 28 | 29 | f, _ := os.Open(source) 30 | mem := make([]byte, n) 31 | f.Read(mem) 32 | f.Close() 33 | 34 | canvas.Start(width, height) 35 | canvas.Title("Visualize Files") 36 | canvas.Rect(0, 0, width, height, "fill:white") 37 | dx := diameter / 2 38 | dy := diameter / 2 39 | canvas.Gstyle("fill-opacity:1.0") 40 | for i := 0; i < n; i++ { 41 | value = int(mem[i]) 42 | if i%rowsize == 0 && i != 0 { 43 | dx = diameter / 2 44 | dy += diameter 45 | } 46 | canvas.Circle(dx, dy, diameter/2, canvas.RGB(value, value, value)) 47 | dx += diameter 48 | } 49 | canvas.Gend() 50 | canvas.End() 51 | } 52 | -------------------------------------------------------------------------------- /compx/comps.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /svgplot/mksvgplotdef: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | rm f??*.svg 3 | gopts="-bx=50 -by=20 -width=460 -height=130 -pw=400 -ph=90 -barsize=3 -fontsize=11" 4 | data="test.d" 5 | # opt 6 | svgplot $gopts -showbg -connect=f -label=showbg $data > f00.svg 7 | svgplot $gopts -connect -label=connect $data > f01.svg 8 | svgplot $gopts -showbar -connect=f -label=showbar $data > f02.svg 9 | svgplot $gopts -area -connect=f -label=area $data > f03.svg 10 | svgplot $gopts -showdot -connect=f -label=showdot $data > f04.svg 11 | svgplot $gopts -showx -label=showx $data > f05.svg 12 | svgplot $gopts -showy -label=showy $data > f06.svg 13 | svgplot $gopts -showfile -label=showfile $data > f07.svg 14 | # attr 15 | svgplot $gopts -bgcolor=lightsteelblue -label=bgcolor $data > f08.svg 16 | svgplot $gopts -linecolor=red -label=linecolor $data > f09.svg 17 | svgplot $gopts -barcolor=red -showbar -connect=f -label=barcolor $data > f10.svg 18 | svgplot $gopts -areacolor=red -area -connect=f -label=hcolor $data > f11.svg 19 | svgplot $gopts -showdot -dotcolor=red -connect=f -label=dotcolor $data > f12.svg 20 | svgplot $gopts -label=labelcolor -labelcolor=red $data > f13.svg 21 | svgplot $gopts -label=fontsize $data > f14.svg 22 | svgplot $gopts -font=Courier -label="font" $data > f15.svg 23 | svgrid -h 1200 -w 1024 -r=f -x=50 -y=50 -g 10 -c 8 f??.svg > svgplotdef.svg 24 | 25 | -------------------------------------------------------------------------------- /fontcompare/fontcompare.go: -------------------------------------------------------------------------------- 1 | // fontcompare: compare two fonts 2 | // +build !appengine 3 | 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | 10 | "github.com/ajstarks/svgo" 11 | ) 12 | 13 | var ( 14 | canvas = svg.New(os.Stdout) 15 | width = 1000 16 | height = 600 17 | chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789(){}[],.:;-+*/\\&_^%$#@!~`'\"<>" 18 | gstyle = "font-family:%s;font-size:%dpt;text-anchor:middle;fill:%s;fill-opacity:%.2f" 19 | ) 20 | 21 | func letters(top, left int, font, color string, opacity float32) { 22 | rows := 7 23 | cols := 13 24 | glyph := 0 25 | fontsize := 32 26 | spacing := fontsize * 2 27 | x := left 28 | y := top 29 | canvas.Gstyle(fmt.Sprintf(gstyle, font, fontsize, color, opacity)) 30 | for r := 0; r < rows; r++ { 31 | for c := 0; c < cols; c++ { 32 | canvas.Text(x, y, chars[glyph:glyph+1]) 33 | glyph++ 34 | x += spacing 35 | } 36 | x = left 37 | y += spacing 38 | } 39 | canvas.Gend() 40 | } 41 | 42 | func main() { 43 | if len(os.Args) > 2 { 44 | canvas.Start(width, height) 45 | canvas.Rect(0, 0, width, height, "fill:white") 46 | canvas.Text(80, 540, os.Args[1], "font-size:14pt; fill:blue; font-family:"+os.Args[1]) 47 | canvas.Text(80, 560, os.Args[2], "font-size:14pt; fill:red; font-family:"+os.Args[2]) 48 | letters(100, 100, os.Args[1], "blue", 0.5) 49 | letters(100, 100, os.Args[2], "red", 0.5) 50 | canvas.End() 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /amt/amt.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | svg "github.com/ajstarks/svgo" 7 | ) 8 | 9 | func main() { 10 | canvas := svg.New(os.Stdout) 11 | width, height := 500, 500 12 | duration, repeat := 1.0, 0 13 | 14 | canvas.Start(width, height) 15 | canvas.Rect(0, 0, width, height) 16 | 17 | // Translate 18 | canvas.CenterRect(0, 0, 40, 30, "fill:red", `id="redbox"`) 19 | canvas.CenterRect(0, 0, 40, 30, "fill:blue", `id="bluebox"`) 20 | canvas.AnimateTranslate("#redbox", 100, 100, 200, 200, duration, repeat) 21 | canvas.AnimateTranslate("#bluebox", 200, 200, 100, 100, duration, repeat) 22 | 23 | // Scale 24 | canvas.Translate(200, 100) 25 | canvas.CenterRect(0, 0, 40, 30, "fill:green", `id="greenbox"`) 26 | canvas.Gend() 27 | canvas.AnimateScale("#greenbox", 1, 3, duration, repeat) 28 | 29 | // SkewX 30 | canvas.Translate(300, 100) 31 | canvas.CenterRect(0, 0, 40, 30, "fill:purple", `id="purplebox"`) 32 | canvas.Gend() 33 | canvas.AnimateSkewX("#purplebox", 0, 45, duration, repeat) 34 | 35 | // SkewY 36 | canvas.Translate(400, 100) 37 | canvas.CenterRect(0, 0, 40, 30, "fill:lightsteelblue", `id="lsbox"`) 38 | canvas.Gend() 39 | canvas.AnimateSkewY("#lsbox", 0, 45, duration, repeat) 40 | 41 | // Rotate 42 | canvas.Translate(width/4, height/2) 43 | canvas.CenterRect(0, 0, 40, 30, "fill:maroon", `id="rotbox"`) 44 | canvas.Gend() 45 | canvas.AnimateRotate("#rotbox", 0, 75, 75, 360, 75, 75, duration*2, repeat) 46 | 47 | canvas.End() 48 | 49 | } 50 | -------------------------------------------------------------------------------- /webfonts/webfonts.go: -------------------------------------------------------------------------------- 1 | // webfonts demo 2 | // +build !appengine 3 | 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "io/ioutil" 9 | "net/http" 10 | "net/url" 11 | "os" 12 | "strings" 13 | 14 | "github.com/ajstarks/svgo" 15 | ) 16 | 17 | var ( 18 | canvas = svg.New(os.Stdout) 19 | width = 500 20 | height = 1100 21 | fontlist = "Sue Ellen Francisco|Over the Rainbow|Pacifico|Inconsolata|Miltonian|Megrim|Monofett|Permanent Marker|Homemade Apple|Ultra" 22 | ) 23 | 24 | const ( 25 | gwfURI = "http://fonts.googleapis.com/css?family=" 26 | fontfmt = "\n" 27 | gfmt = "fill:white;font-size:36pt;text-anchor:middle" 28 | ) 29 | 30 | func googlefont(f string) []string { 31 | empty := []string{} 32 | r, err := http.Get(gwfURI + url.QueryEscape(f)) 33 | if err != nil { 34 | return empty 35 | } 36 | defer r.Body.Close() 37 | b, rerr := ioutil.ReadAll(r.Body) 38 | if rerr != nil || r.StatusCode != http.StatusOK { 39 | return empty 40 | } 41 | canvas.Def() 42 | fmt.Fprintf(canvas.Writer, fontfmt, b) 43 | canvas.DefEnd() 44 | return strings.Split(fontlist, "|") 45 | } 46 | 47 | func main() { 48 | canvas.Start(width, height) 49 | canvas.Title("Webfonts") 50 | if len(os.Args) > 1 { 51 | fontlist = os.Args[1] 52 | } 53 | fl := googlefont(fontlist) 54 | canvas.Rect(0, 0, width, height) 55 | canvas.Ellipse(width/2, height+50, width/2, height/5, "fill:rgb(44,77,232)") 56 | canvas.Gstyle(gfmt) 57 | for i, f := range fl { 58 | canvas.Text(width/2, (i+1)*100, "Hello, World", "font-family:"+f) 59 | } 60 | canvas.Gend() 61 | canvas.End() 62 | } 63 | -------------------------------------------------------------------------------- /randcomp/randcomp.go: -------------------------------------------------------------------------------- 1 | // randcomp visualizes random number generators 2 | // +build !appengine 3 | 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "math/rand" 9 | "os" 10 | "strconv" 11 | "time" 12 | 13 | "github.com/ajstarks/svgo" 14 | ) 15 | 16 | var canvas = svg.New(os.Stdout) 17 | 18 | func main() { 19 | width := 512 20 | height := 256 21 | var n = 256 22 | var rx, ry int 23 | 24 | if len(os.Args) > 1 { 25 | n, _ = strconv.Atoi(os.Args[1]) 26 | } 27 | 28 | f, _ := os.Open("/dev/urandom") 29 | x := make([]byte, n) 30 | y := make([]byte, n) 31 | f.Read(x) 32 | f.Read(y) 33 | f.Close() 34 | 35 | rand.Seed(int64(time.Now().Nanosecond()) % 1e9) 36 | canvas.Start(600, 400) 37 | canvas.Title("Random Integer Comparison") 38 | canvas.Desc("Comparison of Random integers: the random device & the Go rand package") 39 | canvas.Rect(0, 0, width/2, height, "fill:white; stroke:gray") 40 | canvas.Rect(width/2, 0, width/2, height, "fill:white; stroke:gray") 41 | 42 | canvas.Desc("Left: Go rand package (red), Right: /dev/urandom") 43 | canvas.Gstyle("stroke:none; fill-opacity:0.5") 44 | for i := 0; i < n; i++ { 45 | rx = rand.Intn(255) 46 | ry = rand.Intn(255) 47 | canvas.Circle(rx, ry, 5, canvas.RGB(127, 0, 0)) 48 | canvas.Circle(int(x[i])+255, int(y[i]), 5, "fill:black") 49 | } 50 | canvas.Gend() 51 | 52 | canvas.Desc("Legends") 53 | canvas.Gstyle("text-anchor:middle; font-size:18; font-family:Calibri") 54 | canvas.Text(128, 280, "Go rand package", "") 55 | canvas.Text(384, 280, "/dev/urandom") 56 | canvas.Text(256, 280, fmt.Sprintf("n=%d", n), "font-size:12") 57 | canvas.Gend() 58 | canvas.End() 59 | } 60 | -------------------------------------------------------------------------------- /bubtrail/bubtrail.go: -------------------------------------------------------------------------------- 1 | // bubtrail draws a randmonized trail of bubbles 2 | // +build !appengine 3 | 4 | package main 5 | 6 | import ( 7 | "flag" 8 | "fmt" 9 | "math/rand" 10 | "os" 11 | "time" 12 | 13 | "github.com/ajstarks/svgo" 14 | ) 15 | 16 | var ( 17 | width = 1200 18 | height = 600 19 | opacity = 0.5 20 | size = 40 21 | niter = 200 22 | canvas = svg.New(os.Stdout) 23 | ) 24 | 25 | func init() { 26 | flag.IntVar(&size, "s", 40, "bubble size") 27 | flag.IntVar(&niter, "n", 200, "number of iterations") 28 | flag.Float64Var(&opacity, "o", 0.5, "opacity") 29 | flag.Parse() 30 | rand.Seed(int64(time.Now().Nanosecond()) % 1e9) 31 | } 32 | 33 | func background(v int) { canvas.Rect(0, 0, width, height, canvas.RGB(v, v, v)) } 34 | 35 | func random(howsmall, howbig int) int { 36 | if howsmall >= howbig { 37 | return howsmall 38 | } 39 | return rand.Intn(howbig-howsmall) + howsmall 40 | } 41 | 42 | func main() { 43 | var style string 44 | 45 | canvas.Start(width, height) 46 | canvas.Title("Bubble Trail") 47 | background(200) 48 | canvas.Gstyle(fmt.Sprintf("fill-opacity:%.2f;stroke:none", opacity)) 49 | for i := 0; i < niter; i++ { 50 | x := random(0, width) 51 | y := random(height/3, (height*2)/3) 52 | r := random(0, 10000) 53 | switch { 54 | case r >= 0 && r <= 2500: 55 | style = "fill:rgb(255,255,255)" 56 | case r > 2500 && r <= 5000: 57 | style = "fill:rgb(127,0,0)" 58 | case r > 5000 && r <= 7500: 59 | style = "fill:rgb(127,127,127)" 60 | case r > 7500 && r <= 10000: 61 | style = "fill:rgb(0,0,0)" 62 | } 63 | canvas.Circle(x, y, size, style) 64 | } 65 | canvas.Gend() 66 | canvas.End() 67 | } 68 | -------------------------------------------------------------------------------- /html5logo/html5logo.go: -------------------------------------------------------------------------------- 1 | // html5logo draws the w3c HTML5 logo, with scripting added 2 | // +build !appengine 3 | 4 | package main 5 | 6 | import ( 7 | "github.com/ajstarks/svgo" 8 | "os" 9 | ) 10 | 11 | func main() { 12 | // HTML5 logo data from 13 | // "Understanding and Optimizing Web Graphics", Session 508, 14 | // Dean Jackson, Apple WWDC 2011 15 | // 16 | // Draggable elements via Jeff Schiller's dragsvg Javascript library 17 | 18 | // shield 19 | var sx = []int{71, 30, 481, 440, 255} 20 | var sy = []int{460, 0, 0, 460, 512} 21 | // highlight 22 | var hx = []int{256, 405, 440, 256} 23 | var hy = []int{472, 431, 37, 37} 24 | // "five" 25 | var fx = []int{181, 176, 392, 393, 396, 397, 114, 115, 129, 325, 318, 256, 192, 188, 132, 139, 256, 371, 372, 385, 387, 371} 26 | var fy = []int{208, 150, 150, 138, 109, 94, 94, 109, 265, 265, 338, 355, 338, 293, 293, 382, 414, 382, 372, 223, 208, 208} 27 | 28 | canvas := svg.New(os.Stdout) 29 | width := 512 30 | height := 512 31 | 32 | // begin the document with the onload event, and namespace for dragging 33 | canvas.Start(width, height, `onload="initializeDraggableElements();"`, `xmlns:drag="http://www.codedread.com/dragsvg"`) 34 | canvas.Title("HTML5 Logo") 35 | canvas.Rect(0, 0, width, height) // black background 36 | canvas.Script("application/javascript", "http://www.codedread.com/dragsvg.js") // reference the drag script 37 | canvas.Polygon(sx, sy, `drag:enable="true"`, canvas.RGB(227, 79, 38)) // draggable shield 38 | canvas.Polygon(hx, hy, `drag:enable="true"`, canvas.RGBA(255, 255, 255, 0.3)) // draggable highlight 39 | canvas.Polygon(fx, fy, `drag:enable="true"`, canvas.RGB(219, 219, 219)) // draggable five 40 | canvas.End() 41 | } 42 | -------------------------------------------------------------------------------- /gradient/gradient.go: -------------------------------------------------------------------------------- 1 | // gradient shows sample gradient fills 2 | // +build !appengine 3 | 4 | package main 5 | 6 | import ( 7 | "os" 8 | "strconv" 9 | 10 | svg "github.com/ajstarks/svgo" 11 | ) 12 | 13 | func main() { 14 | width := 500 15 | height := 500 16 | 17 | lg := []svg.Offcolor{ 18 | {Offset: 0, Color: "rgb(255,255,0)", Opacity: 1.0}, 19 | {Offset: 100, Color: "rgb(255,0,0)", Opacity: .5}, 20 | {Offset: 0, Color: "rgb(200,200,200)", Opacity: 0.0}, 21 | {Offset: 100, Color: "rgb(0,0,255)", Opacity: 1.0}} 22 | 23 | rainbow := []svg.Offcolor{ 24 | {Offset: 10, Color: "#00cc00", Opacity: 1}, 25 | {Offset: 30, Color: "#006600", Opacity: 1}, 26 | {Offset: 70, Color: "#cc0000", Opacity: 1}, 27 | {Offset: 90, Color: "#000099", Opacity: 1}} 28 | 29 | rg := []svg.Offcolor{ 30 | {Offset: 1, Color: "powderblue", Opacity: 1}, 31 | {Offset: 10, Color: "lightskyblue", Opacity: 1}, 32 | {Offset: 100, Color: "darkblue", Opacity: 1}} 33 | 34 | g := svg.New(os.Stdout) 35 | g.Start(width, height) 36 | g.Title("Gradients") 37 | g.Rect(0, 0, width, height, "fill:white") 38 | g.Def() 39 | g.LinearGradient("h", 0, 100, 0, 0, lg) 40 | g.LinearGradient("v", 0, 0, 100, 0, lg) 41 | g.LinearGradient("rainbow", 0, 0, 100, 0, rainbow) 42 | g.RadialGradient("rad100", 50, 50, 100, 25, 25, rg) 43 | g.RadialGradient("rad50", 50, 50, 50, 20, 50, rg) 44 | for i := 50; i < 100; i += 10 { 45 | g.RadialGradient("grad"+strconv.Itoa(i), 50, 50, uint8(i), 20, 50, rg) 46 | } 47 | g.DefEnd() 48 | 49 | g.Ellipse(width/2, height/2, 100, 100, "fill:url(#rad100)") 50 | g.Rect(300, 200, 100, 100, "fill:url(#h)") 51 | g.Rect(100, 200, 100, 100, "fill:url(#v)") 52 | g.Roundrect(10, 10, width-20, 50, 10, 10, "fill:url(#rainbow)") 53 | 54 | for i := 50; i < 100; i += 10 { 55 | g.Circle(i*5, 100, 15, "fill:url(#grad"+strconv.Itoa(i)+")") 56 | } 57 | g.End() 58 | } 59 | -------------------------------------------------------------------------------- /android/android.go: -------------------------------------------------------------------------------- 1 | // android draws bugdroid, the Android mascot 2 | // +build !appengine 3 | 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | 10 | "github.com/ajstarks/svgo" 11 | ) 12 | 13 | var ( 14 | width = 500 15 | height = 500 16 | canvas = svg.New(os.Stdout) 17 | ) 18 | 19 | const androidcolor = "rgb(164,198,57)" 20 | 21 | func background(v int) { canvas.Rect(0, 0, width, height, canvas.RGB(v, v, v)) } 22 | 23 | func android(x, y int, fill string, opacity float64) { 24 | var linestyle = []string{`stroke="` + fill + `"`, `stroke-linecap="round"`, `stroke-width="5"`} 25 | globalstyle := fmt.Sprintf("fill:%s;opacity:%.2f", fill, opacity) 26 | canvas.Gstyle(globalstyle) 27 | canvas.Arc(x+30, y+70, 35, 35, 0, false, true, x+130, y+70) // head 28 | canvas.Line(x+60, y+25, x+50, y+10, linestyle[0], linestyle[1], linestyle[2]) // left antenna 29 | canvas.Line(x+100, y+25, x+110, y+10, linestyle[0], linestyle[1], linestyle[2]) // right antenna 30 | canvas.Circle(x+60, y+45, 5, "fill:white") // left eye 31 | canvas.Circle(x+100, y+45, 5, `fill="white"`) // right eye 32 | canvas.Roundrect(x+30, y+75, 100, 90, 10, 10) // body 33 | canvas.Rect(x+30, y+75, 100, 80) 34 | canvas.Roundrect(x+5, y+80, 20, 70, 10, 10) // left arm 35 | canvas.Roundrect(x+135, y+80, 20, 70, 10, 10) // right arm 36 | canvas.Roundrect(x+50, y+150, 20, 50, 10, 10) // left leg 37 | canvas.Roundrect(x+90, y+150, 20, 50, 10, 10) // right leg 38 | canvas.Gend() 39 | } 40 | 41 | func main() { 42 | canvas.Start(width, height) 43 | canvas.Title("Android") 44 | background(255) 45 | 46 | android(100, 100, androidcolor, 1.0) 47 | canvas.Scale(3.0) 48 | android(50, 50, "gray", 0.5) 49 | canvas.Gend() 50 | 51 | canvas.Scale(0.5) 52 | android(100, 100, "red", 1.0) 53 | canvas.Gend() 54 | canvas.End() 55 | } 56 | -------------------------------------------------------------------------------- /fe/fe.go: -------------------------------------------------------------------------------- 1 | // fe: SVG Filter Effect example from http://www.w3.org/TR/SVG/filters.html#AnExample 2 | // +build !appengine 3 | 4 | package main 5 | 6 | 7 | import ( 8 | "github.com/ajstarks/svgo" 9 | "os" 10 | ) 11 | 12 | func main() { 13 | 14 | canvas := svg.New(os.Stdout) 15 | width := 410 16 | height := 120 17 | 18 | canvas.Start(width, height) 19 | canvas.Title(`SVGo Filter Example`) 20 | canvas.Desc(`Combines multiple filter primitives to produce a 3D lighting effect`) 21 | 22 | gfs := svg.Filterspec{In: "SourceAlpha", Result: "blur"} 23 | ofs := svg.Filterspec{In: "blur", Result: "offsetBlur"} 24 | sfs := svg.Filterspec{In: "blur", Result: "specOut"} 25 | cfs1 := svg.Filterspec{In: "specOut", In2: "SourceAlpha", Result: "specOut"} 26 | cfs2 := svg.Filterspec{In: "SourceGraphic", In2: "specOut", Result: "litPaint"} 27 | 28 | // define the filters 29 | canvas.Def() 30 | canvas.Filter("myFilter") 31 | canvas.FeGaussianBlur(gfs, 4, 4) 32 | canvas.FeOffset(ofs, 4, 4) 33 | canvas.FeSpecularLighting(sfs, 5, .75, 20, "#bbbbbb") 34 | canvas.FePointLight(-5000, -10000, 20000) 35 | canvas.FeSpecEnd() 36 | canvas.FeComposite(cfs1, "in", 0, 0, 0, 0) 37 | canvas.FeComposite(cfs2, "arithmetic", 0, 1, 1, 0) 38 | canvas.FeMerge([]string{ofs.Result, cfs2.Result}) 39 | canvas.Fend() 40 | canvas.DefEnd() 41 | 42 | // specify the graphic 43 | canvas.Gid("SVG") 44 | canvas.Path("M50,90 C0,90 0,30 50,30 L150,30 C200,30 200,90 150,90 z", "fill:none;stroke:#D90000;stroke-width:10") 45 | canvas.Path("M60,80 C30,80 30,40 60,40 L140,40 C170,40 170,80 140,80 z", "fill:#D90000") 46 | canvas.Text(52, 76, "SVG", "fill:white;stroke:black;font-size:45;font-family:Verdana") 47 | canvas.Gend() 48 | 49 | canvas.Rect(0, 0, width, height, "stroke:black;fill:white") 50 | canvas.Use(0, 0, "#SVG") // plain graphic 51 | canvas.Use(200, 0, "#SVG", `filter="url(#myFilter)"`) // filter applied 52 | canvas.End() 53 | } 54 | -------------------------------------------------------------------------------- /websvg/websvg.go: -------------------------------------------------------------------------------- 1 | // websvg draws SVG in a web server 2 | // +build !appengine 3 | 4 | package main 5 | 6 | import ( 7 | "flag" 8 | "log" 9 | "net/http" 10 | "strings" 11 | 12 | "github.com/ajstarks/svgo" 13 | ) 14 | 15 | const defaultstyle = "fill:rgb(127,0,0)" 16 | 17 | var port = flag.String("port", ":2003", "http service address") 18 | 19 | func main() { 20 | flag.Parse() 21 | http.Handle("/circle/", http.HandlerFunc(circle)) 22 | http.Handle("/rect/", http.HandlerFunc(rect)) 23 | http.Handle("/arc/", http.HandlerFunc(arc)) 24 | http.Handle("/text/", http.HandlerFunc(text)) 25 | err := http.ListenAndServe(*port, nil) 26 | if err != nil { 27 | log.Println("ListenAndServe:", err) 28 | } 29 | } 30 | 31 | func shapestyle(path string) string { 32 | i := strings.LastIndex(path, "/") + 1 33 | if i > 0 && len(path[i:]) > 0 { 34 | return "fill:" + path[i:] 35 | } 36 | return defaultstyle 37 | } 38 | 39 | func circle(w http.ResponseWriter, req *http.Request) { 40 | w.Header().Set("Content-Type", "image/svg+xml") 41 | s := svg.New(w) 42 | s.Start(500, 500) 43 | s.Title("Circle") 44 | s.Circle(250, 250, 125, shapestyle(req.URL.Path)) 45 | s.End() 46 | } 47 | 48 | func rect(w http.ResponseWriter, req *http.Request) { 49 | w.Header().Set("Content-Type", "image/svg+xml") 50 | s := svg.New(w) 51 | s.Start(500, 500) 52 | s.Title("Rectangle") 53 | s.Rect(250, 250, 100, 200, shapestyle(req.URL.Path)) 54 | s.End() 55 | } 56 | 57 | func arc(w http.ResponseWriter, req *http.Request) { 58 | w.Header().Set("Content-Type", "image/svg+xml") 59 | s := svg.New(w) 60 | s.Start(500, 500) 61 | s.Title("Arc") 62 | s.Arc(250, 250, 100, 100, 0, false, false, 100, 125, shapestyle(req.URL.Path)) 63 | s.End() 64 | } 65 | 66 | func text(w http.ResponseWriter, req *http.Request) { 67 | w.Header().Set("Content-Type", "image/svg+xml") 68 | s := svg.New(w) 69 | s.Start(500, 500) 70 | s.Title("Text") 71 | s.Text(250, 250, "Hello, world", "text-anchor:middle;font-size:32px;"+shapestyle(req.URL.Path)) 72 | s.End() 73 | } 74 | -------------------------------------------------------------------------------- /turbulence/turbulence.go: -------------------------------------------------------------------------------- 1 | // turbulence example from http://www.w3.org/TR/2003/REC-SVG11-20030114/filters.html#feTurbulence 2 | // +build !appengine 3 | 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "github.com/ajstarks/svgo" 9 | "os" 10 | ) 11 | 12 | var ( 13 | canvas = svg.New(os.Stdout) 14 | width = 500 15 | height = 500 16 | ) 17 | 18 | type perlin struct { 19 | id string 20 | ftype string 21 | basefreqx float64 22 | basefreqy float64 23 | octave int 24 | seed int64 25 | tile bool 26 | } 27 | 28 | func (p perlin) defturbulence() { 29 | x := svg.Filterspec{} 30 | canvas.Filter(p.id) 31 | canvas.FeTurbulence(x, p.ftype, p.basefreqx, p.basefreqy, p.octave, p.seed, p.tile) 32 | canvas.Fend() 33 | } 34 | 35 | func (p perlin) frect(x, y, w, h int) { 36 | bot := y + h 37 | canvas.Rect(x, y, w, h, fmt.Sprintf(`filter="url(#%s)"`, p.id)) 38 | canvas.Text(x+w/2, bot+25, fmt.Sprintf("type=%s", p.ftype)) 39 | canvas.Text(x+w/2, bot+40, fmt.Sprintf("baseFrequency=%.2f", p.basefreqx)) 40 | canvas.Text(x+w/2, bot+55, fmt.Sprintf("numOctaves=%d", p.octave)) 41 | } 42 | 43 | func main() { 44 | var t1, t2, t3, t4, t5, t6 perlin 45 | 46 | t1 = perlin{"Turb1", "t", 0.05, 0.05, 2, 0, false} 47 | t2 = perlin{"Turb2", "t", 0.10, 0.10, 2, 0, false} 48 | t3 = perlin{"Turb3", "t", 0.05, 0.05, 8, 0, false} 49 | t4 = perlin{"Turb4", "f", 0.10, 0.10, 4, 0, false} 50 | t5 = perlin{"Turb5", "f", 0.40, 0.40, 4, 0, false} 51 | t6 = perlin{"Turb6", "f", 0.10, 0.10, 1, 0, false} 52 | 53 | canvas.Start(width, height) 54 | canvas.Title("Example of feTurbulence") 55 | canvas.Def() 56 | t1.defturbulence() 57 | t2.defturbulence() 58 | t3.defturbulence() 59 | t4.defturbulence() 60 | t5.defturbulence() 61 | t6.defturbulence() 62 | canvas.DefEnd() 63 | 64 | canvas.Gstyle("font-size:10;font-family:Verdana;text-anchor:middle") 65 | t1.frect(25, 25, 100, 75) 66 | t2.frect(175, 25, 100, 75) 67 | t3.frect(325, 25, 100, 75) 68 | t4.frect(25, 180, 100, 75) 69 | t5.frect(175, 180, 100, 75) 70 | t6.frect(325, 180, 100, 75) 71 | canvas.Gend() 72 | canvas.End() 73 | } 74 | -------------------------------------------------------------------------------- /rpd/rpd.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Imports 4 | import ( 5 | "encoding/xml" 6 | "flag" 7 | "fmt" 8 | "github.com/ajstarks/svgo" 9 | "io" 10 | "os" 11 | ) 12 | 13 | // 14 | // This is small 15 | // This is medium 16 | // This is large 17 | // 18 | 19 | type Thing struct { 20 | Top int `xml:"top,attr"` 21 | Left int `xml:"left,attr"` 22 | Sep int `xml:"sep,attr"` 23 | Item []item `xml:"item"` 24 | } 25 | 26 | type item struct { 27 | Width int `xml:"width,attr"` 28 | Height int `xml:"height,attr"` 29 | Name string `xml:"name,attr"` 30 | Color string `xml:"color,attr"` 31 | Text string `xml:",chardata"` 32 | } 33 | 34 | var ( 35 | width = flag.Int("w", 1024, "width") 36 | height = flag.Int("h", 768, "height") 37 | canvas = svg.New(os.Stdout) 38 | ) 39 | 40 | // Open the file 41 | func dothing(location string) { 42 | f, err := os.Open(location) 43 | if err != nil { 44 | fmt.Fprintf(os.Stderr, "%v\n", err) 45 | return 46 | } 47 | defer f.Close() 48 | readthing(f) 49 | } 50 | 51 | // Read the file, loading the defined structure 52 | func readthing(r io.Reader) { 53 | var t Thing 54 | if err := xml.NewDecoder(r).Decode(&t); err != nil { 55 | fmt.Fprintf(os.Stderr, "Unable to parse components (%v)\n", err) 56 | return 57 | } 58 | drawthing(t) 59 | } 60 | 61 | // use the items of "thing" to make the picture 62 | func drawthing(t Thing) { 63 | x := t.Left 64 | y := t.Top 65 | for _, v := range t.Item { 66 | style := fmt.Sprintf("font-size:%dpx;fill:%s", v.Width/2, v.Color) 67 | canvas.Circle(x, y, v.Height/4, "fill:"+v.Color) 68 | canvas.Text(x+t.Sep, y, v.Name+":"+v.Text+"/"+v.Color, style) 69 | y += v.Height 70 | } 71 | } 72 | 73 | func main() { 74 | flag.Parse() 75 | for _, f := range flag.Args() { 76 | canvas.Start(*width, *height) 77 | dothing(f) 78 | canvas.End() 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /svgopher/svgopher.go: -------------------------------------------------------------------------------- 1 | // svgopher - Go mascot remix 2 | // +build !appengine 3 | 4 | package main 5 | 6 | import ( 7 | "os" 8 | 9 | "github.com/ajstarks/svgo" 10 | ) 11 | 12 | var ( 13 | width = 500 14 | height = 300 15 | canvas = svg.New(os.Stdout) 16 | ) 17 | 18 | func background(v int) { canvas.Rect(0, 0, width, height, canvas.RGB(v, v, v)) } 19 | 20 | func gordon(x, y, w, h int) { 21 | 22 | w10 := w / 10 23 | w12 := w / 12 24 | w2 := w / 2 25 | w3 := w / 3 26 | w8 := w / 8 27 | w6 := w / 6 28 | xw := x + w 29 | h23 := (h * 2) / 3 30 | 31 | blf := "fill:black" 32 | wf := "fill:white" 33 | nf := "fill:brown" 34 | brf := "fill:brown; fill-opacity:0.2" 35 | brb := "fill:rgb(210,161,161)" 36 | 37 | canvas.Gstyle("fill:none; stroke:none") 38 | canvas.Bezier(x, y+h, x, y+h, x+w2, y-h, x+w, y+h, brb) 39 | canvas.Roundrect(x, y+(h-1), w, h, 10, 10, brb) 40 | canvas.Circle(x, y+h, w12, brf) // left ear 41 | canvas.Circle(x, y+h, w12-10, nf) 42 | 43 | canvas.Circle(x+w, y+h, w12, brf) // right ear 44 | canvas.Circle(x+w, y+h, w12-10, nf) 45 | 46 | canvas.Circle(x+w3, y+h23, w/9, wf) // left eye 47 | canvas.Circle(x+w3+10, y+h23, w10-10, blf) 48 | canvas.Circle(x+w3+15, y+h23, 5, wf) 49 | 50 | canvas.Circle(xw-w3, y+h23, w/9, wf) // right eye 51 | canvas.Circle(xw-w3+10, y+h23, w10-10, blf) 52 | canvas.Circle(xw-(w3)+15, y+h23, 5, wf) 53 | 54 | canvas.Roundrect(x+w2-w8, y+h+30, w8, w6, 5, 5, wf) // left tooth 55 | canvas.Roundrect(x+w2, y+h+30, w8, w6, 5, 5, wf) // right tooth 56 | 57 | canvas.Ellipse(x+(w2), y+h+30, w6, w12, nf) // snout 58 | canvas.Ellipse(x+(w2), y+h+10, w10, w12, blf) // nose 59 | 60 | canvas.Gend() 61 | } 62 | 63 | func main() { 64 | canvas.Start(width, height) 65 | canvas.Title("SVG Gopher") 66 | background(255) 67 | canvas.Gtransform("translate(100, 100)") 68 | canvas.Gtransform("rotate(-30)") 69 | gordon(48, 48, 240, 72) 70 | canvas.Gend() 71 | canvas.Gend() 72 | canvas.Link("svgdef.svg", "SVG Spec & Usage") 73 | canvas.Text(90, 145, "SVG", "font-family:Calibri,sans-serif;font-size:80;fill:brown") 74 | canvas.LinkEnd() 75 | canvas.End() 76 | } 77 | -------------------------------------------------------------------------------- /flower/flower.go: -------------------------------------------------------------------------------- 1 | // flower - draw random flowers, inspired by Evelyn Eastmond's DesignBlocks gererated "grain2" 2 | // +build !appengine 3 | 4 | package main 5 | 6 | import ( 7 | "flag" 8 | "fmt" 9 | "math" 10 | "math/rand" 11 | "os" 12 | "time" 13 | 14 | "github.com/ajstarks/svgo" 15 | ) 16 | 17 | var ( 18 | canvas = svg.New(os.Stdout) 19 | niter = flag.Int("n", 200, "number of iterations") 20 | width = flag.Int("w", 500, "width") 21 | height = flag.Int("h", 500, "height") 22 | thickness = flag.Int("t", 10, "max petal thinkness") 23 | np = flag.Int("p", 15, "max number of petals") 24 | psize = flag.Int("s", 30, "max length of petals") 25 | opacity = flag.Int("o", 50, "max opacity (10-100)") 26 | ) 27 | 28 | const flowerfmt = `stroke:rgb(%d,%d,%d); stroke-opacity:%.2f; stroke-width:%d` 29 | 30 | func radial(xp int, yp int, n int, l int, style ...string) { 31 | var x, y, r, t, limit float64 32 | limit = 2.0 * math.Pi 33 | r = float64(l) 34 | canvas.Gstyle(style[0]) 35 | for t = 0.0; t < limit; t += limit / float64(n) { 36 | x = r * math.Cos(t) 37 | y = r * math.Sin(t) 38 | canvas.Line(xp, yp, xp+int(x), yp+int(y)) 39 | } 40 | canvas.Gend() 41 | } 42 | 43 | func random(howsmall, howbig int) int { 44 | if howsmall >= howbig { 45 | return howsmall 46 | } 47 | return rand.Intn(howbig-howsmall) + howsmall 48 | } 49 | 50 | func randrad(w int, h int, n int) { 51 | var x, y, r, g, b, o, s, t, p int 52 | for i := 0; i < n; i++ { 53 | x = rand.Intn(w) 54 | y = rand.Intn(h) 55 | r = rand.Intn(255) 56 | g = rand.Intn(255) 57 | b = rand.Intn(255) 58 | o = random(10, *opacity) 59 | s = random(10, *psize) 60 | t = random(2, *thickness) 61 | p = random(10, *np) 62 | radial(x, y, p, s, fmt.Sprintf(flowerfmt, r, g, b, float64(o)/100.0, t)) 63 | } 64 | } 65 | 66 | func background(v int) { canvas.Rect(0, 0, *width, *height, canvas.RGB(v, v, v)) } 67 | 68 | func init() { 69 | flag.Parse() 70 | rand.Seed(int64(time.Now().Nanosecond()) % 1e9) 71 | } 72 | 73 | func main() { 74 | canvas.Start(*width, *height) 75 | canvas.Title("Random Flowers") 76 | background(255) 77 | randrad(*width, *height, *niter) 78 | canvas.End() 79 | } 80 | -------------------------------------------------------------------------------- /cube/cube.go: -------------------------------------------------------------------------------- 1 | // cube: draw cubes 2 | package main 3 | 4 | import ( 5 | "crypto/rand" 6 | "flag" 7 | "fmt" 8 | "os" 9 | 10 | "github.com/ajstarks/svgo" 11 | ) 12 | 13 | var canvas = svg.New(os.Stdout) 14 | 15 | // randcolor returns a random color 16 | func randcolor() string { 17 | rgb := []byte{0, 0, 0} // read error returns black 18 | rand.Read(rgb) 19 | return fmt.Sprintf("fill:rgb(%d,%d,%d)", rgb[0], rgb[1], rgb[2]) 20 | } 21 | 22 | // rcube makes a cube with three visible faces, each with a random color 23 | func rcube(x, y, l int) { 24 | tx := []int{x, x + (l * 3), x, x - (l * 3), x} 25 | ty := []int{y, y + (l * 2), y + (l * 4), y + (l * 2), y} 26 | 27 | lx := []int{x - (l * 3), x, x, x - (l * 3), x - (l * 3)} 28 | ly := []int{y + (l * 2), y + (l * 4), y + (l * 8), y + (l * 6), y + (l * 2)} 29 | 30 | rx := []int{x + (l * 3), x + (l * 3), x, x, x + (l * 3)} 31 | ry := []int{y + (l * 2), y + (l * 6), y + (l * 8), y + (l * 4), y + (l * 2)} 32 | 33 | canvas.Polygon(tx, ty, randcolor()) 34 | canvas.Polygon(lx, ly, randcolor()) 35 | canvas.Polygon(rx, ry, randcolor()) 36 | } 37 | 38 | // lattice draws a grid of cubes, n rows deep. 39 | // The grid begins at (xp, yp), with hspace between cubes in a row, and vspace between rows. 40 | func lattice(xp, yp, w, h, size, hspace, vspace, n int, bgcolor string) { 41 | if bgcolor == "" { 42 | canvas.Rect(0, 0, w, h, randcolor()) 43 | } else { 44 | canvas.Rect(0, 0, w, h, "fill:"+bgcolor) 45 | } 46 | y := yp 47 | for r := 0; r < n; r++ { 48 | for x := xp; x < w; x += hspace { 49 | rcube(x, y, size) 50 | } 51 | y += vspace 52 | } 53 | } 54 | 55 | func main() { 56 | var ( 57 | width = flag.Int("w", 600, "canvas width") 58 | height = flag.Int("h", 600, "canvas height") 59 | x = flag.Int("x", 60, "begin x location") 60 | y = flag.Int("y", 60, "begin y location") 61 | size = flag.Int("size", 20, "cube size") 62 | rows = flag.Int("rows", 3, "rows") 63 | hs = flag.Int("hs", 120, "horizontal spacing") 64 | vs = flag.Int("vs", 160, "vertical spacing") 65 | bg = flag.String("bg", "", "background") 66 | ) 67 | flag.Parse() 68 | canvas.Start(*width, *height) 69 | lattice(*x, *y, *width, *height, *size, *hs, *vs, *rows, *bg) 70 | canvas.End() 71 | } 72 | -------------------------------------------------------------------------------- /lewitt/lewitt.go: -------------------------------------------------------------------------------- 1 | // lewitt: inspired by by Sol LeWitt's Wall Drawing 91: 2 | // +build !appengine 3 | 4 | package main 5 | 6 | // 7 | // A six-inch (15 cm) grid covering the wall. 8 | // Within each square, not straight lines from side to side, using 9 | // red, yellow and blue pencils. Each square contains at least 10 | // one line of each color. 11 | // 12 | // This version violates the original instructions in that straight lines 13 | // as well as arcs are used 14 | 15 | import ( 16 | "flag" 17 | "fmt" 18 | "math/rand" 19 | "os" 20 | "time" 21 | 22 | "github.com/ajstarks/svgo" 23 | ) 24 | 25 | var canvas = svg.New(os.Stdout) 26 | 27 | const tilestyle = `stroke-width:1; stroke:rgb(128,128,128); stroke-opacity:0.5; fill:white` 28 | const penstyle = `stroke:rgb%s; fill:none; stroke-opacity:%.2f; stroke-width:%d` 29 | 30 | var width = 720 31 | var height = 720 32 | 33 | var nlines = flag.Int("n", 20, "number of lines/square") 34 | var nw = flag.Int("w", 3, "maximum pencil width") 35 | var pencils = []string{"(250, 13, 44)", "(247, 212, 70)", "(52, 114, 245)"} 36 | 37 | func background(v int) { canvas.Rect(0, 0, width, height, canvas.RGB(v, v, v)) } 38 | 39 | func lewitt(x int, y int, gsize int, n int, w int) { 40 | var x1, x2, y1, y2 int 41 | var op float64 42 | canvas.Rect(x, y, gsize, gsize, tilestyle) 43 | for i := 0; i < n; i++ { 44 | choice := rand.Intn(len(pencils)) 45 | op = float64(random(1, 10)) / 10.0 46 | x1 = random(x, x+gsize) 47 | y1 = random(y, y+gsize) 48 | x2 = random(x, x+gsize) 49 | y2 = random(y, y+gsize) 50 | if random(0, 100) > 50 { 51 | canvas.Line(x1, y1, x2, y2, fmt.Sprintf(penstyle, pencils[choice], op, random(1, w))) 52 | } else { 53 | canvas.Arc(x1, y1, gsize, gsize, 0, false, true, x2, y2, fmt.Sprintf(penstyle, pencils[choice], op, random(1, w))) 54 | } 55 | } 56 | } 57 | 58 | func random(howsmall, howbig int) int { 59 | if howsmall >= howbig { 60 | return howsmall 61 | } 62 | return rand.Intn(howbig-howsmall) + howsmall 63 | } 64 | 65 | func init() { 66 | flag.Parse() 67 | rand.Seed(int64(time.Now().Nanosecond()) % 1e9) 68 | } 69 | 70 | func main() { 71 | 72 | canvas.Start(width, height) 73 | canvas.Title("Sol Lewitt's Wall Drawing 91") 74 | background(255) 75 | gsize := 120 76 | nc := width / gsize 77 | nr := height / gsize 78 | for cols := 0; cols < nc; cols++ { 79 | for rows := 0; rows < nr; rows++ { 80 | lewitt(cols*gsize, rows*gsize, gsize, *nlines, *nw) 81 | } 82 | } 83 | canvas.End() 84 | } 85 | -------------------------------------------------------------------------------- /structlayout-svg/structlayout-svg.go: -------------------------------------------------------------------------------- 1 | // structlayout-svg: generate SVG struct layouts 2 | package main 3 | 4 | import ( 5 | "encoding/json" 6 | "flag" 7 | "fmt" 8 | "log" 9 | "os" 10 | 11 | "github.com/ajstarks/svgo" 12 | "honnef.co/go/tools/structlayout" 13 | ) 14 | 15 | var title, bgcolor, scolor, pcolor string 16 | 17 | func init() { 18 | flag.StringVar(&title, "t", "Structure Layout", "title") 19 | flag.StringVar(&bgcolor, "bgcolor", "rgb(240,240,240)", "background color") 20 | flag.StringVar(&scolor, "scolor", "steelblue", "structure color") 21 | flag.StringVar(&pcolor, "pcolor", "maroon", "padding color") 22 | } 23 | 24 | func main() { 25 | log.SetFlags(0) 26 | flag.Parse() 27 | 28 | // Decode the JSON 29 | var fields []structlayout.Field 30 | if err := json.NewDecoder(os.Stdin).Decode(&fields); err != nil { 31 | log.Fatal(err) 32 | } 33 | if len(fields) == 0 { 34 | return 35 | } 36 | 37 | // layout variables 38 | top := 50 39 | structheight := 50 40 | structwidth := 100 41 | gutter := 8 42 | byteheight := 10 43 | fontsize := byteheight + byteheight/2 44 | width := 600 45 | 46 | // Determine the height of the canvas 47 | height := top + gutter 48 | for _, f := range fields { 49 | height += byteheight * int(f.Size) 50 | height += gutter 51 | } 52 | x := width / 10 53 | y := top 54 | 55 | // Set up the canvas 56 | canvas := svg.New(os.Stdout) 57 | canvas.Start(width, height) 58 | canvas.Rect(0, 0, width, height, "fill:"+bgcolor) 59 | canvas.Gstyle(fmt.Sprintf("font-size:%dpx;font-family:sans-serif", fontsize)) 60 | canvas.Text(x+structwidth/2, top/2, title, "text-anchor:middle;font-size:120%") 61 | 62 | // For every field, draw a labled box 63 | pos := int64(0) 64 | var fillcolor string 65 | nudge := (fontsize * 2) / 3 66 | for _, f := range fields { 67 | name := f.Name + " " + f.Type 68 | if f.IsPadding { 69 | name = "padding" 70 | fillcolor = pcolor 71 | } else { 72 | fillcolor = scolor 73 | } 74 | structheight = byteheight * int(f.Size) 75 | canvas.Rect(x, y, structwidth, structheight, "fill:"+fillcolor) 76 | canvas.Text(x+structwidth+10, y+nudge, fmt.Sprintf("%d", pos)) 77 | canvas.Text(x+structwidth+fontsize*4, y+nudge, fmt.Sprintf("%s (size %d, align %d)", name, f.Size, f.Align)) 78 | 79 | if f.Size > 2 { 80 | canvas.Text(x-10, y+structheight, fmt.Sprintf("%d", pos+f.Size-1), "text-anchor:end") 81 | } 82 | pos += f.Size 83 | y += structheight + gutter 84 | } 85 | canvas.Gend() 86 | canvas.End() 87 | } 88 | -------------------------------------------------------------------------------- /colortab/colortab.go: -------------------------------------------------------------------------------- 1 | // colortab -- make a color/code placemat 2 | // +build !appengine 3 | 4 | package main 5 | 6 | import ( 7 | "bufio" 8 | "flag" 9 | "fmt" 10 | "os" 11 | "strings" 12 | 13 | "github.com/ajstarks/svgo" 14 | ) 15 | 16 | func main() { 17 | var ( 18 | canvas = svg.New(os.Stdout) 19 | filename = flag.String("f", "svgcolors.txt", "input file") 20 | fontname = flag.String("font", "Calibri,sans-serif", "fontname") 21 | outline = flag.Bool("o", false, "outline") 22 | neg = flag.Bool("n", false, "negative") 23 | showrgb = flag.Bool("rgb", false, "show RGB") 24 | showcode = flag.Bool("showcode", true, "only show colors") 25 | circsw = flag.Bool("circle", true, "circle swatch") 26 | fontsize = flag.Int("fs", 12, "fontsize") 27 | width = flag.Int("w", 1600, "width") 28 | height = flag.Int("h", 900, "height") 29 | rowsize = flag.Int("r", 32, "rowsize") 30 | colw = flag.Int("c", 320, "column size") 31 | swatch = flag.Int("s", 16, "swatch size") 32 | gutter = flag.Int("g", 11, "gutter") 33 | err error 34 | colorfmt, tcolor, line string 35 | ) 36 | 37 | flag.Parse() 38 | f, oerr := os.Open(*filename) 39 | if oerr != nil { 40 | fmt.Fprintf(os.Stderr, "%v\n", oerr) 41 | return 42 | } 43 | canvas.Start(*width, *height) 44 | canvas.Title("SVG Color Table") 45 | if *neg { 46 | canvas.Rect(0, 0, *width, *height, "fill:black") 47 | tcolor = "white" 48 | } else { 49 | canvas.Rect(0, 0, *width, *height, "fill:white") 50 | tcolor = "black" 51 | } 52 | top := 32 53 | left := 32 54 | in := bufio.NewReader(f) 55 | canvas.Gstyle(fmt.Sprintf("font-family:%s;font-size:%dpt;fill:%s", 56 | *fontname, *fontsize, tcolor)) 57 | for x, y, nr := left, top, 0; err == nil; nr++ { 58 | line, err = in.ReadString('\n') 59 | fields := strings.Split(strings.TrimSpace(line), "\t") 60 | if nr%*rowsize == 0 && nr > 0 { 61 | x += *colw 62 | y = top 63 | } 64 | if len(fields) == 3 { 65 | colorfmt = "fill:" + fields[1] 66 | if *outline { 67 | colorfmt = colorfmt + ";stroke-width:1;stroke:" + tcolor 68 | } 69 | if *circsw { 70 | canvas.Circle(x, y, *swatch/2, colorfmt) 71 | } else { 72 | canvas.CenterRect(x, y, *swatch, *swatch, colorfmt) 73 | } 74 | canvas.Text(x+*swatch+*fontsize/2, y+(*swatch/4), fields[0], "stroke:none") 75 | var label string 76 | if *showcode { 77 | if *showrgb { 78 | label = fields[1] 79 | } else { 80 | label = fields[2] 81 | } 82 | canvas.Text(x+((*colw*4)/5), y+(*swatch/4), label, "text-anchor:end;fill:gray") 83 | } 84 | } 85 | y += (*swatch + *gutter) 86 | } 87 | canvas.Gend() 88 | canvas.End() 89 | } 90 | -------------------------------------------------------------------------------- /svgrid/svgrid.go: -------------------------------------------------------------------------------- 1 | // svgrid -- composite SVG files in a grid 2 | // +build !appengine 3 | 4 | package main 5 | 6 | import ( 7 | "encoding/xml" 8 | "flag" 9 | "fmt" 10 | "io" 11 | "os" 12 | 13 | "github.com/ajstarks/svgo" 14 | ) 15 | 16 | // SVG is a SVG document 17 | type SVG struct { 18 | Width string `xml:"width,attr"` 19 | Height string `xml:"height,attr"` 20 | Doc string `xml:",innerxml"` 21 | } 22 | 23 | var ( 24 | byrow bool 25 | startx, starty, count, gutter, gwidth, gheight int 26 | canvas = svg.New(os.Stdout) 27 | ) 28 | 29 | // init sets up command line options 30 | func init() { 31 | flag.BoolVar(&byrow, "r", true, "order row wise") 32 | flag.IntVar(&startx, "x", 0, "begin x") 33 | flag.IntVar(&starty, "y", 0, "begin y") 34 | flag.IntVar(&count, "c", 3, "columns or rows") 35 | flag.IntVar(&gutter, "g", 100, "gutter") 36 | flag.IntVar(&gwidth, "w", 1024, "width") 37 | flag.IntVar(&gheight, "h", 768, "height") 38 | flag.Parse() 39 | } 40 | 41 | // placepic puts a SVG file at a location 42 | func placepic(x, y int, filename string) (int, int) { 43 | var ( 44 | s SVG 45 | width, height int 46 | wunit, hunit string 47 | ) 48 | f, err := os.Open(filename) 49 | if err != nil { 50 | fmt.Fprintf(os.Stderr, "%v\n", err) 51 | return 0, 0 52 | } 53 | defer f.Close() 54 | if err := xml.NewDecoder(f).Decode(&s); err != nil { 55 | fmt.Fprintf(os.Stderr, "Unable to parse (%v)\n", err) 56 | return 0, 0 57 | } 58 | 59 | // read the width and height, including any units 60 | // if there are errors use 10 as a default 61 | nw, _ := fmt.Sscanf(s.Width, "%d%s", &width, &wunit) 62 | if nw < 1 { 63 | width = 10 64 | } 65 | nh, _ := fmt.Sscanf(s.Height, "%d%s", &height, &hunit) 66 | if nh < 1 { 67 | height = 10 68 | } 69 | canvas.Group(`clip-path="url(#pic)"`, fmt.Sprintf(`transform="translate(%d,%d)"`, x, y)) 70 | canvas.ClipPath(`id="pic"`) 71 | canvas.Rect(0, 0, width, height) 72 | canvas.ClipEnd() 73 | io.WriteString(canvas.Writer, s.Doc) 74 | canvas.Gend() 75 | return width, height 76 | } 77 | 78 | // compose places files row or column-wise 79 | func compose(x, y, n int, rflag bool, files []string) { 80 | px := x 81 | py := y 82 | var pw, ph int 83 | for i, f := range files { 84 | if i > 0 && i%n == 0 { 85 | if rflag { 86 | px = x 87 | py += gutter + ph 88 | } else { 89 | px += gutter + pw 90 | py = y 91 | } 92 | } 93 | pw, ph = placepic(px, py, f) 94 | if rflag { 95 | px += gutter + pw 96 | } else { 97 | py += gutter + ph 98 | } 99 | } 100 | } 101 | 102 | // main lays out files as specified on the command line 103 | func main() { 104 | canvas.Start(gwidth, gheight) 105 | compose(startx, starty, count, byrow, flag.Args()) 106 | canvas.End() 107 | } 108 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9 h1:7kQgkwGRoLzC9K0oyXdJo7nve/bynv/KwUsxbiTlzAM= 3 | github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= 4 | github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19 h1:iXUgAaqDcIUGbRoy2TdeofRG/j1zpGRSEmNK05T+bi8= 5 | github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= 6 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 7 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 8 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 9 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 10 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 11 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 12 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 13 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 14 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 15 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 16 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 17 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 18 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 19 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 20 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 21 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 22 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 23 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 24 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 25 | golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= 26 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 27 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 28 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 29 | honnef.co/go/tools v0.1.3 h1:qTakTkI6ni6LFD5sBwwsdSO+AQqbSIxOauHTTQKZ/7o= 30 | honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= 31 | -------------------------------------------------------------------------------- /tsg/tsg.go: -------------------------------------------------------------------------------- 1 | // tsg -- twitter search grid 2 | // +build !appengine 3 | 4 | package main 5 | 6 | import ( 7 | "encoding/xml" 8 | "flag" 9 | "fmt" 10 | "io" 11 | "net/http" 12 | "net/url" 13 | "os" 14 | 15 | "github.com/ajstarks/svgo" 16 | ) 17 | 18 | var canvas = svg.New(os.Stdout) 19 | 20 | // Feed is the Atom feed structure 21 | type Feed struct { 22 | XMLName xml.Name `xml:"http://www.w3.org/2005/Atom feed"` 23 | Entry []Entry `xml:"entry"` 24 | } 25 | 26 | // Entry defines an entry within an Aton feed 27 | type Entry struct { 28 | Link []Link `xml:"link"` 29 | Title string `xml:"title"` 30 | Author Person `xml:"author"` 31 | } 32 | 33 | // Link defines a link within an Atom feed 34 | type Link struct { 35 | Rel string `xml:"rel,attr"` 36 | Href string `xml:"href,attr"` 37 | } 38 | 39 | // Person defines a person responsible for the tweet 40 | type Person struct { 41 | Name string `xml:"name"` 42 | } 43 | 44 | // Text defines the text of the tweet 45 | type Text struct { 46 | Type string `xml:",attr"` 47 | Body string `xml:",chardata"` 48 | } 49 | 50 | var ( 51 | nresults = flag.Int("n", 100, "Maximum results (up to 100)") 52 | since = flag.String("d", "", "Search since this date (YYYY-MM-DD)") 53 | ) 54 | 55 | const ( 56 | queryURI = "http://search.twitter.com/search.atom?q=%s&since=%s&rpp=%d" 57 | textfmt = "font-family:Calibri,Lucida,sans;fill:gray;text-anchor:middle;font-size:48px" 58 | imw = 48 59 | imh = 48 60 | ) 61 | 62 | // ts dereferences the twitter search URL and reads the XML (Atom) response 63 | func ts(s string, date string, n int) { 64 | 65 | r, err := http.Get(fmt.Sprintf(queryURI, url.QueryEscape(s), date, n)) 66 | if err != nil { 67 | fmt.Fprintf(os.Stderr, "%v\n", err) 68 | return 69 | } 70 | if r.StatusCode == http.StatusOK { 71 | readatom(r.Body) 72 | } else { 73 | fmt.Fprintf(os.Stderr, 74 | "Twitter is unable to search for %s (%s)\n", s, r.Status) 75 | } 76 | r.Body.Close() 77 | } 78 | 79 | // readatom unmarshals the twitter search response and formats the results into a grid 80 | func readatom(r io.Reader) { 81 | var twitter Feed 82 | err := xml.NewDecoder(r).Decode(&twitter) 83 | if err == nil { 84 | tgrid(twitter, 25, 25, 50, 50, 10) 85 | } else { 86 | fmt.Fprintf(os.Stderr, "Unable to parse the Atom feed (%v)\n", err) 87 | } 88 | } 89 | 90 | // tgrid makes a clickable grid of tweets from the Atom feed 91 | func tgrid(t Feed, x, y, w, h, nc int) { 92 | var slink, imlink string 93 | xp := x 94 | for i, entry := range t.Entry { 95 | for _, link := range entry.Link { 96 | switch link.Rel { 97 | case "alternate": 98 | slink = link.Href 99 | case "image": 100 | imlink = link.Href 101 | } 102 | } 103 | if i%nc == 0 && i > 0 { 104 | xp = x 105 | y += h 106 | } 107 | canvas.Link(slink, slink) 108 | canvas.Image(xp, y, imw, imh, imlink) 109 | canvas.LinkEnd() 110 | xp += w 111 | } 112 | } 113 | 114 | // for every non-flag argument, make a twitter search grid 115 | func main() { 116 | flag.Parse() 117 | width := 550 118 | height := 700 119 | canvas.Start(width, height) 120 | for _, s := range flag.Args() { 121 | canvas.Title("Twitter search for " + s) 122 | ts(s, *since, *nresults) 123 | canvas.Text(width/2, height-50, s, textfmt) 124 | } 125 | canvas.End() 126 | } 127 | -------------------------------------------------------------------------------- /planets/planets.go: -------------------------------------------------------------------------------- 1 | // planets: an exploration of scale 2 | // +build !appengine 3 | 4 | package main 5 | 6 | import ( 7 | "flag" 8 | "fmt" 9 | "image/png" 10 | "os" 11 | 12 | "github.com/ajstarks/svgo" 13 | ) 14 | 15 | var ssDist = []float64{ 16 | 0.00, // Sun 17 | 0.34, // Mercury 18 | 0.72, // Venus 19 | 1.00, // Earth 20 | 1.54, // Mars 21 | 5.02, // Jupiter 22 | 9.46, // Saturn 23 | 20.11, // Uranus 24 | 30.08} // Netpune 25 | 26 | var ssRad = []float64{ // Miles 27 | 423200.0, // Sun 28 | 1516.0, // Mercury 29 | 3760.0, // Venus 30 | 3957.0, // Earth 31 | 2104.0, // Mars 32 | 42980.0, // Jupiter 33 | 35610.0, // Saturn 34 | 15700.0, // Uranus 35 | 15260.0} // Neptune 36 | 37 | var ssColor = []string{ // R, G, B 38 | // Eyeballed from image 39 | "F7730C", // Sun 40 | "FAF8F2", // Mercury 41 | "FFFFF2", // Venus 42 | "0B5CE3", // Earth 43 | "F0C61D", // Mars 44 | "FDC791", // Jupiter 45 | "E0C422", // Saturn 46 | "DCF1F5", // Uranus 47 | "39B6F7"} // Neptune 48 | 49 | var ssImages = []string{ 50 | "sun.png", 51 | "mercury.png", 52 | "venus.png", 53 | "earth.png", 54 | "mars.png", 55 | "jupiter.png", 56 | "saturn.png", 57 | "uranus.png", 58 | "neptune.png"} 59 | 60 | var ( 61 | showdisk = flag.Bool("d", false, "show disk") 62 | showyou = flag.Bool("y", true, "show location") 63 | cw = flag.Int("w", 1200, "width") 64 | ch = flag.Int("h", 200, "height") 65 | canvas = svg.New(os.Stdout) 66 | ) 67 | 68 | func vmap(value float64, low1 float64, high1 float64, low2 float64, high2 float64) float64 { 69 | return low2 + (high2-low2)*(value-low1)/(high1-low1) 70 | } 71 | 72 | func main() { 73 | flag.Parse() 74 | width, height := *cw, *ch 75 | tfmt := "fill:white; font-size:%dpx; font-family:Calibri,sans; text-anchor:middle" 76 | canvas.Start(width, height) 77 | canvas.Title("Planets") 78 | canvas.Rect(0, 0, width, height, "fill:black") 79 | nobj := len(ssDist) 80 | y := height / 2 81 | margin := 100 82 | minsize := 7.0 83 | labeloc := height / 4 84 | fontsize := (width * 20) / 1000 85 | 86 | var x, r, imScale, maxh float64 87 | var px, po int 88 | 89 | if *showdisk { 90 | maxh = float64(height) / minsize 91 | } else { 92 | maxh = float64(height) / 4.0 93 | } 94 | for i := 1; i < nobj; i++ { 95 | x = vmap(ssDist[i], ssDist[1], ssDist[nobj-1], float64(margin), float64(width-margin)) 96 | r = (vmap(ssRad[i], ssRad[1], ssRad[nobj-1], minsize, maxh)) / 2 97 | px = int(x) 98 | if *showdisk { 99 | po = 0 100 | canvas.Circle(px, y, int(r), "fill:#"+ssColor[i]) 101 | } else { // show images 102 | f, err := os.Open(ssImages[i]) 103 | if err != nil { 104 | fmt.Fprintf(os.Stderr, "%s: %s\n", err, ssImages[i]) 105 | continue 106 | } 107 | defer f.Close() 108 | p, perr := png.DecodeConfig(f) 109 | if perr != nil { 110 | fmt.Fprintf(os.Stderr, "%s: %s\n", perr, ssImages[i]) 111 | continue 112 | } 113 | imScale = r / float64(p.Width) 114 | hs := float64(p.Height) * imScale 115 | dy := y - (int(hs) / 2) // center the image 116 | po = int(r) / 2 117 | canvas.Image(px, dy, int(r), int(hs), ssImages[i]) 118 | } 119 | if ssDist[i] == 1.0 && *showyou { // earth 120 | canvas.Line(px+po, y-po, px+po, y-labeloc, 121 | "stroke-width:1px;stroke:white") 122 | canvas.Text(px+po, y-labeloc-10, "You are here", fmt.Sprintf(tfmt, fontsize)) 123 | } 124 | } 125 | canvas.End() 126 | } 127 | -------------------------------------------------------------------------------- /float/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package svg generates SVG as defined by the Scalable Vector Graphics 1.1 Specification (). 3 | Output goes to the specified io.Writer. 4 | 5 | Supported SVG elements and functions 6 | 7 | Shapes, lines, text 8 | 9 | circle, ellipse, polygon, polyline, rect (including roundrects), line, text 10 | 11 | Paths 12 | 13 | general, arc, cubic and quadratic bezier paths, 14 | 15 | Image and Gradients 16 | 17 | image, linearGradient, radialGradient, 18 | 19 | Transforms 20 | 21 | translate, rotate, scale, skewX, skewY 22 | 23 | Filter Effects 24 | 25 | filter, feBlend, feColorMatrix, feColorMatrix, feComponentTransfer, feComposite, feConvolveMatrix, feDiffuseLighting, 26 | feDisplacementMap, feDistantLight, feFlood, feGaussianBlur, feImage, feMerge, feMorphology, feOffset, fePointLight, 27 | feSpecularLighting, feSpotLight,feTile, feTurbulence 28 | 29 | 30 | Metadata elements 31 | 32 | desc, defs, g (style, transform, id), mask, marker, pattern, title, (a)ddress, link, script, style, use 33 | 34 | Usage: (assuming GOPATH is set) 35 | 36 | go get github.com/ajstarks/svgo/float 37 | 38 | 39 | You can use godoc to browse the documentation from the command line: 40 | 41 | $ go doc github.com/ajstarks/svgo/float 42 | 43 | 44 | a minimal program, to generate SVG to standard output. 45 | 46 | package main 47 | 48 | import ( 49 | "github.com/ajstarks/svgo/float" 50 | "os" 51 | ) 52 | 53 | func main() { 54 | width := 500.0 55 | height := 500.0 56 | canvas := svg.New(os.Stdout) 57 | canvas.Start(width, height) 58 | canvas.Circle(width/2, height/2, 100) 59 | canvas.Text(width/2, height/2, "Hello, SVG", "text-anchor:middle;font-size:30px;fill:white") 60 | canvas.End() 61 | } 62 | 63 | Drawing in a web server: (http://localhost:2003/circle) 64 | 65 | package main 66 | 67 | import ( 68 | "log" 69 | "github.com/ajstarks/svgo" 70 | "net/http" 71 | ) 72 | 73 | func main() { 74 | http.Handle("/circle", http.HandlerFunc(circle)) 75 | err := http.ListenAndServe(":2003", nil) 76 | if err != nil { 77 | log.Fatal("ListenAndServe:", err) 78 | } 79 | } 80 | 81 | func circle(w http.ResponseWriter, req *http.Request) { 82 | w.Header().Set("Content-Type", "image/svg+xml") 83 | s := svg.New(w) 84 | s.Start(500, 500) 85 | s.Circle(250, 250, 125, "fill:none;stroke:black") 86 | s.End() 87 | } 88 | 89 | Functions and types 90 | 91 | Many functions use x, y to specify an object's location, and w, h to specify the object's width and height. 92 | Where applicable, a final optional argument specifies the style to be applied to the object. 93 | The style strings follow the SVG standard; name:value pairs delimited by semicolons, or a 94 | series of name="value" pairs. For example: `"fill:none; opacity:0.3"` or `fill="none" opacity="0.3"` (see: ) 95 | 96 | The SVG type: 97 | 98 | type SVG struct { 99 | Writer io.Writer 100 | } 101 | 102 | Most operations are methods on this type, specifying the destination io.Writer. 103 | 104 | The Offcolor type: 105 | 106 | type Offcolor struct { 107 | Offset uint8 108 | Color string 109 | Opacity float64 110 | } 111 | 112 | is used to specify the offset, color, and opacity of stop colors in linear and radial gradients 113 | 114 | The Filterspec type: 115 | 116 | type Filterspec struct { 117 | In string 118 | In2 string 119 | Result string 120 | } 121 | 122 | is used to specify inputs and results for filter effects 123 | 124 | */ 125 | package svg 126 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package svg generates SVG as defined by the Scalable Vector Graphics 1.1 Specification (). 3 | Output goes to the specified io.Writer. 4 | 5 | Supported SVG elements and functions 6 | 7 | Shapes, lines, text 8 | 9 | circle, ellipse, polygon, polyline, rect (including roundrects), line, text 10 | 11 | Paths 12 | 13 | general, arc, cubic and quadratic bezier paths, 14 | 15 | Image and Gradients 16 | 17 | image, linearGradient, radialGradient, 18 | 19 | Transforms 20 | 21 | translate, rotate, scale, skewX, skewY 22 | 23 | Filter Effects 24 | 25 | filter, feBlend, feColorMatrix, feColorMatrix, feComponentTransfer, feComposite, feConvolveMatrix, feDiffuseLighting, 26 | feDisplacementMap, feDistantLight, feFlood, feGaussianBlur, feImage, feMerge, feMorphology, feOffset, fePointLight, 27 | feSpecularLighting, feSpotLight,feTile, feTurbulence 28 | 29 | 30 | Metadata elements 31 | 32 | desc, defs, g (style, transform, id), mask, marker, pattern, title, (a)ddress, link, script, style, use 33 | 34 | Usage: (assuming GOPATH is set) 35 | 36 | go get github.com/ajstarks/svgo 37 | go install github.com/ajstarks/svgo/... 38 | 39 | 40 | You can use godoc to browse the documentation from the command line: 41 | 42 | $ godoc github.com/ajstarks/svgo 43 | 44 | 45 | a minimal program, to generate SVG to standard output. 46 | 47 | package main 48 | 49 | import ( 50 | "github.com/ajstarks/svgo" 51 | "os" 52 | ) 53 | 54 | func main() { 55 | width := 500 56 | height := 500 57 | canvas := svg.New(os.Stdout) 58 | canvas.Start(width, height) 59 | canvas.Circle(width/2, height/2, 100) 60 | canvas.Text(width/2, height/2, "Hello, SVG", "text-anchor:middle;font-size:30px;fill:white") 61 | canvas.End() 62 | } 63 | 64 | Drawing in a web server: (http://localhost:2003/circle) 65 | 66 | package main 67 | 68 | import ( 69 | "log" 70 | "github.com/ajstarks/svgo" 71 | "net/http" 72 | ) 73 | 74 | func main() { 75 | http.Handle("/circle", http.HandlerFunc(circle)) 76 | err := http.ListenAndServe(":2003", nil) 77 | if err != nil { 78 | log.Fatal("ListenAndServe:", err) 79 | } 80 | } 81 | 82 | func circle(w http.ResponseWriter, req *http.Request) { 83 | w.Header().Set("Content-Type", "image/svg+xml") 84 | s := svg.New(w) 85 | s.Start(500, 500) 86 | s.Circle(250, 250, 125, "fill:none;stroke:black") 87 | s.End() 88 | } 89 | 90 | Functions and types 91 | 92 | Many functions use x, y to specify an object's location, and w, h to specify the object's width and height. 93 | Where applicable, a final optional argument specifies the style to be applied to the object. 94 | The style strings follow the SVG standard; name:value pairs delimited by semicolons, or a 95 | series of name="value" pairs. For example: `"fill:none; opacity:0.3"` or `fill="none" opacity="0.3"` (see: ) 96 | 97 | The SVG type: 98 | 99 | type SVG struct { 100 | Writer io.Writer 101 | } 102 | 103 | Most operations are methods on this type, specifying the destination io.Writer. 104 | 105 | The Offcolor type: 106 | 107 | type Offcolor struct { 108 | Offset uint8 109 | Color string 110 | Opacity float64 111 | } 112 | 113 | is used to specify the offset, color, and opacity of stop colors in linear and radial gradients 114 | 115 | The Filterspec type: 116 | 117 | type Filterspec struct { 118 | In string 119 | In2 string 120 | Result string 121 | } 122 | 123 | is used to specify inputs and results for filter effects 124 | 125 | */ 126 | package svg 127 | -------------------------------------------------------------------------------- /f50/f50.go: -------------------------------------------------------------------------------- 1 | // f50 -- given a search term, display 10x5 image grid, sorted by interestingness 2 | // +build !appengine 3 | 4 | package main 5 | 6 | import ( 7 | "encoding/xml" 8 | "fmt" 9 | "net/http" 10 | "net/url" 11 | "os" 12 | 13 | "github.com/ajstarks/svgo" 14 | ) 15 | 16 | // FlickrResp defines the Flickr response 17 | type FlickrResp struct { 18 | Stat string `xml:"stat,attr"` 19 | Photos Photos `xml:"photos"` 20 | } 21 | 22 | // Photos defines a set of Flickr photos 23 | type Photos struct { 24 | Page string `xml:"page,attr"` 25 | Pages string `xml:"pages,attr"` 26 | Perpage string `xml:"perpage,attr"` 27 | Total string `xml:"total,attr"` 28 | Photo []Photo `xml:"photo"` 29 | } 30 | 31 | // Photo defines a Flickr photo 32 | type Photo struct { 33 | Id string `xml:"id,attr"` 34 | Owner string `xml:"owner,attr"` 35 | Secret string `xml:"secret,attr"` 36 | Server string `xml:"server,attr"` 37 | Farm string `xml:"farm,attr"` 38 | Title string `xml:"title,attr"` 39 | Ispublic string `xml:"ispublic,attr"` 40 | Isfriend string `xml:"isfriend,attr"` 41 | IsFamily string `xml:"isfamily,attr"` 42 | } 43 | 44 | var ( 45 | width = 805 46 | height = 500 47 | canvas = svg.New(os.Stdout) 48 | ) 49 | 50 | const ( 51 | apifmt = "https://api.flickr.com/services/rest/?method=%s&api_key=%s&%s=%s&per_page=50&sort=interestingness-desc" 52 | urifmt = "http://farm%s.static.flickr.com/%s/%s.jpg" 53 | apiKey = "YOURKEY" 54 | textStyle = "font-family:Calibri,sans-serif; font-size:48px; fill:white; text-anchor:start" 55 | imageWidth = 75 56 | imageHeight = 75 57 | ) 58 | 59 | // FlickrAPI calls the API given a method with single name/value pair 60 | func flickrAPI(method, name, value string) string { 61 | return fmt.Sprintf(apifmt, method, apiKey, name, value) 62 | } 63 | 64 | // makeURI converts the elements of a photo into a Flickr photo URI 65 | func makeURI(p Photo, imsize string) string { 66 | im := p.Id + "_" + p.Secret 67 | 68 | if len(imsize) > 0 { 69 | im += "_" + imsize 70 | } 71 | return fmt.Sprintf(urifmt, p.Farm, p.Server, im) 72 | } 73 | 74 | // imageGrid reads the response from Flickr, and creates a grid of images 75 | func imageGrid(f FlickrResp, x, y, cols, gutter int, imgsize string) { 76 | if f.Stat != "ok" { 77 | fmt.Fprintf(os.Stderr, "Status: %v\n", f.Stat) 78 | return 79 | } 80 | xpos := x 81 | for i, p := range f.Photos.Photo { 82 | if i%cols == 0 && i > 0 { 83 | xpos = x 84 | y += (imageHeight + gutter) 85 | } 86 | canvas.Link(makeURI(p, ""), p.Title) 87 | canvas.Image(xpos, y, imageWidth, imageHeight, makeURI(p, "s")) 88 | canvas.LinkEnd() 89 | xpos += (imageWidth + gutter) 90 | } 91 | } 92 | 93 | // fs calls the Flickr API to perform a photo search 94 | func fs(s string) { 95 | var f FlickrResp 96 | r, weberr := http.Get(flickrAPI("flickr.photos.search", "text", s)) 97 | if weberr != nil { 98 | fmt.Fprintf(os.Stderr, "%v\n", weberr) 99 | return 100 | } 101 | defer r.Body.Close() 102 | xmlerr := xml.NewDecoder(r.Body).Decode(&f) 103 | if xmlerr != nil || r.StatusCode != http.StatusOK { 104 | fmt.Fprintf(os.Stderr, "%v (status=%d)\n", xmlerr, r.StatusCode) 105 | return 106 | } 107 | canvas.Title(s) 108 | imageGrid(f, 5, 5, 10, 5, "s") 109 | canvas.Text(20, height-40, s, textStyle) 110 | } 111 | 112 | // for each search term on the commandline, create a photo grid 113 | func main() { 114 | for i := 1; i < len(os.Args); i++ { 115 | canvas.Start(width, height) 116 | canvas.Rect(0, 0, width, height, "fill:black") 117 | fs(url.QueryEscape(os.Args[i])) 118 | canvas.End() 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /compx/testcomp.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Component 5 | Another Thing 6 | 7 | 8 | Public 9 | Confidential 10 | Restricted 11 | Highly Restricted 12 | 13 | 14 | Note 15 | This is the test pattern for compx 16 | for best results, set the width to be 17 | at least 1300 18 | 19 | the management 20 | 21 | 22 | Here is another Note 23 | This one is bigger 24 | 25 | 26 | a stupid note 27 | you did not specify anything 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | [1] 43 | [2] 44 | [3] 45 | North to East 46 | 47 | 48 | [4] 49 | [5] 50 | [6] 51 | [7] 52 | [8] 53 | West to North 54 | 55 | 56 | [9] 57 | [10] 58 | [11] 59 | South to West 60 | 61 | 62 | [12] 63 | [13] 64 | [14] 65 | [15] 66 | [16] 67 | East to South 68 | 69 | 70 | -------------------------------------------------------------------------------- /ltr/ltr.go: -------------------------------------------------------------------------------- 1 | // ltr: Layer Tennis remixes 2 | 3 | package main 4 | 5 | import ( 6 | "flag" 7 | "fmt" 8 | "os" 9 | 10 | "github.com/ajstarks/svgo" 11 | ) 12 | 13 | var ( 14 | canvas = svg.New(os.Stdout) 15 | poster, opacity, row, col, offset bool 16 | title string 17 | width, height int 18 | ) 19 | 20 | const ( 21 | stdwidth = 900 22 | stdheight = 280 23 | ni = 11 24 | ) 25 | 26 | // imagefiles returns a list of files in the specifed directory 27 | // or nil on error. Each file includes the prepended directory name 28 | func imagefiles(directory string) []string { 29 | f, ferr := os.Open(directory) 30 | if ferr != nil { 31 | return nil 32 | } 33 | defer f.Close() 34 | files, derr := f.Readdir(-1) 35 | if derr != nil || len(files) == 0 { 36 | return nil 37 | } 38 | names := make([]string, len(files)) 39 | for i, v := range files { 40 | names[i] = directory + "/" + v.Name() 41 | } 42 | return names 43 | } 44 | 45 | // ltposter creates poster style: a title, followed by a list 46 | // of volleys 47 | func ltposter(x, y, w, h int, f []string) { 48 | canvas.Image(x, y, w*2, h*2, f[0]) // first file, assumed to be the banner 49 | y = y + (h * 2) 50 | for i := 1; i < len(f); i += 2 { 51 | canvas.Image(x, y, w, h, f[i]) 52 | canvas.Image(x+w, y, w, h, f[i+1]) 53 | if i%2 == 1 { 54 | y += h 55 | } 56 | } 57 | } 58 | 59 | // ltcol creates a single column of volley images 60 | func ltcol(x, y, w, h int, f []string) { 61 | for i := 0; i < len(f); i++ { 62 | canvas.Image(x, y, w, h, f[i]) 63 | y += h 64 | } 65 | } 66 | 67 | // ltop creates a view with each volley stacked together with 68 | // semi-transparent opacity 69 | func ltop(x, y, w, h int, f []string) { 70 | for i := 1; i < len(f); i++ { // skip the first file, assumed to be the banner 71 | canvas.Image(x, y, w, h, f[i], "opacity:0.2") 72 | } 73 | } 74 | 75 | // ltrow creates a row-wise view of volley images. 76 | func ltrow(x, y, w, h int, f []string) { 77 | for i := 0; i < len(f); i++ { 78 | canvas.Image(x, y, w, h, f[i]) 79 | x += w 80 | } 81 | } 82 | 83 | // ltoffset creates a view where each volley is offset from its opposing volley. 84 | func ltoffset(x, y, w, h int, f []string) { 85 | for i := 1; i < len(f); i++ { // skip the first file, assumed to be the banner 86 | 87 | if i%2 == 0 { 88 | x += w 89 | } else { 90 | x = 0 91 | } 92 | canvas.Image(x, y, w, h, f[i]) 93 | y += h 94 | } 95 | } 96 | 97 | // dotitle creates the title 98 | func dotitle(s string) { 99 | if len(title) > 0 { 100 | canvas.Title(title) 101 | } else { 102 | canvas.Title(s) 103 | } 104 | } 105 | 106 | // init sets up the command line flags. 107 | func init() { 108 | flag.BoolVar(&poster, "poster", false, "poster style") 109 | flag.BoolVar(&opacity, "opacity", false, "opacity style") 110 | flag.BoolVar(&row, "row", false, "display is a single row") 111 | flag.BoolVar(&col, "col", false, "display in a single column") 112 | flag.BoolVar(&offset, "offset", false, "display in a row, even layers offset") 113 | flag.IntVar(&width, "width", stdwidth, "image width") 114 | flag.IntVar(&height, "height", stdheight, "image height") 115 | flag.StringVar(&title, "title", "", "title") 116 | flag.Parse() 117 | } 118 | 119 | func main() { 120 | x := 0 121 | y := 0 122 | nd := len(flag.Args()) 123 | for i, dir := range flag.Args() { 124 | filelist := imagefiles(dir) 125 | if len(filelist) != ni || filelist == nil { 126 | fmt.Fprintf(os.Stderr, "in the %s directory, need %d images, read %d\n", dir, ni, len(filelist)) 127 | continue 128 | } 129 | switch { 130 | 131 | case opacity: 132 | if i == 0 { 133 | canvas.Start(width*nd, height*nd) 134 | dotitle(dir) 135 | } 136 | ltop(x, y, width, height, filelist) 137 | y += height 138 | 139 | case poster: 140 | if i == 0 { 141 | canvas.Start(width, ((height*(ni-1)/4)+height)*nd) 142 | dotitle(dir) 143 | } 144 | ltposter(x, y, width/2, height/2, filelist) 145 | y += (height * 3) + (height / 2) 146 | 147 | case col: 148 | if i == 0 { 149 | canvas.Start(width*nd, height*ni) 150 | dotitle(dir) 151 | } 152 | ltcol(x, y, width, height, filelist) 153 | x += width 154 | 155 | case row: 156 | if i == 0 { 157 | canvas.Start(width*ni, height*nd) 158 | dotitle(dir) 159 | } 160 | ltrow(x, y, width, height, filelist) 161 | y += height 162 | 163 | case offset: 164 | n := ni - 1 165 | pw := width * 2 166 | ph := nd * (height * (n)) 167 | if i == 0 { 168 | canvas.Start(pw, ph) 169 | canvas.Rect(0, 0, pw, ph, "fill:white") 170 | dotitle(dir) 171 | } 172 | ltoffset(x, y, width, height, filelist) 173 | y += n * height 174 | 175 | } 176 | } 177 | canvas.End() 178 | } 179 | -------------------------------------------------------------------------------- /colortab/svgcolors.txt: -------------------------------------------------------------------------------- 1 | aliceblue #F0F8FF 240,248,255 2 | antiquewhite #FAEBD7 250,235,215 3 | aqua #00FFFF 0,255,25 4 | aquamarine #7FFFD4 127,255,21 5 | azure #F0FFFF 240,255,255 6 | beige #F5F5DC 245,245,220 7 | bisque #FFE4C4 255,228,196 8 | black #000000 0,0,0 9 | blanchedalmond #FFEBCD 255,235,205 10 | blue #0000FF 0,0,255 11 | blueviolet #8A2BE2 138,43,226 12 | brown #A52A2A 165,42,42 13 | burlywood #DEB887 222,184,135 14 | cadetblue #5F9EA0 95,158,160 15 | chartreuse #7FFF00 127,255,0 16 | chocolate #D2691E 210,105,30 17 | coral #FF7F50 255,127,80 18 | cornflowerblue #6495ED 100,149,237 19 | cornsilk #FFF8DC 255,248,220 20 | crimson #DC143C 220,20,60 21 | cyan #00FFFF 0,255,255 22 | darkblue #00008B 0,0,139 23 | darkcyan #008B8B 0,139,139 24 | darkgoldenrod #B8860B 184,134,11 25 | darkgray #A9A9A9 169,169,169 26 | darkgreen #006400 0,100,0 27 | darkgrey #A9A9A9 169,169,169 28 | darkkhaki #BDB76B 189,183,107 29 | darkmagenta #8B008B 139,0,139 30 | darkolivegreen #556B2F 85,107,47 31 | darkorange #FF8C00 255,140,0 32 | darkorchid #9932CC 153,50,204 33 | darkred #8B0000 139,0,0 34 | darksalmon #E9967A 233,150,122 35 | darkseagreen #8FBC8F 143,188,143 36 | darkslateblue #483D8B 72,61,139 37 | darkslategray #2F4F4F 47,79,79 38 | darkslategrey #2F4F4F 47,79,79 39 | darkturquoise #00CED1 0,206,209 40 | darkviolet #9400D3 148,0,211 41 | deeppink #FF1493 255,20,147 42 | deepskyblue #00BFFF 0,191,255 43 | dimgray #696969 105,105,105 44 | dimgrey #696969 105,105,105 45 | dodgerblue #1E90FF 30,144,255 46 | firebrick #B22222 178,34,34 47 | floralwhite #FFFAF0 255,250,240 48 | forestgreen #228B22 34,139,34 49 | fuchsia #FF00FF 255,0,255 50 | gainsboro #DCDCDC 220,220,220 51 | ghostwhite #F8F8FF 248,248,255 52 | gold #FFD700 255,215,0 53 | goldenrod #DAA520 218,165,32 54 | gray #808080 128,128,128 55 | green #008000 0,128,0 56 | greenyellow #ADFF2F 173,255,47 57 | grey #808080 128,128,128 58 | honeydew #F0FFF0 240,255,240 59 | hotpink #FF69B4 255,105,180 60 | indianred #CD5C5C 205,92,92 61 | indigo #4B0082 75,0,130 62 | ivory #FFFFF0 255,255,240 63 | khaki #F0E68C 240,230,140 64 | lavender #E6E6FA 230,230,250 65 | lavenderblush #FFF0F5 255,240,245 66 | lawngreen #7CFC00 124,252,0 67 | lemonchiffon #FFFACD 255,250,205 68 | lightblue #ADD8E6 173,216,230 69 | lightcoral #F08080 240,128,128 70 | lightcyan #E0FFFF 224,255,255 71 | lightgoldenrodyellow #FAFAD2 250,250,210 72 | lightgray #D3D3D3 211,211,211 73 | lightgreen #90EE90 144,238,144 74 | lightgrey #D3D3D3 211,211,211 75 | lightpink #FFB6C1 255,182,193 76 | lightsalmon #FFA07A 255,160,122 77 | lightseagreen #20B2AA 32,178,170 78 | lightskyblue #87CEFA 135,206,250 79 | lightslategray #778899 119,136,153 80 | lightslategrey #778899 119,136,153 81 | lightsteelblue #B0C4DE 176,196,222 82 | lightyellow #FFFFE0 255,255,224 83 | lime #00FF00 0,255,0 84 | limegreen #32CD32 50,205,50 85 | linen #FAF0E6 250,240,230 86 | magenta #FF00FF 255,0,255 87 | maroon #800000 128,0,0 88 | mediumaquamarine #66CDAA 102,205,170 89 | mediumblue #0000CD 0,0,205 90 | mediumorchid #BA55D3 186,85,211 91 | mediumpurple #9370DB 147,112,219 92 | mediumseagreen #3CB371 60,179,113 93 | mediumslateblue #7B68EE 123,104,238 94 | mediumspringgreen #00FA9A 0,250,154 95 | mediumturquoise #48D1CC 72,209,204 96 | mediumvioletred #C71585 199,21,133 97 | midnightblue #191970 25,25,112 98 | mintcream #F5FFFA 245,255,250 99 | mistyrose #FFE4E1 255,228,225 100 | moccasin #FFE4B5 255,228,181 101 | navajowhite #FFDEAD 255,222,173 102 | navy #000080 0,0,128 103 | oldlace #FDF5E6 253,245,230 104 | olive #808000 128,128,0 105 | olivedrab #6B8E23 107,142,35 106 | orange #FFA500 255,165,0 107 | orangered #FF4500 255,69,0 108 | orchid #DA70D6 218,112,214 109 | palegoldenrod #EEE8AA 238,232,170 110 | palegreen #98FB98 152,251,152 111 | paleturquoise #AFEEEE 175,238,238 112 | palevioletred #DB7093 219,112,147 113 | papayawhip #FFEFD5 255,239,213 114 | peachpuff #FFDAB9 255,218,185 115 | peru #CD853F 205,133,63 116 | pink #FFC0CB 255,192,203 117 | plum #DDA0DD 221,160,221 118 | powderblue #B0E0E6 176,224,230 119 | purple #800080 128,0,128 120 | red #FF0000 255,0,0 121 | rosybrown #BC8F8F 188,143,143 122 | royalblue #4169E1 65,105,225 123 | saddlebrown #8B4513 139,69,19 124 | salmon #FA8072 250,128,114 125 | sandybrown #F4A460 244,164,96 126 | seagreen #2E8B57 46,139,87 127 | seashell #FFF5EE 255,245,238 128 | sienna #A0522D 160,82,45 129 | silver #C0C0C0 192,192,192 130 | skyblue #87CEEB 135,206,235 131 | slateblue #6A5ACD 106,90,205 132 | slategray #708090 112,128,144 133 | slategrey #708090 112,128,144 134 | snow #FFFAFA 255,250,250 135 | springgreen #00FF7F 0,255,127 136 | steelblue #4682B4 70,130,180 137 | tan #D2B48C 210,180,140 138 | teal #008080 0,128,128 139 | thistle #D8BFD8 216,191,216 140 | tomato #FF6347 255,99,71 141 | turquoise #40E0D0 64,224,208 142 | violet #EE82EE 238,130,238 143 | wheat #F5DEB3 245,222,179 144 | white #FFFFFF 255,255,255 145 | whitesmoke #F5F5F5 245,245,245 146 | yellow #FFFF00 255,255,0 147 | yellowgreen #9ACD32 154,205,50 148 | -------------------------------------------------------------------------------- /shotchart/shotchart.go: -------------------------------------------------------------------------------- 1 | // shotchart: make NBA shotcharts 2 | package main 3 | 4 | import ( 5 | "encoding/json" 6 | "flag" 7 | "fmt" 8 | "io" 9 | "net/http" 10 | "os" 11 | 12 | "github.com/ajstarks/svgo" 13 | ) 14 | 15 | // shotdata defines the shotchart JSON response from stats.nba.com 16 | type shotdata struct { 17 | Resource string `json:"resource"` 18 | Parameters struct { 19 | Leagueid string `json:"LeagueID"` 20 | Season string `json:"Season"` 21 | Seasontype string `json:"SeasonType"` 22 | Teamid int `json:"TeamID"` 23 | Playerid int `json:"PlayerID"` 24 | Contextfilter string `json:"ContextFilter"` 25 | Contextmeasure string `json:"ContextMeasure"` 26 | } `json:"parameters"` 27 | Resultsets []struct { 28 | Name string `json:"name"` 29 | Headers []string `json:"headers"` 30 | Rowset [][]interface{} `json:"rowSet"` 31 | } `json:"resultSets"` 32 | } 33 | 34 | // vmap maps one range into another 35 | func vmap(value, low1, high1, low2, high2 float64) float64 { 36 | return low2 + (high2-low2)*(value-low1)/(high1-low1) 37 | } 38 | 39 | const ( 40 | shotsURLfmt = "http://stats.nba.com/stats/shotchartdetail?PlayerID=%s&CFID=33&CFPARAMS=2014-15&ContextFilter=&ContextMeasure=FGA&DateFrom=&DateTo=&GameID=&GameSegment=&LastNGames=0&LeagueID=00&Location=&MeasureType=Base&Month=0&OpponentTeamID=0&Outcome=&PaceAdjust=N&PerMode=PerGame&Period=0&PlusMinus=N&Position=&Rank=N&RookieYear=&Season=2014-15&SeasonSegment=&SeasonType=Regular+Season&TeamID=0&VsConference=&VsDivision=&mode=Advanced&showDetails=0&showShots=1&showZones=0" 41 | picURLfmt = "http://stats.nba.com/media/players/230x185/%s.png" 42 | activepicURLfmt = "http://stats.nba.com/media/players/700/%s.png" 43 | ) 44 | 45 | // shotAPI retrieves shotchart data from the source (currently stats.nba.com) 46 | func shotAPI(id string) (io.ReadCloser, error) { 47 | resp, err := http.Get(fmt.Sprintf(shotsURLfmt, id)) 48 | if err != nil { 49 | return nil, err 50 | } 51 | if resp.StatusCode != http.StatusOK { 52 | return nil, fmt.Errorf("unable to retreive network data for %s (%s)", id, resp.Status) 53 | } 54 | return resp.Body, nil 55 | } 56 | 57 | // shotchart retrieves shot data given an id, either from local files or the network API 58 | func shotchart(id string, network bool) { 59 | var ( 60 | shots shotdata 61 | r io.ReadCloser 62 | err error 63 | picture string 64 | ) 65 | 66 | if network { 67 | r, err = shotAPI(id) 68 | if err != nil { 69 | fmt.Fprintf(os.Stderr, "%v\n", err) 70 | return 71 | } 72 | picture = fmt.Sprintf(activepicURLfmt, id) 73 | } else { 74 | r, err = os.Open(id + ".json") 75 | if err != nil { 76 | fmt.Fprintf(os.Stderr, "%v\n", err) 77 | return 78 | } 79 | picture = id + ".png" 80 | } 81 | 82 | defer r.Close() 83 | err = json.NewDecoder(r).Decode(&shots) 84 | if err != nil { 85 | fmt.Fprintf(os.Stderr, "%v\n", err) 86 | return 87 | } 88 | 89 | // define the canvas, read and parse the response 90 | canvas := svg.New(os.Stdout) 91 | width, height := 900, 846 92 | fw, fh := float64(width), float64(height) 93 | //imw, imh := 230, 185 94 | imw, imh := 700, 440 95 | top := fw / 10 96 | canvas.Start(width, height) 97 | canvas.Rect(0, 0, width, height, "fill:white;stroke:black;stroke-width:2") 98 | //canvas.Image(width-imw, height-imh, imw, imh, picture) 99 | canvas.Image(width/2, height-imh, imw, imh, picture) 100 | canvas.Gstyle("font-family:Calibri,sans-serif;font-size:24px") 101 | nfg := 0 102 | for _, r := range shots.Resultsets { 103 | if r.Name == "Shot_Chart_Detail" { 104 | var playername string 105 | attempts := len(r.Rowset) 106 | for _, rs := range r.Rowset { 107 | var x, y float64 108 | var fill string 109 | for i, v := range rs { 110 | if i == 4 { 111 | playername = v.(string) 112 | } 113 | if i == 17 { 114 | x = v.(float64) 115 | } 116 | if i == 18 { 117 | y = v.(float64) 118 | } 119 | if i == 20 { 120 | xp := int(vmap(x, -300, 300, 0, fw)) 121 | yp := int(vmap(y, -300, 300, top, fh)) 122 | if v.(float64) == 0 { 123 | fill = "red" 124 | } else { 125 | nfg++ 126 | fill = "black" 127 | } 128 | canvas.Circle(xp, (yp-(height/2))+10, 4, "fill-opacity:0.3;fill:"+fill) 129 | } 130 | } 131 | } 132 | fgpct := (float64(nfg) / float64(attempts)) * 100 133 | canvas.Text(10, height-40, playername, "fill:gray") 134 | canvas.Text(10, height-10, fmt.Sprintf("%d out of %d", attempts, nfg), "fill:gray") 135 | canvas.Text(width/2, height-10, fmt.Sprintf("%.1f%%", fgpct), "text-anchor:middle;font-size:120%;fill:gray") 136 | canvas.Gend() 137 | } 138 | } 139 | canvas.End() 140 | 141 | } 142 | func main() { 143 | var network bool 144 | flag.BoolVar(&network, "net", false, "retrieve data from the network") 145 | flag.Parse() 146 | 147 | for _, file := range flag.Args() { 148 | shotchart(file, network) 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /personal/personal.go: -------------------------------------------------------------------------------- 1 | // personal: make persona slides 2 | package main 3 | 4 | import ( 5 | "encoding/xml" 6 | "flag" 7 | "fmt" 8 | "os" 9 | 10 | "github.com/ajstarks/deck/generate" 11 | ) 12 | 13 | // Persona is a title and 1 to n persons 14 | type Persona struct { 15 | Title string `xml:"title,attr"` 16 | Person []Person `xml:"person"` 17 | } 18 | 19 | // Person describes the attributes of a persona 20 | type Person struct { 21 | Name string `xml:"name"` 22 | Role string `xml:"role"` 23 | Picture pic `xml:"picture"` 24 | Location string `xml:"location"` 25 | About string `xml:"about"` 26 | Lists []list `xml:"list"` 27 | Scores []scoreset `xml:"scoreset"` 28 | Summary string `xml:"summary"` 29 | } 30 | 31 | type score struct { 32 | LName string `xml:"lname,attr"` 33 | RName string `xml:"rname,attr"` 34 | Score float64 `xml:"score,attr"` 35 | } 36 | 37 | type scoreset struct { 38 | Name string `xml:"name,attr"` 39 | Scores []score `xml:"score"` 40 | } 41 | 42 | type list struct { 43 | Name string `xml:"name,attr"` 44 | Items []string `xml:"item"` 45 | } 46 | 47 | type pic struct { 48 | Name string `xml:"name,attr"` 49 | Width int `xml:"width,attr"` 50 | Height int `xml:"height,attr"` 51 | } 52 | 53 | const ( 54 | scorewidth = 25.0 55 | scoreheight = 2.5 56 | ptop = 89.0 57 | left1 = 15.0 58 | left2 = 30.0 59 | scorecolor = "rgb(230,230,230)" 60 | scorelabelcolor = "rgb(75,75,75)" 61 | ) 62 | 63 | // makeslides reads and decode the persona file, and makes slides 64 | func makeslides(filename, color string, deck *generate.Deck) { 65 | r, err := os.Open(filename) 66 | if err != nil { 67 | fmt.Fprintf(os.Stderr, "%v\n", err) 68 | return 69 | } 70 | defer r.Close() 71 | var p Persona 72 | if err := xml.NewDecoder(r).Decode(&p); err == nil { 73 | pslide(&p, deck, color) 74 | } else { 75 | fmt.Fprintf(os.Stderr, "Unable to parse personas (%v)\n", err) 76 | } 77 | } 78 | 79 | // pslide makes a persona slide 80 | func pslide(p *Persona, deck *generate.Deck, color string) { 81 | for _, person := range p.Person { 82 | deck.StartSlide() 83 | deck.TextMid(left1, 95, p.Title, "sans", 2, "darkgray") 84 | deck.TextMid(left1, ptop, person.Name, "sans", 2.5, "black") 85 | deck.Image(left1, 72, person.Picture.Width, person.Picture.Height, person.Picture.Name, "") 86 | deck.Rect(left1, 42, 20, 30, color) 87 | deck.TextEnd(11.5, 53, "ROLE", "sans", 1.2, "white") 88 | deck.Text(12, 53, person.Role, "sans", 1.2, "white") 89 | deck.TextEnd(11.5, 50, "LOCATION", "sans", 1.2, "white") 90 | deck.Text(12, 50, person.Location, "sans", 1.2, "white") 91 | deck.TextBlock(6, 42, person.About, "sans", 1.2, 15, "white") 92 | deck.Text(left2, 55, "SUMMARY", "sans", 1.5, color) 93 | deck.Rect(66, 30, 75, 60, color, 7) 94 | deck.TextBlock(left2, 51, person.Summary, "sans", 1.2, 60, "black") 95 | measures(person.Scores, deck, color) 96 | lists(person.Lists, deck, color) 97 | deck.EndSlide() 98 | } 99 | } 100 | 101 | // list displays list data 102 | func lists(pl []list, deck *generate.Deck, color string) { 103 | var lx, ly float64 104 | for _, l := range pl { 105 | switch l.Name { 106 | case "GOALS/LIKES": 107 | lx = left2 108 | ly = ptop 109 | case "FRUSTRATIONS": 110 | lx = left2 111 | ly = 72 112 | case "RELATED QUESTIONS": 113 | lx = left2 114 | ly = 25 115 | } 116 | deck.Text(lx, ly, l.Name, "sans", 1.5, color) 117 | ly -= 1.5 * scoreheight 118 | if len(l.Items) < 7 { 119 | deck.List(lx, ly, 1.1, 1.4, 40, l.Items, "bullet", "sans", "black") 120 | } else { 121 | deck.List(lx, ly, 1.1, 1.4, 40, l.Items[0:6], "bullet", "sans", "black") 122 | deck.List(lx+32, ly, 1.1, 1.4, 40, l.Items[6:], "bullet", "sans", "black") 123 | } 124 | } 125 | } 126 | 127 | // measures places a percentage indicator in a rectangle 128 | func measures(ps []scoreset, deck *generate.Deck, color string) { 129 | var sx, sy float64 130 | for _, s := range ps { 131 | switch s.Name { 132 | case "TECHNOLOGY": 133 | sx = left1 134 | sy = 20 135 | case "PERSONALITY": 136 | sx = 85 137 | sy = ptop 138 | } 139 | deck.Text(sx-scorewidth/2, sy, s.Name, "sans", 1.5, color) 140 | sy -= 2.0 * scoreheight 141 | for _, ss := range s.Scores { 142 | makescore(sx, sy, ss, deck, color) 143 | sy -= 2.5 * scoreheight 144 | } 145 | } 146 | } 147 | 148 | // make score builds the rect and circle to indicate a percentage score 149 | func makescore(x, y float64, s score, deck *generate.Deck, color string) { 150 | deck.Text(x-scorewidth/2, y+2, s.LName, "sans", 1, scorelabelcolor) 151 | if len(s.RName) > 0 { 152 | deck.TextEnd(x+scorewidth/2, y+2, s.RName, "sans", 1, scorelabelcolor) 153 | } 154 | deck.Rect(x, y, scorewidth, scoreheight, scorecolor) 155 | deck.Circle((x-scorewidth/2)+(scorewidth*(s.Score/100)), y, scoreheight/2, color) 156 | } 157 | 158 | func main() { 159 | hcolor := flag.String("h", "rgb(67,74,154)", "highlight color") 160 | flag.Parse() 161 | deck := generate.NewSlides(os.Stdout, 1600, 900) 162 | deck.StartDeck() 163 | for _, f := range flag.Args() { 164 | makeslides(f, *hcolor, deck) 165 | } 166 | deck.EndDeck() 167 | } 168 | -------------------------------------------------------------------------------- /tumblrgrid/tumblrgrid.go: -------------------------------------------------------------------------------- 1 | // tumblrgrid: display a flexible grid of pictures from tumblr, filtered by tags 2 | // +build !appengine 3 | 4 | package main 5 | 6 | import ( 7 | "encoding/json" 8 | "flag" 9 | "fmt" 10 | "io" 11 | "io/ioutil" 12 | "net/http" 13 | "os" 14 | 15 | "github.com/ajstarks/svgo" 16 | ) 17 | 18 | var ( 19 | localfile, postlink bool 20 | ncols, gutter, thumbwidth, piclimit int 21 | filtertag string 22 | ) 23 | 24 | const ( 25 | apiKey = "APIKEY" 26 | apifmt = "http://api.tumblr.com/v2/blog/%s/posts?api_key=%s&type=photo&limit=%d" 27 | ) 28 | 29 | // Tumblr is the JSON data descriptions 30 | type Tumblr struct { 31 | Meta meta 32 | Response response 33 | } 34 | 35 | type meta struct { 36 | Msg string 37 | Status int 38 | } 39 | 40 | type response struct { 41 | Blog blog 42 | Posts []posts 43 | Total_posts int 44 | } 45 | 46 | type blog struct { 47 | Name string 48 | Posts int 49 | Title string 50 | Updated int 51 | Description string 52 | Url string 53 | } 54 | 55 | type posts struct { 56 | Photos []photos 57 | Tags []string 58 | Type string 59 | Link_url string 60 | Post_url string 61 | } 62 | 63 | type photos struct { 64 | Alt_sizes []altsizes 65 | } 66 | 67 | type altsizes struct { 68 | Height int 69 | Url string 70 | Width int 71 | } 72 | 73 | // resource gives a ReadCloser given a local file or URL 74 | func resource(name string) (io.ReadCloser, error) { 75 | if len(name) == 0 { 76 | return os.Stdin, nil 77 | } 78 | if localfile { 79 | return os.Open(name) 80 | } 81 | h, err := http.Get(fmt.Sprintf(apifmt, name, apiKey, piclimit)) 82 | return h.Body, err 83 | } 84 | 85 | // grid displays tumblr photos in a flexible grid 86 | func grid(canvas *svg.SVG, location string, x, y, nc, gutter int) { 87 | var ( 88 | t Tumblr 89 | r io.ReadCloser 90 | err error 91 | b []byte 92 | ) 93 | 94 | //get data from the resource, put it into the data structure 95 | if r, err = resource(location); err != nil { 96 | fmt.Fprintln(os.Stderr, err) 97 | return 98 | } 99 | 100 | defer r.Close() 101 | if b, err = ioutil.ReadAll(r); err != nil { 102 | fmt.Fprintf(os.Stderr, "%s %v\n", location, err) 103 | return 104 | } 105 | 106 | if err = json.Unmarshal(b, &t); err != nil { 107 | fmt.Fprintf(os.Stderr, "%s, %v\n", location, err) 108 | return 109 | } 110 | 111 | // create the linked blog title 112 | fontsize := thumbwidth / 3 113 | title := t.Response.Blog.Title 114 | 115 | if len(title) == 0 { 116 | title = t.Response.Blog.Name 117 | } 118 | canvas.Link(t.Response.Blog.Url, t.Response.Blog.Name) 119 | if nc < 4 { // if the columns are too narrow, rotate the title text 90 degrees 120 | canvas.TranslateRotate(x, y/2, 90) 121 | canvas.Text(fontsize, fontsize, title, "fill:lightgray") 122 | canvas.Gend() 123 | } else { 124 | canvas.Text(x, y/2, title, "fill:lightgray") 125 | } 126 | canvas.LinkEnd() 127 | 128 | // walk through the posts, displaying thumbnails, filtered by tags 129 | np := 0 130 | xp := x 131 | 132 | for _, posts := range t.Response.Posts { 133 | if np >= piclimit { 134 | break 135 | } 136 | if !intag(filtertag, posts.Tags) { 137 | continue 138 | } 139 | for _, photos := range posts.Photos { 140 | for i, p := range photos.Alt_sizes { 141 | if i == 0 { // link to the first image in the list 142 | if postlink { 143 | canvas.Link(posts.Post_url, "Photo") 144 | } else { 145 | canvas.Link(p.Url, "Photo") 146 | } 147 | } 148 | if p.Width == thumbwidth { 149 | np++ 150 | canvas.Image(xp, y, p.Width, p.Width, p.Url) 151 | xp += p.Width + gutter 152 | if np%nc == 0 { 153 | xp = x 154 | y += p.Width + gutter 155 | } 156 | } 157 | } 158 | canvas.LinkEnd() 159 | } 160 | } 161 | } 162 | 163 | // intag searches for tags in list 164 | func intag(tag string, list []string) bool { 165 | if len(tag) == 0 { 166 | return true 167 | } 168 | for _, s := range list { 169 | if s == tag { 170 | return true 171 | } 172 | } 173 | return false 174 | } 175 | 176 | // init sets up command flags 177 | func init() { 178 | flag.BoolVar(&localfile, "f", false, "read from local files") 179 | flag.BoolVar(&postlink, "p", false, "link to original post") 180 | flag.IntVar(&ncols, "nc", 5, "number of columns") 181 | flag.IntVar(&gutter, "g", 5, "gutter (pixels)") 182 | flag.IntVar(&thumbwidth, "tw", 75, "thumbnail width") 183 | flag.IntVar(&piclimit, "n", 30, "picture limit") 184 | flag.StringVar(&filtertag, "tag", "", "filter tag") 185 | flag.Parse() 186 | } 187 | 188 | func main() { 189 | 190 | np := len(flag.Args()) 191 | if np == 0 { 192 | np = 1 193 | } 194 | 195 | thalf := thumbwidth / 2 196 | x := thalf 197 | y := 50 198 | nrows := piclimit / ncols 199 | colincr := (ncols * thumbwidth) + (ncols * gutter) + thalf 200 | width := (colincr * np) + thalf 201 | height := (thumbwidth * nrows) + (nrows * gutter) + y + thalf 202 | gstyle := "font-family:Calibri,sans-serif;font-size:18px" 203 | 204 | canvas := svg.New(os.Stdout) 205 | canvas.Start(width, height) 206 | canvas.Rect(0, 0, width, height, "fill:rgb(43,62,87)") // tumblr blue 207 | canvas.Gstyle(gstyle) 208 | if len(flag.Args()) == 0 { 209 | grid(canvas, "", x, y, ncols, gutter) 210 | } else { 211 | for _, f := range flag.Args() { 212 | grid(canvas, f, x, y, ncols, gutter) 213 | x += colincr 214 | } 215 | } 216 | canvas.Gend() 217 | canvas.End() 218 | } 219 | -------------------------------------------------------------------------------- /codepic/codepic.go: -------------------------------------------------------------------------------- 1 | // codepic -- produce code+output sample suitable for slides 2 | // +build !appengine 3 | 4 | package main 5 | 6 | import ( 7 | "bufio" 8 | "encoding/xml" 9 | "flag" 10 | "fmt" 11 | "io" 12 | "os" 13 | "strings" 14 | 15 | "github.com/ajstarks/svgo" 16 | ) 17 | 18 | var ( 19 | canvas = svg.New(os.Stdout) 20 | font string 21 | codeframe, picframe, syntax bool 22 | linespacing, fontsize, top, left, boxwidth, width, height int 23 | ) 24 | 25 | const ( 26 | framestyle = "stroke:gray;stroke-dasharray:1,1;fill:none" 27 | codefmt = "font-family:%s;font-size:%dpx" 28 | labelfmt = "text-anchor:middle;" + codefmt 29 | kwfmt = `%s` 30 | commentfmt = `%s` 31 | textfmt = "%s\n" 32 | svgofmt = `font-weight="bold" fill="rgb(0,0,127)"` 33 | gokwfmt = `font-style="italic" fill="rgb(127,0,0)"` 34 | ) 35 | 36 | // SVG is the incoming SVG file, capture everything into between and 37 | // in the Doc string. This code will be translated to form the "picture" portion 38 | type SVG struct { 39 | Width int `xml:"width,attr"` 40 | Height int `xml:"height,attr"` 41 | Doc string `xml:",innerxml"` 42 | } 43 | 44 | var gokw = []string{ 45 | "defer ", "go ", "range ", "chan ", " continue", "if ", "for ", "func ", 46 | "uint8", "uint", "uint16", "uint32", "complex64", "complex128", " byte", "int8", "int16", "int32", 47 | "int64", " int", "float64", "float32", " string", "import ", "const ", 48 | "package ", "return", "var ", "type ", "switch ", "case ", "default:", 49 | } 50 | 51 | var svgokw = []string{ 52 | ".Start", ".Startview,", ".End", ".Script", ".Gstyle", ".Gtransform", ".Scale", ".Offcolor", 53 | ".ScaleXY", ".SkewX", ".SkewY", ".SkewXY,", ".Rotate", ".TranslateRotate", ".RotateTranslate", ".Translate", 54 | ".Group", ".Gid", ".Gend", ".ClipPath", ".ClipEnd", ".DefEnd", ".Def", ".Desc", ".Title", ".Linkf", 55 | ".LinkEnd", ".Use", ".Mask", ".MaskEnd", ".Circle", ".Ellipse", ".Polygon", ".Rect", ".CenterRect", 56 | ".Roundrect", ".Square", ".Path", ".Arc", ".Bezier", ".Qbez", ".Qbezier", ".Line", ".Polyline", ".Image", 57 | ".Textpath", ".Textlines,", ".Text", ".RGBA", ".RGB", ".LinearGradient", ".RadialGradient", ".Grid", 58 | } 59 | 60 | // codepic makes a code+picture SVG file, given a go source file 61 | // and conventionally named output -- given .go, .svg 62 | func codepic(filename string) { 63 | var basename string 64 | 65 | bn := strings.Split(filename, ".") 66 | if len(bn) > 0 { 67 | basename = bn[0] 68 | } else { 69 | fmt.Fprintf(os.Stderr, "cannot get the basename for %s\n", filename) 70 | return 71 | } 72 | canvas.Start(width, height) 73 | canvas.Title(basename) 74 | canvas.Rect(0, 0, width, height, "fill:white") 75 | placepic(width/2, top, basename) 76 | canvas.Gstyle(fmt.Sprintf(codefmt, font, fontsize)) 77 | placecode(left+fontsize, top+fontsize*2, filename) 78 | canvas.Gend() 79 | canvas.End() 80 | } 81 | 82 | // placecode places the code section on the left 83 | func placecode(x, y int, filename string) { 84 | var rerr error 85 | var line string 86 | var ic bool 87 | f, err := os.Open(filename) 88 | if err != nil { 89 | fmt.Fprintf(os.Stderr, "%v\n", err) 90 | return 91 | } 92 | defer f.Close() 93 | in := bufio.NewReader(f) 94 | 95 | for xp := left + fontsize; rerr == nil; y += linespacing { 96 | line, rerr = in.ReadString('\n') 97 | if len(line) > 0 { 98 | line, ic = svgtext(xp, y, line[0:len(line)-1]) 99 | if !ic && syntax { 100 | line = keyword(line, gokwfmt, gokw) 101 | line = keyword(line, svgofmt, svgokw) 102 | } 103 | io.WriteString(canvas.Writer, line) 104 | } 105 | } 106 | if codeframe { 107 | canvas.Rect(top, left, left+boxwidth, y, framestyle) 108 | } 109 | } 110 | 111 | // keyword styles keywords in a line of code 112 | func keyword(line string, style string, kw []string) string { 113 | for _, k := range kw { 114 | line = strings.Replace(line, k, fmt.Sprintf(kwfmt, style, k), 1) 115 | } 116 | return line 117 | } 118 | 119 | // svgtext 120 | func svgtext(x, y int, s string) (string, bool) { 121 | var iscomment = false 122 | s = strings.Replace(s, "&", "&", -1) 123 | s = strings.Replace(s, "<", "<", -1) 124 | s = strings.Replace(s, ">", ">", -1) 125 | s = strings.Replace(s, "\t", " ", -1) 126 | 127 | if syntax { 128 | i := strings.Index(s, "// ") 129 | if i >= 0 { 130 | iscomment = true 131 | s = strings.Replace(s, s[i:], fmt.Sprintf(commentfmt, s[i:]), 1) 132 | } 133 | } 134 | return fmt.Sprintf(textfmt, x, y, s), iscomment 135 | } 136 | 137 | // placepic places the picture on the right 138 | func placepic(x, y int, basename string) { 139 | var s SVG 140 | f, err := os.Open(basename + ".svg") 141 | if err != nil { 142 | fmt.Fprintf(os.Stderr, "%v\n", err) 143 | return 144 | } 145 | defer f.Close() 146 | if err := xml.NewDecoder(f).Decode(&s); err != nil { 147 | fmt.Fprintf(os.Stderr, "Unable to parse (%v)\n", err) 148 | return 149 | } 150 | canvas.Text(x, height-10, basename+".go", fmt.Sprintf(labelfmt, font, fontsize*2)) 151 | canvas.Group(`clip-path="url(#pic)"`, fmt.Sprintf(`transform="translate(%d,%d)"`, x, y)) 152 | canvas.ClipPath(`id="pic"`) 153 | canvas.Rect(0, 0, s.Width, s.Height) 154 | canvas.ClipEnd() 155 | io.WriteString(canvas.Writer, s.Doc) 156 | canvas.Gend() 157 | if picframe { 158 | canvas.Rect(x, y, s.Width, s.Height, framestyle) 159 | } 160 | } 161 | 162 | // init initializes flags 163 | func init() { 164 | flag.BoolVar(&codeframe, "codeframe", false, "frame the code") 165 | flag.BoolVar(&picframe, "picframe", false, "frame the picture") 166 | flag.BoolVar(&syntax, "syntax", false, "syntax coloring") 167 | flag.IntVar(&width, "w", 1024, "width") 168 | flag.IntVar(&height, "h", 768, "height") 169 | flag.IntVar(&linespacing, "ls", 16, "linespacing") 170 | flag.IntVar(&fontsize, "fs", 14, "fontsize") 171 | flag.IntVar(&top, "top", 20, "top") 172 | flag.IntVar(&left, "left", 20, "left") 173 | flag.IntVar(&boxwidth, "boxwidth", 450, "boxwidth") 174 | flag.StringVar(&font, "font", "Inconsolata", "font name") 175 | flag.Parse() 176 | } 177 | 178 | // for every file, make a code+pic SVG file 179 | func main() { 180 | for _, f := range flag.Args() { 181 | codepic(f) 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /rr/rr.go: -------------------------------------------------------------------------------- 1 | // radar roadmap (via Ernst and Young) 2 | package main 3 | 4 | import ( 5 | "encoding/xml" 6 | "flag" 7 | "fmt" 8 | "github.com/ajstarks/svgo" 9 | "io" 10 | "math" 11 | "os" 12 | "strings" 13 | ) 14 | 15 | var ( 16 | width, height, iscale, fontsize, margin int 17 | bgcolor, itemcolor, title string 18 | opacity float64 19 | showtitle bool 20 | gstyle = "font-family:Calibri;font-size:%dpx;text-anchor:middle" 21 | canvas = svg.New(os.Stdout) 22 | ) 23 | 24 | // Roadmap XML structure: 25 | // a roadmap consists of sections, which contain items, which indicate maturity and impact 26 | type Roadmap struct { 27 | Title string `xml:"title,attr"` 28 | Duration int `xml:"duration,attr"` 29 | Unit string `xml:"unit,attr"` 30 | Section []section `xml:"section"` 31 | } 32 | 33 | type section struct { 34 | Name string `xml:"name,attr"` 35 | Spacing float64 `xml:"spacing,attr"` 36 | Item []item `xml:"item"` 37 | } 38 | 39 | type item struct { 40 | Impact int `xml:"impact,attr"` 41 | Effort int `xml:"effort,attr"` 42 | Begin string `xml:"begin,attr"` 43 | Age float64 `xml:"age,attr"` 44 | Name string `xml:",chardata"` 45 | Desc desc `xml:"desc"` 46 | } 47 | 48 | type desc struct { 49 | Description string `xml:",chardata"` 50 | } 51 | 52 | // dorr does file i/o 53 | func dorr(location string) { 54 | var f *os.File 55 | var err error 56 | if len(location) > 0 { 57 | f, err = os.Open(location) 58 | } else { 59 | f = os.Stdin 60 | } 61 | if err == nil { 62 | readrr(f) 63 | f.Close() 64 | } else { 65 | fmt.Fprintf(os.Stderr, "%v\n", err) 66 | } 67 | } 68 | 69 | // readrr reads and parses the XML specification 70 | func readrr(r io.Reader) { 71 | var rm Roadmap 72 | if err := xml.NewDecoder(r).Decode(&rm); err == nil { 73 | drawrr(rm) 74 | } else { 75 | fmt.Fprintf(os.Stderr, "%v\n", err) 76 | } 77 | } 78 | 79 | // drawrr draws the roadmap 80 | func drawrr(rm Roadmap) { 81 | 82 | if len(rm.Title) > 0 { 83 | title = rm.Title 84 | } 85 | canvas.Title(title) 86 | canvas.Gstyle(fmt.Sprintf(gstyle, fontsize)) 87 | canvas.Rect(0, 0, width, height, "fill:"+bgcolor) 88 | duration := rm.Duration 89 | if duration <= 0 { 90 | duration = 3 91 | } 92 | ns := len(rm.Section) 93 | cx := (width / 2) 94 | cy := (height / 2) 95 | r := ((width - margin) / duration) / 2 96 | sr := r 97 | midsize := width / 100 98 | 99 | // for each unit of time, draw cencentric circles 100 | for i := 0; i < duration; i++ { 101 | canvas.Circle(cx, cy, sr, "fill:none;stroke:lightgray;stroke-dasharray:7,7") 102 | canvas.Text(cx, (cy - sr), fmt.Sprintf("%s %d", rm.Unit, i+1), "font-size:150%;fill:gray") 103 | sr += r 104 | } 105 | // for each section, define its boundaries and draw its label 106 | angle := 360.0 / float64(ns) 107 | a := angle 108 | a2 := a / 2 109 | for _, s := range rm.Section { 110 | drawseclines(cx, cy, float64(r*duration), a, a2, s.Name) 111 | spacing := s.Spacing 112 | if spacing == 0 { 113 | spacing = (angle / float64(len(s.Item))) - 1 114 | } 115 | iangle := a + spacing 116 | // for each item in the section, place the marker and label 117 | for _, i := range s.Item { 118 | itemx, itemy := dpolar(cx, cy, i.Age*float64(r), iangle) 119 | drawitem(itemx, itemy, i.Impact*iscale, i.Effort, i.Name) 120 | iangle += spacing 121 | } 122 | a += angle 123 | } 124 | 125 | canvas.Circle(cx, cy, midsize, "fill:red") 126 | canvas.Text(cx, cy, "READY", "baseline-shift:-25%") 127 | if showtitle { 128 | dotitle(title) 129 | } 130 | canvas.Gend() 131 | } 132 | 133 | // radians converts degrees to radians 134 | func radians(d float64) float64 { 135 | return d * (math.Pi / 180.0) 136 | } 137 | 138 | // dotitle places the title text 139 | func dotitle(s string) { 140 | canvas.Text(width/2, height-10, s, "font-size:200%;text-anchor:middle") 141 | } 142 | 143 | // dpolar returns the cartesion coordinates given the center, size, and angle (in degrees) 144 | func dpolar(cx, cy int, r, d float64) (int, int) { 145 | x := r * math.Cos(radians(d)) 146 | y := r * math.Sin(radians(d)) 147 | return cx + int(x), cy + int(y) 148 | } 149 | 150 | // drawseclines defines and labels the sections 151 | func drawseclines(cx, cy int, size, a, h float64, s string) { 152 | fs := fontsize + (fontsize / 2) 153 | ix, iy := dpolar(cx, cy, size, a) 154 | ix2, iy2 := dpolar(cx, cy, size+50, a+h) 155 | canvas.Line(cx, cy, ix, iy, "stroke:lightgray") 156 | textlines(ix2, iy2, fs, fs+2, "middle", "black", strings.Split(s, "\\n")) 157 | } 158 | 159 | // drawitem draws a roadmap item 160 | func drawitem(x, y, isize, ieffort int, s string) { 161 | var op float64 162 | if ieffort > 0 { 163 | op = opacity * (float64(ieffort) / 10.0) 164 | } else { 165 | op = opacity 166 | } 167 | style := fmt.Sprintf("fill:%s;fill-opacity:%.2f;stroke:white", itemcolor, op) 168 | canvas.Circle(x, y, isize/2, style) 169 | textlines(x-(isize/2)-2, y, fontsize, fontsize+2, "end", "black", strings.Split(s, "\\n")) 170 | } 171 | 172 | // textlines displays text at a specified size, leading, fill, and alignment 173 | func textlines(x, y, fs, leading int, align, fill string, s []string) { 174 | canvas.Gstyle(fmt.Sprintf("font-size:%dpx;text-anchor:%s;fill:%s", fs, align, fill)) 175 | for _, v := range s { 176 | canvas.Text(x, y, v) 177 | y += leading 178 | } 179 | canvas.Gend() 180 | } 181 | 182 | // init sets up the command flags 183 | func init() { 184 | flag.StringVar(&bgcolor, "bg", "white", "background color") 185 | flag.StringVar(&itemcolor, "ic", "rgb(131,206,226)", "item color") 186 | flag.IntVar(&width, "w", 800, "width") 187 | flag.IntVar(&height, "h", 800, "height") 188 | flag.IntVar(&fontsize, "f", 12, "fontsize (px)") 189 | flag.IntVar(&iscale, "s", int(float64(width)*.009), "impact scale") 190 | flag.IntVar(&margin, "m", 150, "outside margin") 191 | flag.BoolVar(&showtitle, "showtitle", false, "show title") 192 | flag.StringVar(&title, "t", "Roadmap", "title") 193 | flag.Float64Var(&opacity, "o", 1.0, "opacity") 194 | flag.Parse() 195 | } 196 | 197 | // for every input file (or stdin), draw a roadmap as specified by command flags 198 | func main() { 199 | canvas.Start(width, height) 200 | if len(flag.Args()) == 0 { 201 | dorr("") 202 | } else { 203 | for _, f := range flag.Args() { 204 | dorr(f) 205 | } 206 | } 207 | canvas.End() 208 | } 209 | -------------------------------------------------------------------------------- /stockproduct/stockproduct.go: -------------------------------------------------------------------------------- 1 | // stockproduct draws a bar chart comparing stock price to products 2 | // +build !appengine 3 | 4 | package main 5 | 6 | import ( 7 | "encoding/xml" 8 | "flag" 9 | "fmt" 10 | "os" 11 | 12 | "github.com/ajstarks/svgo" 13 | ) 14 | 15 | // Parameters defines options 16 | type Parameters struct { 17 | showline, showimage, showproduct, showprice, showdate, showgrid bool 18 | x, y, w, h, width, height, spacing, fontsize, dot int 19 | minvalue, maxvalue, ginterval, opacity, rotatetext float64 20 | barcolor string 21 | } 22 | 23 | // 24 | // 25 | // 26 | // 27 | // 28 | // 29 | // 30 | // 31 | 32 | // StockProduct is the top-level drawing 33 | type StockProduct struct { 34 | Title string `xml:"title,attr"` 35 | Sdata []Sdata `xml:"sdata"` 36 | } 37 | 38 | // Sdata defines stock data 39 | type Sdata struct { 40 | Price float64 `xml:"price,attr"` 41 | Date string `xml:"date,attr"` 42 | Product string `xml:"product,attr"` 43 | Image string `xml:"image,attr"` 44 | } 45 | 46 | // vmap maps ranges 47 | func vmap(value float64, low1 float64, high1 float64, low2 float64, high2 float64) float64 { 48 | return low2 + (high2-low2)*(value-low1)/(high1-low1) 49 | 50 | } 51 | 52 | // barchart draws a chart from data read at location, on a SVG canvas 53 | // if the location is the empty string, read from standard input. 54 | // Data items are scaled according to the width, with parameters controlling the visibility 55 | // of lines, products, images, and dates 56 | func (p *Parameters) barchart(location string, canvas *svg.SVG) { 57 | var ( 58 | f *os.File 59 | err error 60 | sp StockProduct 61 | ) 62 | if len(location) > 0 { 63 | f, err = os.Open(location) 64 | } else { 65 | f = os.Stdin 66 | } 67 | if err != nil { 68 | fmt.Fprintln(os.Stderr, err) 69 | return 70 | } 71 | defer f.Close() 72 | if err := xml.NewDecoder(f).Decode(&sp); err != nil { 73 | fmt.Fprintln(os.Stderr, err) 74 | return 75 | } 76 | 77 | bottom := p.y + p.h 78 | interval := p.w / (len(sp.Sdata) - 1) 79 | bw := interval - p.spacing 80 | offset := 120 81 | halfoffset := offset / 2 82 | 83 | if bw < 2 { 84 | bw = 2 85 | } 86 | canvas.Text(p.x, p.y-halfoffset, sp.Title, "font-size:300%") 87 | if p.showgrid { 88 | canvas.Gstyle("stroke:lightgray;stroke-width:1px") 89 | gx := p.x - (bw / 2) 90 | for i := p.maxvalue; i >= p.minvalue; i -= p.ginterval { 91 | yp := int(vmap(i, p.minvalue, p.maxvalue, float64(p.y), float64(bottom))) 92 | by := p.y + (bottom - yp) 93 | canvas.Line(gx, by, p.x+p.w+(bw/2), by) 94 | canvas.Text(gx-halfoffset, by, fmt.Sprintf("%.0f", i), "fill:black;stroke:none") 95 | } 96 | canvas.Gend() 97 | } 98 | canvas.Gstyle(fmt.Sprintf("stroke-opacity:%.2f;stroke:%s;stroke-width:%d;text-anchor:middle", p.opacity, p.barcolor, bw)) 99 | for _, d := range sp.Sdata { 100 | yp := int(vmap(d.Price, p.minvalue, p.maxvalue, float64(p.y), float64(bottom))) 101 | by := p.y + (bottom - yp) 102 | if p.showline { 103 | canvas.Line(p.x, bottom, p.x, by) 104 | } 105 | if p.dot > 0 { 106 | canvas.Circle(p.x, by, p.dot, fmt.Sprintf("stroke:none;fill-opacity:%.2f;fill:%s", p.opacity, p.barcolor)) 107 | } 108 | if p.showimage { 109 | if len(d.Image) > 0 { 110 | canvas.Image(p.x-bw/2, by-offset-p.fontsize, bw, offset, d.Image) 111 | } 112 | } 113 | canvas.Gstyle("stroke:none;fill:black") 114 | if p.showproduct { 115 | if p.rotatetext != 0 { 116 | canvas.TranslateRotate(p.x, bottom+40, p.rotatetext) 117 | canvas.Text(0, 0, d.Product) 118 | canvas.Gend() 119 | } else { 120 | canvas.Text(p.x, bottom+40, d.Product) 121 | } 122 | } 123 | if p.showprice { 124 | canvas.Text(p.x, by+p.fontsize, fmt.Sprintf("%.2f", d.Price), "font-size:150%;font-weight:bold") 125 | } 126 | if p.showdate { 127 | canvas.Text(p.x, bottom+20, d.Date) 128 | } 129 | canvas.Gend() 130 | p.x += interval 131 | } 132 | canvas.Gend() 133 | } 134 | 135 | var param Parameters 136 | 137 | // set parameters according to command flags 138 | func init() { 139 | flag.BoolVar(¶m.showline, "line", true, "show lines") 140 | flag.BoolVar(¶m.showimage, "image", true, "show images") 141 | flag.BoolVar(¶m.showproduct, "product", true, "show products") 142 | flag.BoolVar(¶m.showprice, "price", true, "show prices") 143 | flag.BoolVar(¶m.showdate, "date", true, "show dates") 144 | flag.BoolVar(¶m.showgrid, "grid", true, "show grid") 145 | flag.IntVar(¶m.width, "w", 1600, "overall width") 146 | flag.IntVar(¶m.height, "h", 900, "overall height") 147 | flag.IntVar(¶m.x, "left", 150, "left") 148 | flag.IntVar(¶m.y, "top", 120, "top") 149 | flag.IntVar(¶m.w, "gw", 1400, "graph width") 150 | flag.IntVar(¶m.h, "gh", 700, "graph height") 151 | flag.IntVar(¶m.dot, "dot", 0, "dotsize") 152 | flag.IntVar(¶m.fontsize, "fs", 14, "font size (px)") 153 | flag.IntVar(¶m.spacing, "spacing", 15, "bar spacing") 154 | flag.Float64Var(¶m.maxvalue, "max", 400, "max value") 155 | flag.Float64Var(¶m.minvalue, "min", 0, "max value") 156 | flag.Float64Var(¶m.ginterval, "ginterval", 50, "max value") 157 | flag.Float64Var(¶m.opacity, "opacity", 0.5, "bar opacity") 158 | flag.Float64Var(¶m.rotatetext, "rt", 0, "rotate text") 159 | flag.StringVar(¶m.barcolor, "color", "lightgray", "bar color") 160 | flag.Parse() 161 | } 162 | 163 | func main() { 164 | width := 1600 165 | height := 900 166 | canvas := svg.New(os.Stdout) 167 | canvas.Start(param.width, param.height) 168 | canvas.Rect(0, 0, width, height, canvas.RGB(255, 255, 255)) 169 | canvas.Gstyle(fmt.Sprintf("font-family:Calibri,sans-serif;font-size:%dpx", param.fontsize)) 170 | if len(flag.Args()) == 0 { 171 | param.barchart("", canvas) 172 | } else { 173 | for _, f := range flag.Args() { 174 | param.barchart(f, canvas) 175 | } 176 | } 177 | canvas.Gend() 178 | canvas.End() 179 | } 180 | -------------------------------------------------------------------------------- /pmap/pmap.go: -------------------------------------------------------------------------------- 1 | // pmap percentage maps 2 | // +build !appengine 3 | 4 | package main 5 | 6 | import ( 7 | "encoding/xml" 8 | "flag" 9 | "fmt" 10 | "io" 11 | "os" 12 | 13 | "github.com/ajstarks/svgo" 14 | ) 15 | 16 | // Pmap defines a porportional map 17 | type Pmap struct { 18 | Top int `xml:"top,attr"` 19 | Left int `xml:"left,attr"` 20 | Title string `xml:"title,attr"` 21 | Pdata []Pdata `xml:"pdata"` 22 | } 23 | 24 | // Pdata defines data with a portpotional map 25 | type Pdata struct { 26 | Legend string `xml:"legend,attr"` 27 | Stagger string `xml:"stagger,attr"` 28 | Alternate string `xml:"alternate,attr"` 29 | Item []Item `xml:"item"` 30 | } 31 | 32 | // Item defines an item with porpotional map data 33 | type Item struct { 34 | Name string `xml:",chardata"` 35 | Value float64 `xml:"value,attr"` 36 | } 37 | 38 | var ( 39 | width, height, fontsize, fontscale, round, gutter, pred, pgreen, pblue, oflen int 40 | bgcolor, olcolor, colorspec, title string 41 | showpercent, showdata, alternate, showtitle, stagger, showlegend, showtotal bool 42 | ofpct float64 43 | leftmargin = 40 44 | topmargin = 40 45 | canvas = svg.New(os.Stdout) 46 | ) 47 | 48 | const ( 49 | globalfmt = "stroke-width:1;font-family:Calibri,sans-serif;text-anchor:middle;font-size:%dpt" 50 | legendstyle = "text-anchor:start;font-size:150%" 51 | linefmt = "stroke:%s" 52 | ) 53 | 54 | func dopmap(location string) { 55 | var f *os.File 56 | var err error 57 | if len(location) > 0 { 58 | f, err = os.Open(location) 59 | } else { 60 | f = os.Stdin 61 | } 62 | if err == nil { 63 | readpmap(f) 64 | f.Close() 65 | } else { 66 | fmt.Fprintf(os.Stderr, "%v\n", err) 67 | } 68 | } 69 | 70 | func readpmap(r io.Reader) { 71 | var pm Pmap 72 | if err := xml.NewDecoder(r).Decode(&pm); err == nil { 73 | drawpmap(pm) 74 | } else { 75 | fmt.Fprintf(os.Stderr, "Unable to parse pmap (%v)\n", err) 76 | } 77 | } 78 | 79 | func drawpmap(m Pmap) { 80 | fs := fontsize 81 | if m.Left > 0 { 82 | leftmargin = m.Left 83 | } 84 | if m.Top > 0 { 85 | topmargin = m.Top 86 | } else { 87 | topmargin = fs * fontscale 88 | } 89 | x := leftmargin 90 | y := topmargin 91 | if len(m.Title) > 0 { 92 | title = m.Title 93 | } 94 | canvas.Title(title) 95 | if showtitle { 96 | dotitle(title) 97 | } 98 | for _, p := range m.Pdata { 99 | pmap(x, y, fs, p) 100 | y += fs*fontscale + (gutter + fs*2) 101 | } 102 | } 103 | 104 | func pmap(x, y, fs int, m Pdata) { 105 | var tfill, vfmt, oc string 106 | var up bool 107 | h := fs * fontscale 108 | fw := fs * 80 109 | slen := fs + (fs / 2) 110 | up = false 111 | 112 | sum := 0.0 113 | for _, v := range m.Item { 114 | sum += v.Value 115 | } 116 | 117 | if len(olcolor) > 0 { 118 | oc = olcolor 119 | } else { 120 | oc = bgcolor 121 | } 122 | loffset := (fs * fontscale) + fs 123 | gline := fmt.Sprintf(linefmt, "gray") 124 | wline := fmt.Sprintf(linefmt, oc) 125 | if len(m.Legend) > 0 && showlegend { 126 | if showtotal { 127 | canvas.Text(x, y-fs, fmt.Sprintf("%s (total: "+floatfmt(sum)+")", m.Legend, sum), legendstyle) 128 | } else { 129 | canvas.Text(x, y-fs, m.Legend, legendstyle) 130 | } 131 | } 132 | for i, p := range m.Item { 133 | k := p.Name 134 | v := p.Value 135 | if v == 0.0 { 136 | continue 137 | } 138 | pct := v / sum 139 | pw := int(pct * float64(fw)) 140 | xw := x + (pw / 2) 141 | yh := y + (h / 2) 142 | if pct >= .4 { 143 | tfill = "fill:white" 144 | } else { 145 | tfill = "fill:black" 146 | } 147 | if round > 0 { 148 | canvas.Roundrect(x, y, pw, h, round, round, canvas.RGBA(pred, pgreen, pblue, pct)) 149 | } else { 150 | canvas.Rect(x, y, pw, h, canvas.RGBA(pred, pgreen, pblue, pct)) 151 | } 152 | 153 | dy := yh + fs + (fs / 2) 154 | if pct <= ofpct || len(k) > oflen { // overflow label 155 | if up { 156 | dy -= loffset 157 | yh -= loffset 158 | canvas.Line(xw, y, xw, dy+(fs/2), gline) 159 | } else { 160 | dy += loffset 161 | yh += loffset 162 | canvas.Line(xw, y+h, xw, dy-(fs*3), gline) 163 | } 164 | if alternate { 165 | up = !up 166 | slen = fs * 2 167 | } else { 168 | slen = fs * 3 169 | } 170 | if stagger { 171 | loffset += slen 172 | } 173 | tfill = "fill:black" 174 | } 175 | canvas.Text(xw, yh, k, tfill) 176 | dpfmt := tfill + ";font-size:75%" 177 | vfmt = floatfmt(v) 178 | switch { 179 | case showpercent && !showdata: 180 | canvas.Text(xw, dy, fmt.Sprintf("%.1f%%", pct*100), dpfmt) 181 | case showpercent && showdata: 182 | canvas.Text(xw, dy, fmt.Sprintf(vfmt+", %.1f%%", v, pct*100), dpfmt) 183 | case showdata && !showpercent: 184 | canvas.Text(xw, dy, fmt.Sprintf(vfmt, v), dpfmt) 185 | } 186 | x += pw 187 | if i < len(m.Item) { 188 | canvas.Line(x, y, x, y+h, wline) 189 | } 190 | } 191 | } 192 | 193 | func floatfmt(v float64) string { 194 | var vfmt = "%.1f" 195 | if v-float64(int(v)) == 0.0 { 196 | vfmt = "%.0f" 197 | } 198 | return vfmt 199 | } 200 | 201 | func dotitle(s string) { 202 | offset := 40 203 | canvas.Text(leftmargin, height-offset, s, "text-anchor:start;font-size:250%") 204 | } 205 | 206 | func init() { 207 | flag.IntVar(&width, "w", 1024, "width") 208 | flag.IntVar(&height, "h", 768, "height") 209 | flag.IntVar(&fontsize, "f", 12, "font size (pt)") 210 | flag.IntVar(&fontscale, "s", 5, "font scaling factor") 211 | flag.IntVar(&round, "r", 0, "rounded corner size") 212 | flag.IntVar(&gutter, "g", 100, "gutter") 213 | flag.IntVar(&oflen, "ol", 20, "overflow length") 214 | flag.StringVar(&bgcolor, "bg", "white", "background color") 215 | flag.StringVar(&olcolor, "oc", "", "outline color") 216 | flag.StringVar(&colorspec, "c", "0,0,0", "color (r,g,b)") 217 | flag.StringVar(&title, "t", "Proportions", "title") 218 | flag.BoolVar(&showpercent, "p", false, "show percentage") 219 | flag.BoolVar(&showdata, "d", false, "show data") 220 | flag.BoolVar(&alternate, "a", false, "alternate overflow labels") 221 | flag.BoolVar(&stagger, "stagger", false, "stagger labels") 222 | flag.BoolVar(&showlegend, "showlegend", true, "show the legend") 223 | flag.BoolVar(&showtitle, "showtitle", false, "show the title") 224 | flag.BoolVar(&showtotal, "showtotal", false, "show totals in the legend") 225 | flag.Float64Var(&ofpct, "op", 0.05, "overflow percentage") 226 | flag.Parse() 227 | fmt.Sscanf(colorspec, "%d,%d,%d", &pred, &pgreen, &pblue) 228 | } 229 | 230 | func main() { 231 | canvas.Start(width, height) 232 | canvas.Rect(0, 0, width, height, "fill:"+bgcolor) 233 | canvas.Gstyle(fmt.Sprintf(globalfmt, fontsize)) 234 | 235 | if len(flag.Args()) == 0 { 236 | dopmap("") 237 | } else { 238 | for _, f := range flag.Args() { 239 | dopmap(f) 240 | } 241 | } 242 | canvas.Gend() 243 | canvas.End() 244 | } 245 | -------------------------------------------------------------------------------- /picserv/pic256.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | pic256 4 | 21 | 22 | 23 |

Pic256

24 |

Programmed pictures in a 256x256 square

25 |

Defaults

26 | 27 |
rotext
28 |
flower
29 |
cube
30 |
funnel
31 |
rshape
32 |
lewitt
33 |
mondrian
34 |
face
35 |
clock
36 |
pacman
37 |
tux
38 |
ubuntu
39 | 40 |

Variations

41 | 42 |

rotext

43 |

44 |

char=a
45 |
char=b&ti=40
46 |
char=c&ti=60
47 |
char=d&ti=90&font=Courier
48 |

49 | 50 |

flower

51 |

52 |

petals=10&n=100
53 |
petals=15&n=50
54 |
petals=30&n=20
55 |
petals=30&n=10
56 |

57 | 58 |

cube

59 |

60 |

y=80&row=1
61 |
y=50&row=2
62 |
&row=3
63 |
y=0&row=4
64 |

65 | 66 |

funnel

67 |

68 |

step=10
69 |
step=15
70 |
step=25
71 |
step=25&bg=white&fg=black
72 |

73 | 74 |

rshape

75 |

76 |

shape=c
77 |
shape=r
78 |
same=f
79 |
shape=r&same=t
80 |

81 | 82 |

lewitt

83 |

84 |

pen=1&lines=20
85 |
pen=2&lines=30
86 |
pen=3&lines=40
87 |
pen=5&?lines=100
88 |

89 | 90 | 91 |

mondrian

92 |

93 |

random=true
94 |
random=t
95 |
random=1
96 |
random=f
97 |

98 | 99 |

face

100 |

101 |

mood=happy&glance=u
102 |
mood=neutral&glance=d
103 |
mood=sad&glance=l
104 |
mood=happy&glance=r
105 |

106 | 107 |

clock

108 |

109 |

clock
110 |
hour=23
111 |
hour=12&min=34
112 |
hour=6&min=30&sec=0
113 |

114 | 115 |

pacman

116 |

117 |

pacman
118 |
angle=10
119 |
angle=40
120 |
angle=60
121 |

122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /picserv/index.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | const ( 4 | index = ` 5 | 6 | pic256 7 | 24 | 25 | 26 |

Pic256

27 |

Programmed pictures in a 256x256 square

28 |

Defaults

29 | 30 |
rotext
31 |
flower
32 |
cube
33 |
funnel
34 |
rshape
35 |
lewitt
36 |
mondrian
37 |
face
38 |
clock
39 |
pacman
40 |
tux
41 |
ubuntu
42 | 43 |

Variations

44 | 45 |

rotext

46 |

47 |

char=a
48 |
char=b&ti=40
49 |
char=c&ti=60
50 |
char=d&ti=90&font=Courier
51 |

52 | 53 |

flower

54 |

55 |

petals=10&n=100
56 |
petals=15&n=50
57 |
petals=30&n=20
58 |
petals=30&n=10
59 |

60 | 61 |

cube

62 |

63 |

y=80&row=1
64 |
y=50&row=2
65 |
&row=3
66 |
y=0&row=4
67 |

68 | 69 |

funnel

70 |

71 |

step=10
72 |
step=15
73 |
step=25
74 |
step=25&bg=white&fg=black
75 |

76 | 77 |

rshape

78 |

79 |

shape=c
80 |
shape=r
81 |
same=f
82 |
shape=r&same=t
83 |

84 | 85 |

lewitt

86 |

87 |

pen=1&lines=20
88 |
pen=2&lines=30
89 |
pen=3&lines=40
90 |
pen=5&?lines=100
91 |

92 | 93 | 94 |

mondrian

95 |

96 |

random=true
97 |
random=t
98 |
random=1
99 |
random=f
100 |

101 | 102 |

face

103 |

104 |

mood=happy&glance=u
105 |
mood=neutral&glance=d
106 |
mood=sad&glance=l
107 |
mood=happy&glance=r
108 |

109 | 110 |

clock

111 |

112 |

clock
113 |
hour=23
114 |
hour=12&min=34
115 |
hour=6&min=30&sec=0
116 |

117 | 118 |

pacman

119 |

120 |

pacman
121 |
angle=10
122 |
angle=40
123 |
angle=60
124 |

125 | 126 | 127 | 128 | ` 129 | ) 130 | -------------------------------------------------------------------------------- /benchviz/benchviz.go: -------------------------------------------------------------------------------- 1 | // benchviz: visualize benchmark data from benchcmp 2 | package main 3 | 4 | import ( 5 | "bufio" 6 | "bytes" 7 | "flag" 8 | "fmt" 9 | "io" 10 | "math" 11 | "os" 12 | "strconv" 13 | "strings" 14 | 15 | svg "github.com/ajstarks/svgo" 16 | ) 17 | 18 | // geometry defines the layout of the visualization 19 | type geometry struct { 20 | top, left, width, height, vwidth, vp, barHeight int 21 | dolines, coldata bool 22 | title, rcolor, scolor, style string 23 | deltamax, speedupmax float64 24 | } 25 | 26 | // process reads the input and calls the visualization function 27 | func process(canvas *svg.SVG, filename string, g geometry) int { 28 | if filename == "" { 29 | return g.visualize(canvas, filename, os.Stdin) 30 | } 31 | f, err := os.Open(filename) 32 | if err != nil { 33 | fmt.Fprintf(os.Stderr, "%v\n", err) 34 | return 0 35 | } 36 | defer f.Close() 37 | return g.visualize(canvas, filename, f) 38 | } 39 | 40 | // vmap maps world to canvas coordinates 41 | func vmap(value, low1, high1, low2, high2 float64) float64 { 42 | return low2 + (high2-low2)*(value-low1)/(high1-low1) 43 | } 44 | 45 | // visualize performs the visualization of the input, reading a line a time 46 | func (g *geometry) visualize(canvas *svg.SVG, filename string, f io.Reader) int { 47 | var ( 48 | err error 49 | line, vs, bmtitle string 50 | dmin, dmax float64 51 | ) 52 | 53 | bh := g.barHeight 54 | vizwidth := g.vwidth 55 | vspacing := g.barHeight + (g.barHeight / 3) // vertical spacing 56 | bmtype := "delta" 57 | 58 | in := bufio.NewReader(f) 59 | canvas.Gstyle(fmt.Sprintf("font-size:%dpx;font-family:sans-serif", bh)) 60 | if g.title == "" { 61 | bmtitle = filename 62 | } else { 63 | bmtitle = g.title 64 | } 65 | canvas.Text(g.left, g.top, bmtitle, "font-size:150%") 66 | 67 | height := 0 68 | for x, y, nr := g.left+g.vp, g.top+vspacing, 0; err == nil; nr++ { 69 | line, err = in.ReadString('\n') 70 | fields := strings.Split(strings.TrimSpace(line), ` `) 71 | 72 | if len(fields) <= 1 || len(line) < 2 { 73 | continue 74 | } 75 | name := fields[0] 76 | value := fields[len(fields)-1] 77 | // The improvement is the last value with 78 | // a % at the end, or ~. 79 | for _, v := range fields { 80 | if len(v) > 0 && v[len(v)-1] == '%' { 81 | value = v 82 | vs = v[:len(v)-1] 83 | } 84 | if v == "~" { 85 | value = "~" 86 | vs = "0" 87 | } 88 | } 89 | v, _ := strconv.ParseFloat(vs, 64) 90 | av := math.Abs(v) 91 | 92 | switch { 93 | case strings.HasPrefix(value, "delt"): 94 | bmtype = "delta" 95 | dmin = 0.0 96 | dmax = g.deltamax // 100.0 97 | y += vspacing * 2 98 | continue 99 | 100 | case strings.HasPrefix(value, "speed"): 101 | bmtype = "speedup" 102 | dmin = 0.0 103 | dmax = g.speedupmax // 10.0 104 | y += vspacing * 2 105 | continue 106 | 107 | case strings.HasPrefix(name, "#"): 108 | y += vspacing 109 | canvas.Text(g.left, y, line[1:], "font-style:italic;fill:gray") 110 | continue 111 | } 112 | 113 | bw := int(vmap(av, dmin, dmax, 0, float64(vizwidth))) 114 | switch g.style { 115 | case "bar": 116 | g.bars(canvas, x, y, bw, bh, vspacing/2, bmtype, name, value, v) 117 | case "inline": 118 | g.inline(canvas, g.left, y, bw, bh, bmtype, name, value, v) 119 | default: 120 | g.bars(canvas, x, y, bw, bh, vspacing/2, bmtype, name, value, v) 121 | } 122 | y += vspacing 123 | height = y 124 | } 125 | canvas.Gend() 126 | return height 127 | } 128 | 129 | // inline makes the inline style pf visualization 130 | func (g *geometry) inline(canvas *svg.SVG, x, y, w, h int, bmtype, name, value string, v float64) { 131 | var color string 132 | switch bmtype { 133 | case "delta": 134 | if v > 0 { 135 | color = g.rcolor 136 | } else { 137 | color = g.scolor 138 | } 139 | case "speedup": 140 | if v < 1.0 { 141 | color = g.rcolor 142 | } else { 143 | color = g.scolor 144 | } 145 | } 146 | canvas.Text(x-10, y, value, "text-anchor:end") 147 | canvas.Text(x, y, name) 148 | canvas.Rect(x, y-h, w, h, "fill-opacity:0.3;fill:"+color) 149 | } 150 | 151 | // bars creates barchart style visualization 152 | func (g *geometry) bars(canvas *svg.SVG, x, y, w, h, vs int, bmtype, name, value string, v float64) { 153 | canvas.Gstyle("font-style:italic;font-size:75%") 154 | toffset := h / 4 155 | var tx int 156 | var tstyle string 157 | switch bmtype { 158 | case "delta": 159 | if v > 0 { 160 | canvas.Rect(x-w, y-h/2, w, h, "fill-opacity:0.3;fill:"+g.rcolor) 161 | tx = x - w - toffset 162 | tstyle = "text-anchor:end" 163 | } else { 164 | canvas.Rect(x, y-h/2, w, h, "fill-opacity:0.3;fill:"+g.scolor) 165 | tx = x + w + toffset 166 | tstyle = "text-anchor:start" 167 | } 168 | case "speedup": 169 | if v < 1.0 { 170 | canvas.Rect(x-w, y-h/2, w, h, "fill-opacity:0.3;fill:"+g.rcolor) 171 | tx = x - w - toffset 172 | tstyle = "text-anchor:end" 173 | } else { 174 | canvas.Rect(x, y-h/2, w, h, "fill-opacity:0.3;fill:"+g.scolor) 175 | tx = x + w + toffset 176 | tstyle = "text-anchor:start" 177 | } 178 | } 179 | if g.coldata { 180 | canvas.Text(x-toffset, y+toffset, value, "text-anchor:end") 181 | } else { 182 | canvas.Text(tx, y+toffset, value, tstyle) 183 | } 184 | canvas.Gend() 185 | canvas.Text(g.left, y+(h/2), name, "text-anchor:start") 186 | if g.dolines { 187 | canvas.Line(g.left, y+vs, g.left+(g.width-g.left), y+vs, "stroke:lightgray;stroke-width:1") 188 | } 189 | } 190 | 191 | func main() { 192 | var ( 193 | width = flag.Int("w", 1024, "width") 194 | top = flag.Int("top", 50, "top") 195 | left = flag.Int("left", 100, "left margin") 196 | vp = flag.Int("vp", 512, "visualization point") 197 | vw = flag.Int("vw", 300, "visual area width") 198 | bh = flag.Int("bh", 20, "bar height") 199 | smax = flag.Float64("sm", 10, "maximum speedup") 200 | dmax = flag.Float64("dm", 100, "maximum delta") 201 | title = flag.String("title", "", "title") 202 | speedcolor = flag.String("scolor", "green", "speedup color") 203 | regresscolor = flag.String("rcolor", "red", "regression color") 204 | style = flag.String("style", "bar", "set the style (bar or inline)") 205 | lines = flag.Bool("line", false, "show lines between entries") 206 | coldata = flag.Bool("col", false, "show data in a single column") 207 | ) 208 | flag.Parse() 209 | 210 | g := geometry{ 211 | width: *width, 212 | top: *top, 213 | left: *left, 214 | vp: *vp, 215 | vwidth: *vw, 216 | barHeight: *bh, 217 | title: *title, 218 | scolor: *speedcolor, 219 | rcolor: *regresscolor, 220 | style: *style, 221 | dolines: *lines, 222 | coldata: *coldata, 223 | speedupmax: *smax, 224 | deltamax: *dmax, 225 | } 226 | 227 | // For every named file or stdin, render the SVG in memory, accumulating the height. 228 | var b bytes.Buffer 229 | canvas := svg.New(&b) 230 | height := 0 231 | if len(flag.Args()) > 0 { 232 | for _, f := range flag.Args() { 233 | height = process(canvas, f, g) 234 | g.top = height + 50 235 | } 236 | } else { 237 | height = process(canvas, "", g) 238 | } 239 | g.height = height + 15 240 | 241 | // Write the rendered SVG to stdout 242 | out := svg.New(os.Stdout) 243 | out.Start(g.width, g.height) 244 | out.Rect(0, 0, g.width, g.height, "fill:white;stroke-width:2px;stroke:lightgray") 245 | b.WriteTo(os.Stdout) 246 | out.End() 247 | } 248 | -------------------------------------------------------------------------------- /bulletgraph/bulletgraph.go: -------------------------------------------------------------------------------- 1 | // bulletgraph - bullet graphs 2 | // (Design Specification http://www.perceptualedge.com/articles/misc/Bullet_Graph_Design_Spec.pdf) 3 | // +build !appengine 4 | 5 | package main 6 | 7 | import ( 8 | "encoding/xml" 9 | "flag" 10 | "fmt" 11 | "io" 12 | "os" 13 | "strconv" 14 | "strings" 15 | 16 | "github.com/ajstarks/svgo" 17 | ) 18 | 19 | var ( 20 | width, height, fontsize, barheight, gutter, circleradius int 21 | bgcolor, barcolor, datacolor, compcolor, title, font string 22 | showtitle, circlemark bool 23 | gstyle = "font-family:'%s',sans-serif;font-size:%dpx" 24 | ) 25 | 26 | // a Bulletgraph Defintion 27 | // 28 | // This is a note 29 | // More expository text 30 | // 31 | // 32 | // 33 | // 34 | // 35 | // 36 | 37 | // Bulletgraph is the top-level drawing 38 | type Bulletgraph struct { 39 | Top int `xml:"top,attr"` 40 | Left int `xml:"left,attr"` 41 | Right int `xml:"right,attr"` 42 | Title string `xml:"title,attr"` 43 | Bdata []bdata `xml:"bdata"` 44 | Note []note `xml:"note"` 45 | } 46 | 47 | type bdata struct { 48 | Title string `xml:"title,attr"` 49 | Subtitle string `xml:"subtitle,attr"` 50 | Scale string `xml:"scale,attr"` 51 | Qmeasure string `xml:"qmeasure,attr"` 52 | Cmeasure float64 `xml:"cmeasure,attr"` 53 | Measure float64 `xml:"measure,attr"` 54 | } 55 | 56 | type note struct { 57 | Text string `xml:",chardata"` 58 | } 59 | 60 | // dobg does file i/o 61 | func dobg(location string, s *svg.SVG) { 62 | var f *os.File 63 | var err error 64 | if len(location) > 0 { 65 | f, err = os.Open(location) 66 | } else { 67 | f = os.Stdin 68 | } 69 | if err == nil { 70 | readbg(f, s) 71 | f.Close() 72 | } else { 73 | fmt.Fprintf(os.Stderr, "%v\n", err) 74 | } 75 | } 76 | 77 | // readbg reads and parses the XML specification 78 | func readbg(r io.Reader, s *svg.SVG) { 79 | var bg Bulletgraph 80 | if err := xml.NewDecoder(r).Decode(&bg); err == nil { 81 | drawbg(bg, s) 82 | } else { 83 | fmt.Fprintf(os.Stderr, "%v\n", err) 84 | } 85 | } 86 | 87 | // drawbg draws the bullet graph 88 | func drawbg(bg Bulletgraph, canvas *svg.SVG) { 89 | qmheight := barheight / 3 90 | 91 | if bg.Left == 0 { 92 | bg.Left = 250 93 | } 94 | if bg.Right == 0 { 95 | bg.Right = 50 96 | } 97 | if bg.Top == 0 { 98 | bg.Top = 50 99 | } 100 | if len(title) > 0 { 101 | bg.Title = title 102 | } 103 | 104 | maxwidth := width - (bg.Left + bg.Right) 105 | x := bg.Left 106 | y := bg.Top 107 | scalesep := 4 108 | tx := x - fontsize 109 | 110 | canvas.Title(bg.Title) 111 | // for each bdata element... 112 | for _, v := range bg.Bdata { 113 | 114 | // extract the data from the XML attributes 115 | sc := strings.Split(v.Scale, ",") 116 | qm := strings.Split(v.Qmeasure, ",") 117 | 118 | // you must have min,max,increment for the scale, at least one qualitative measure 119 | if len(sc) != 3 || len(qm) < 1 { 120 | continue 121 | } 122 | // get the qualitative measures 123 | qmeasures := make([]float64, len(qm)) 124 | for i, q := range qm { 125 | qmeasures[i], _ = strconv.ParseFloat(q, 64) 126 | } 127 | scalemin, _ := strconv.ParseFloat(sc[0], 64) 128 | scalemax, _ := strconv.ParseFloat(sc[1], 64) 129 | scaleincr, _ := strconv.ParseFloat(sc[2], 64) 130 | 131 | // label the graph 132 | canvas.Text(tx, y+(barheight/2), fmt.Sprintf("%s (%g)", v.Title, v.Measure), "text-anchor:end;font-weight:bold") 133 | canvas.Text(tx, y+(barheight/2)+fontsize, v.Subtitle, "fill:darkgray;text-anchor:end;font-size:75%") 134 | 135 | // draw the scale 136 | scfmt := "%g" 137 | if fraction(scaleincr) > 0 { 138 | scfmt = "%.1f" 139 | } 140 | canvas.Gstyle("text-anchor:middle;font-size:75%") 141 | for sc := scalemin; sc <= scalemax; sc += scaleincr { 142 | scx := vmap(sc, scalemin, scalemax, 0, float64(maxwidth)) 143 | canvas.Text(x+int(scx), y+scalesep+barheight+fontsize/2, fmt.Sprintf(scfmt, sc)) 144 | } 145 | canvas.Gend() 146 | 147 | // draw the qualitative measures 148 | canvas.Gstyle("fill-opacity:0.5;fill:" + barcolor) 149 | canvas.Rect(x, y, maxwidth, barheight) 150 | for _, q := range qmeasures { 151 | qbarlength := vmap(q, scalemin, scalemax, 0, float64(maxwidth)) 152 | canvas.Rect(x, y, int(qbarlength), barheight) 153 | } 154 | canvas.Gend() 155 | 156 | // draw the measure and the comparative measure 157 | barlength := int(vmap(v.Measure, scalemin, scalemax, 0, float64(maxwidth))) 158 | canvas.Rect(x, y+qmheight, barlength, qmheight, "fill:"+datacolor) 159 | cmx := int(vmap(v.Cmeasure, scalemin, scalemax, 0, float64(maxwidth))) 160 | if circlemark { 161 | canvas.Circle(x+cmx, y+barheight/2, circleradius, "fill-opacity:0.3;fill:"+compcolor) 162 | } else { 163 | cbh := barheight / 4 164 | canvas.Line(x+cmx, y+cbh, x+cmx, y+barheight-cbh, "stroke-width:3;stroke:"+compcolor) 165 | } 166 | 167 | y += barheight + gutter // adjust vertical position for the next iteration 168 | } 169 | // if requested, place the title below the last bar 170 | if showtitle && len(bg.Title) > 0 { 171 | y += fontsize * 2 172 | canvas.Text(bg.Left, y, bg.Title, "text-anchor:start;font-size:200%") 173 | } 174 | 175 | if len(bg.Note) > 0 { 176 | canvas.Gstyle("font-size:100%;text-anchor:start") 177 | y += fontsize * 2 178 | leading := 3 179 | for _, note := range bg.Note { 180 | canvas.Text(bg.Left, y, note.Text) 181 | y += fontsize + leading 182 | } 183 | canvas.Gend() 184 | } 185 | } 186 | 187 | //vmap maps one interval to another 188 | func vmap(value float64, low1 float64, high1 float64, low2 float64, high2 float64) float64 { 189 | return low2 + (high2-low2)*(value-low1)/(high1-low1) 190 | } 191 | 192 | // fraction returns the fractions portion of a floating point number 193 | func fraction(n float64) float64 { 194 | i := int(n) 195 | return n - float64(i) 196 | } 197 | 198 | // init sets up the command flags 199 | func init() { 200 | flag.StringVar(&bgcolor, "bg", "white", "background color") 201 | flag.StringVar(&barcolor, "bc", "rgb(240,240,240)", "bar color") 202 | flag.StringVar(&datacolor, "dc", "rgb(200,200,200)", "data color") 203 | flag.StringVar(&compcolor, "cc", "rgb(127,0,0)", "comparative color") 204 | flag.StringVar(&font, "font", "Calibri", "font") 205 | flag.IntVar(&width, "w", 1024, "width") 206 | flag.IntVar(&height, "h", 800, "height") 207 | flag.IntVar(&barheight, "bh", 32, "bar height") 208 | flag.IntVar(&circleradius, "cr", 8, "circle radius") 209 | flag.IntVar(&gutter, "g", 36, "gutter") 210 | flag.IntVar(&fontsize, "f", 18, "fontsize (px)") 211 | flag.BoolVar(&circlemark, "circle", false, "circle mark") 212 | flag.BoolVar(&showtitle, "showtitle", true, "show title") 213 | flag.StringVar(&title, "t", "", "title") 214 | flag.Parse() 215 | } 216 | 217 | // for every input file (or stdin), draw a bullet graph 218 | // as specified by command flags 219 | func main() { 220 | canvas := svg.New(os.Stdout) 221 | canvas.Start(width, height) 222 | canvas.Rect(0, 0, width, height, "fill:"+bgcolor) 223 | canvas.Gstyle(fmt.Sprintf(gstyle, font, fontsize)) 224 | if len(flag.Args()) == 0 { 225 | dobg("", canvas) 226 | } else { 227 | for _, f := range flag.Args() { 228 | dobg(f, canvas) 229 | } 230 | } 231 | canvas.Gend() 232 | canvas.End() 233 | } 234 | -------------------------------------------------------------------------------- /svgplay/svgplay.go: -------------------------------------------------------------------------------- 1 | // svgplay: sketch with SVGo, (derived from the old misc/goplay), except: 2 | // (1) only listen on localhost, (default port 1999) 3 | // (2) always render html, 4 | // (3) SVGo default code, 5 | package main 6 | 7 | import ( 8 | "bytes" 9 | "flag" 10 | "io/ioutil" 11 | "log" 12 | "net/http" 13 | "os" 14 | "os/exec" 15 | "path/filepath" 16 | "strconv" 17 | "text/template" 18 | ) 19 | 20 | var ( 21 | httpListen = flag.String("port", "1999", "port to listen on") 22 | useFloat = flag.Bool("f", false, "use the floating point version") 23 | ) 24 | 25 | var ( 26 | // a source of numbers, for naming temporary files 27 | uniq = make(chan int) 28 | ) 29 | 30 | func main() { 31 | flag.Parse() 32 | 33 | // source of unique numbers 34 | go func() { 35 | for i := 0; ; i++ { 36 | uniq <- i 37 | } 38 | }() 39 | 40 | http.HandleFunc("/", FrontPage) 41 | http.HandleFunc("/compile", Compile) 42 | log.Printf("☠ ☠ ☠ Warning: this server allows a client connecting to 127.0.0.1:%s to execute code on this computer ☠ ☠ ☠", *httpListen) 43 | log.Fatal(http.ListenAndServe("127.0.0.1:"+*httpListen, nil)) 44 | } 45 | 46 | // FrontPage is an HTTP handler that renders the svgoplay interface. 47 | // If a filename is supplied in the path component of the URI, 48 | // its contents will be put in the interface's text area. 49 | // Otherwise, the default "hello, world" program is displayed. 50 | func FrontPage(w http.ResponseWriter, req *http.Request) { 51 | data, err := ioutil.ReadFile(req.URL.Path[1:]) 52 | if err != nil { 53 | if (*useFloat) { 54 | data = helloWorldFloat 55 | } else { 56 | data = helloWorld 57 | } 58 | } 59 | frontPage.Execute(w, data) 60 | } 61 | 62 | // Compile is an HTTP handler that reads Go source code from the request, 63 | // runs the program (returning any errors), 64 | // and sends the program's output as the HTTP response. 65 | func Compile(w http.ResponseWriter, req *http.Request) { 66 | out, err := compile(req) 67 | if err != nil { 68 | compileError(w, out, err) 69 | return 70 | } 71 | 72 | // write the output of target as the http response 73 | w.Write(out) 74 | } 75 | 76 | var ( 77 | tmpdir, pkgdir, buildpid string 78 | ) 79 | 80 | func init() { 81 | // find real temporary directory (for rewriting filename in output) 82 | var err error 83 | tmpdir, err = filepath.EvalSymlinks(os.TempDir()) 84 | if err != nil { 85 | log.Fatal(err) 86 | } 87 | 88 | buildpid = strconv.Itoa(os.Getpid()) 89 | } 90 | 91 | func compile(req *http.Request) (out []byte, err error) { 92 | // target is the base name for .go, object, executable files 93 | target := filepath.Join(tmpdir, "svgplay"+buildpid+strconv.Itoa(<-uniq)) 94 | src := target + ".go" 95 | 96 | // write body to target.go 97 | body := new(bytes.Buffer) 98 | if _, err = body.ReadFrom(req.Body); err != nil { 99 | return 100 | } 101 | defer os.Remove(src) 102 | if err = ioutil.WriteFile(src, body.Bytes(), 0666); err != nil { 103 | return 104 | } 105 | return run("", "go", "run", src) 106 | } 107 | 108 | // error writes compile, link, or runtime errors to the HTTP connection. 109 | // The JavaScript interface uses the 404 status code to identify the error. 110 | func compileError(w http.ResponseWriter, out []byte, err error) { 111 | w.WriteHeader(404) 112 | if out != nil { 113 | elines := bytes.Split(out, []byte{'\n'}) 114 | for _, l := range elines { 115 | i := bytes.Index(l, []byte{':'}) 116 | output.Execute(w, l[i+1:]) 117 | } 118 | } else { 119 | output.Execute(w, err.Error()) 120 | } 121 | } 122 | 123 | // run executes the specified command and returns its output and an error. 124 | func run(dir string, args ...string) ([]byte, error) { 125 | var buf bytes.Buffer 126 | cmd := exec.Command(args[0], args[1:]...) 127 | cmd.Dir = dir 128 | cmd.Stdout = &buf 129 | cmd.Stderr = cmd.Stdout 130 | err := cmd.Run() 131 | return buf.Bytes(), err 132 | } 133 | 134 | var frontPage = template.Must(template.New("frontPage").Parse(frontPageText)) // HTML template 135 | var output = template.Must(template.New("output").Parse(outputText)) // HTML template 136 | 137 | var outputText = `
{{printf "%s" . |html}}
` 138 | 139 | var frontPageText = ` 140 | 141 | 142 | svgplay: SVGo sketching 143 | 163 | 252 | 253 | 254 |
255 | 256 |
257 | (Shift-Enter to compile and run.)     258 | Compile and run after each keystroke 259 |
260 |
261 | 262 |
263 |
264 |
265 | 266 | 267 | ` 268 | 269 | var helloWorld = []byte(`package main 270 | 271 | import ( 272 | "math/rand" 273 | "os" 274 | "time" 275 | "github.com/ajstarks/svgo" 276 | ) 277 | 278 | func rn(n int) int { return rand.Intn(n) } 279 | 280 | func main() { 281 | canvas := svg.New(os.Stdout) 282 | width := 500 283 | height := 500 284 | nstars := 250 285 | style := "font-size:48pt;fill:white;text-anchor:middle" 286 | 287 | rand.Seed(time.Now().Unix()) 288 | canvas.Start(width, height) 289 | canvas.Rect(0,0,width,height) 290 | for i := 0; i < nstars; i++ { 291 | canvas.Circle(rn(width), rn(height), rn(3), "fill:white") 292 | } 293 | canvas.Circle(width/2, height, width/2, "fill:rgb(77, 117, 232)") 294 | canvas.Text(width/2, height*4/5, "hello, world", style) 295 | canvas.End() 296 | }`) 297 | 298 | var helloWorldFloat = []byte(` 299 | package main 300 | 301 | import ( 302 | "math/rand" 303 | "os" 304 | "time" 305 | "github.com/ajstarks/svgo/float" 306 | ) 307 | 308 | func rn(n float64) float64 { return rand.Float64() * n } 309 | 310 | func main() { 311 | canvas := svg.New(os.Stdout) 312 | width := 500.0 313 | height := 500.0 314 | nstars := 250 315 | style := "font-size:48pt;fill:white;text-anchor:middle" 316 | 317 | rand.Seed(time.Now().Unix()) 318 | canvas.Start(width, height) 319 | canvas.Rect(0,0,width,height) 320 | for i := 0; i < nstars; i++ { 321 | canvas.Circle(rn(width), rn(height), rn(3), "fill:white") 322 | } 323 | canvas.Circle(width/2, height, width/2, "fill:rgb(77, 117, 232)") 324 | canvas.Text(width/2, height*4/5, "hello, world", style) 325 | canvas.End() 326 | }`) 327 | -------------------------------------------------------------------------------- /svgplot/svgplot.go: -------------------------------------------------------------------------------- 1 | //svgplot -- plot data (a stream of x,y coordinates) 2 | // +build !appengine 3 | 4 | package main 5 | 6 | import ( 7 | "flag" 8 | "fmt" 9 | "io" 10 | "math" 11 | "os" 12 | 13 | "github.com/ajstarks/svgo" 14 | ) 15 | 16 | // rawdata defines data as float64 x,y coordinates 17 | type rawdata struct { 18 | x float64 19 | y float64 20 | } 21 | 22 | type options map[string]bool 23 | type attributes map[string]string 24 | type measures map[string]int 25 | 26 | // plotset defines plot metadata 27 | type plotset struct { 28 | opt options 29 | attr attributes 30 | size measures 31 | } 32 | 33 | var ( 34 | canvas = svg.New(os.Stdout) 35 | plotopt = options{} 36 | plotattr = attributes{} 37 | plotnum = measures{} 38 | ps = plotset{plotopt, plotattr, plotnum} 39 | plotw, ploth, plotc, gwidth, gheight, gutter, beginx, beginy int 40 | ) 41 | 42 | const ( 43 | globalfmt = "font-family:%s;font-size:%dpt;stroke-width:%dpx" 44 | linestyle = "fill:none;stroke:" 45 | linefmt = "fill:none;stroke:%s" 46 | barfmt = linefmt + ";stroke-width:%dpx" 47 | ticfmt = "stroke:rgb(200,200,200);stroke-width:1px" 48 | labelfmt = ticfmt + ";text-anchor:end;fill:black" 49 | textfmt = "stroke:none;baseline-shift:-33.3%" 50 | smallint = -(1 << 30) 51 | ) 52 | 53 | // init initializes command flags and sets default options 54 | func init() { 55 | 56 | // boolean options 57 | showx := flag.Bool("showx", false, "show the xaxis") 58 | showy := flag.Bool("showy", false, "show the yaxis") 59 | showbar := flag.Bool("showbar", false, "show data bars") 60 | area := flag.Bool("area", false, "area chart") 61 | connect := flag.Bool("connect", true, "connect data points") 62 | showdot := flag.Bool("showdot", false, "show dots") 63 | showbg := flag.Bool("showbg", true, "show the background color") 64 | showfile := flag.Bool("showfile", false, "show the filename") 65 | sameplot := flag.Bool("sameplot", false, "plot on the same frame") 66 | 67 | // attributes 68 | bgcolor := flag.String("bgcolor", "rgb(240,240,240)", "plot background color") 69 | barcolor := flag.String("barcolor", "gray", "bar color") 70 | dotcolor := flag.String("dotcolor", "black", "dot color") 71 | linecolor := flag.String("linecolor", "gray", "line color") 72 | areacolor := flag.String("areacolor", "gray", "area color") 73 | font := flag.String("font", "Calibri,sans", "font") 74 | labelcolor := flag.String("labelcolor", "black", "label color") 75 | plotlabel := flag.String("label", "", "plot label") 76 | 77 | // sizes 78 | dotsize := flag.Int("dotsize", 2, "dot size") 79 | linesize := flag.Int("linesize", 2, "line size") 80 | barsize := flag.Int("barsize", 2, "bar size") 81 | fontsize := flag.Int("fontsize", 11, "font size") 82 | xinterval := flag.Int("xint", 10, "x axis interval") 83 | yinterval := flag.Int("yint", 4, "y axis interval") 84 | ymin := flag.Int("ymin", smallint, "y minimum") 85 | ymax := flag.Int("ymax", smallint, "y maximum") 86 | 87 | // meta options 88 | flag.IntVar(&beginx, "bx", 100, "initial x") 89 | flag.IntVar(&beginy, "by", 50, "initial y") 90 | flag.IntVar(&plotw, "pw", 500, "plot width") 91 | flag.IntVar(&ploth, "ph", 500, "plot height") 92 | flag.IntVar(&plotc, "pc", 2, "plot columns") 93 | flag.IntVar(&gutter, "gutter", ploth/10, "gutter") 94 | flag.IntVar(&gwidth, "width", 1024, "canvas width") 95 | flag.IntVar(&gheight, "height", 768, "canvas height") 96 | 97 | flag.Parse() 98 | 99 | // fill in the plotset -- all options, attributes, and sizes 100 | plotopt["showx"] = *showx 101 | plotopt["showy"] = *showy 102 | plotopt["showbar"] = *showbar 103 | plotopt["area"] = *area 104 | plotopt["connect"] = *connect 105 | plotopt["showdot"] = *showdot 106 | plotopt["showbg"] = *showbg 107 | plotopt["showfile"] = *showfile 108 | plotopt["sameplot"] = *sameplot 109 | 110 | plotattr["bgcolor"] = *bgcolor 111 | plotattr["barcolor"] = *barcolor 112 | plotattr["linecolor"] = *linecolor 113 | plotattr["dotcolor"] = *dotcolor 114 | plotattr["areacolor"] = *areacolor 115 | plotattr["font"] = *font 116 | plotattr["label"] = *plotlabel 117 | plotattr["labelcolor"] = *labelcolor 118 | 119 | plotnum["dotsize"] = *dotsize 120 | plotnum["linesize"] = *linesize 121 | plotnum["fontsize"] = *fontsize 122 | plotnum["xinterval"] = *xinterval 123 | plotnum["yinterval"] = *yinterval 124 | plotnum["barsize"] = *barsize 125 | plotnum["ymin"] = *ymin 126 | plotnum["ymax"] = *ymax 127 | } 128 | 129 | // fmap maps world data to document coordinates 130 | func fmap(value float64, low1 float64, high1 float64, low2 float64, high2 float64) float64 { 131 | return low2 + (high2-low2)*(value-low1)/(high1-low1) 132 | } 133 | 134 | // doplot opens a file and makes a plot 135 | func doplot(x, y int, location string) { 136 | var f *os.File 137 | var err error 138 | if len(location) > 0 { 139 | if plotopt["showfile"] { 140 | plotattr["label"] = location 141 | } 142 | f, err = os.Open(location) 143 | } else { 144 | f = os.Stdin 145 | } 146 | if err != nil { 147 | fmt.Fprintf(os.Stderr, "%v\n", err) 148 | return 149 | } 150 | nd, data := readxy(f) 151 | f.Close() 152 | 153 | if nd >= 2 { 154 | plot(x, y, plotw, ploth, ps, data) 155 | } 156 | } 157 | 158 | // plot places a plot at the specified location with the specified dimemsions 159 | // usinng the specified settings, using the specified data 160 | func plot(x, y, w, h int, settings plotset, d []rawdata) { 161 | nd := len(d) 162 | if nd < 2 { 163 | fmt.Fprintf(os.Stderr, "%d is not enough points to plot\n", len(d)) 164 | return 165 | } 166 | // Compute the minima and maxima 167 | maxx, minx := d[0].x, d[0].x 168 | maxy, miny := d[0].y, d[0].y 169 | for _, v := range d { 170 | 171 | if v.x > maxx { 172 | maxx = v.x 173 | } 174 | if v.y > maxy { 175 | maxy = v.y 176 | } 177 | if v.x < minx { 178 | minx = v.x 179 | } 180 | if v.y < miny { 181 | miny = v.y 182 | } 183 | } 184 | 185 | if settings.size["ymin"] != smallint { 186 | miny = float64(settings.size["ymin"]) 187 | } 188 | if settings.size["ymax"] != smallint { 189 | maxy = float64(settings.size["ymax"]) 190 | } 191 | // Prepare for a area or line chart by allocating 192 | // polygon coordinates; for the hrizon plot, you need two extra coordinates 193 | // for the extrema. 194 | needpoly := settings.opt["area"] || settings.opt["connect"] 195 | var xpoly, ypoly []int 196 | if needpoly { 197 | xpoly = make([]int, nd+2) 198 | ypoly = make([]int, nd+2) 199 | // preload the extrema of the polygon, 200 | // the bottom left and bottom right of the plot's rectangle 201 | xpoly[0] = x 202 | ypoly[0] = y + h 203 | xpoly[nd+1] = x + w 204 | ypoly[nd+1] = y + h 205 | } 206 | // Draw the plot's bounding rectangle 207 | if settings.opt["showbg"] && !settings.opt["sameplot"] { 208 | canvas.Rect(x, y, w, h, "fill:"+settings.attr["bgcolor"]) 209 | } 210 | // Loop through the data, drawing items as specified 211 | spacer := 10 212 | canvas.Gstyle(fmt.Sprintf(globalfmt, 213 | settings.attr["font"], settings.size["fontsize"], settings.size["linesize"])) 214 | 215 | for i, v := range d { 216 | xp := int(fmap(v.x, minx, maxx, float64(x), float64(x+w))) 217 | yp := int(fmap(v.y, miny, maxy, float64(y), float64(y-h))) 218 | 219 | if needpoly { 220 | xpoly[i+1] = xp 221 | ypoly[i+1] = yp + h 222 | } 223 | if settings.opt["showbar"] { 224 | canvas.Line(xp, yp+h, xp, y+h, 225 | fmt.Sprintf(barfmt, settings.attr["barcolor"], settings.size["barsize"])) 226 | } 227 | if settings.opt["showdot"] { 228 | canvas.Circle(xp, yp+h, settings.size["dotsize"], "fill:"+settings.attr["dotcolor"]) 229 | } 230 | if settings.opt["showx"] { 231 | if i%settings.size["xinterval"] == 0 { 232 | canvas.Text(xp, (y+h)+(spacer*2), fmt.Sprintf("%d", int(v.x)), "text-anchor:middle") 233 | canvas.Line(xp, (y + h), xp, (y+h)+spacer, ticfmt) 234 | } 235 | } 236 | } 237 | // Done constructing the points for the area or line plots, display them in one shot 238 | if settings.opt["area"] { 239 | canvas.Polygon(xpoly, ypoly, "fill:"+settings.attr["areacolor"]) 240 | } 241 | 242 | if settings.opt["connect"] { 243 | canvas.Polyline(xpoly[1:nd+1], ypoly[1:nd+1], linestyle+settings.attr["linecolor"]) 244 | } 245 | // Put on the y axis labels, if specified 246 | if settings.opt["showy"] { 247 | bot := math.Floor(miny) 248 | top := math.Ceil(maxy) 249 | yrange := top - bot 250 | interval := yrange / float64(settings.size["yinterval"]) 251 | canvas.Gstyle(labelfmt) 252 | for yax := bot; yax <= top; yax += interval { 253 | yaxp := fmap(yax, bot, top, float64(y), float64(y-h)) 254 | canvas.Text(x-spacer, int(yaxp)+h, fmt.Sprintf("%.1f", yax), textfmt) 255 | canvas.Line(x-spacer, int(yaxp)+h, x, int(yaxp)+h) 256 | } 257 | canvas.Gend() 258 | } 259 | // Finally, tack on the label, if specified 260 | if len(settings.attr["label"]) > 0 { 261 | canvas.Text(x, y+spacer, settings.attr["label"], "font-size:120%;fill:"+settings.attr["labelcolor"]) 262 | } 263 | 264 | canvas.Gend() 265 | } 266 | 267 | // readxy reads coordinates (x,y float64 values) from a io.Reader 268 | func readxy(f io.Reader) (int, []rawdata) { 269 | var ( 270 | r rawdata 271 | err error 272 | n, nf int 273 | ) 274 | data := make([]rawdata, 1) 275 | for ; err == nil; n++ { 276 | if n > 0 { 277 | data = append(data, r) 278 | } 279 | nf, err = fmt.Fscan(f, &data[n].x, &data[n].y) 280 | if nf != 2 { 281 | continue 282 | } 283 | } 284 | return n - 1, data[0 : n-1] 285 | } 286 | 287 | // plotgrid places plots on a grid, governed by a number of columns. 288 | func plotgrid(x, y int, files []string) { 289 | px := x 290 | for i, f := range files { 291 | if i > 0 && i%plotc == 0 && !plotopt["sameplot"] { 292 | px = x 293 | y += (ploth + gutter) 294 | } 295 | doplot(px, y, f) 296 | if !plotopt["sameplot"] { 297 | px += (plotw + gutter) 298 | } 299 | } 300 | } 301 | 302 | // main plots data from specified files or standard input in a 303 | // grid where plotc specifies the number of columns. 304 | func main() { 305 | canvas.Start(gwidth, gheight) 306 | canvas.Rect(0, 0, gwidth, gheight, "fill:white") 307 | filenames := flag.Args() 308 | if len(filenames) == 0 { 309 | doplot(beginx, beginy, "") 310 | } else { 311 | plotgrid(beginx, beginy, filenames) 312 | } 313 | canvas.End() 314 | } 315 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Attribution 4.0 International Public License 2 | 3 | By exercising the Licensed Rights (defined below), You accept and agree to 4 | be bound by the terms and conditions of this Creative Commons Attribution 5 | 4.0 International Public License ("Public License"). To the extent this 6 | Public License may be interpreted as a contract, You are granted the 7 | Licensed Rights in consideration of Your acceptance of these terms and 8 | conditions, and the Licensor grants You such rights in consideration 9 | of benefits the Licensor receives from making the Licensed Material 10 | available under these terms and conditions. 11 | 12 | Section 1 – Definitions. 13 | 14 | Adapted Material means material subject to Copyright and Similar Rights 15 | that is derived from or based upon the Licensed Material and in which 16 | the Licensed Material is translated, altered, arranged, transformed, or 17 | otherwise modified in a manner requiring permission under the Copyright 18 | and Similar Rights held by the Licensor. For purposes of this Public 19 | License, where the Licensed Material is a musical work, performance, 20 | or sound recording, Adapted Material is always produced where the 21 | Licensed Material is synched in timed relation with a moving image. 22 | Adapter's License means the license You apply to Your Copyright and 23 | Similar Rights in Your contributions to Adapted Material in accordance 24 | with the terms and conditions of this Public License. Copyright and 25 | Similar Rights means copyright and/or similar rights closely related to 26 | copyright including, without limitation, performance, broadcast, sound 27 | recording, and Sui Generis Database Rights, without regard to how the 28 | rights are labeled or categorized. For purposes of this Public License, 29 | the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar 30 | Rights. Effective Technological Measures means those measures that, 31 | in the absence of proper authority, may not be circumvented under laws 32 | fulfilling obligations under Article 11 of the WIPO Copyright Treaty 33 | adopted on December 20, 1996, and/or similar international agreements. 34 | Exceptions and Limitations means fair use, fair dealing, and/or any other 35 | exception or limitation to Copyright and Similar Rights that applies to 36 | Your use of the Licensed Material. Licensed Material means the artistic 37 | or literary work, database, or other material to which the Licensor 38 | applied this Public License. Licensed Rights means the rights granted 39 | to You subject to the terms and conditions of this Public License, which 40 | are limited to all Copyright and Similar Rights that apply to Your use 41 | of the Licensed Material and that the Licensor has authority to license. 42 | Licensor means the individual(s) or entity(ies) granting rights under 43 | this Public License. Share means to provide material to the public by 44 | any means or process that requires permission under the Licensed Rights, 45 | such as reproduction, public display, public performance, distribution, 46 | dissemination, communication, or importation, and to make material 47 | available to the public including in ways that members of the public 48 | may access the material from a place and at a time individually chosen 49 | by them. Sui Generis Database Rights means rights other than copyright 50 | resulting from Directive 96/9/EC of the European Parliament and of the 51 | Council of 11 March 1996 on the legal protection of databases, as amended 52 | and/or succeeded, as well as other essentially equivalent rights anywhere 53 | in the world. You means the individual or entity exercising the Licensed 54 | Rights under this Public License. Your has a corresponding meaning. 55 | Section 2 – Scope. 56 | 57 | License grant. Subject to the terms and conditions of this Public 58 | License, the Licensor hereby grants You a worldwide, royalty-free, 59 | non-sublicensable, non-exclusive, irrevocable license to exercise the 60 | Licensed Rights in the Licensed Material to: reproduce and Share the 61 | Licensed Material, in whole or in part; and produce, reproduce, and 62 | Share Adapted Material. Exceptions and Limitations. For the avoidance 63 | of doubt, where Exceptions and Limitations apply to Your use, this 64 | Public License does not apply, and You do not need to comply with 65 | its terms and conditions. Term. The term of this Public License is 66 | specified in Section 6(a). Media and formats; technical modifications 67 | allowed. The Licensor authorizes You to exercise the Licensed Rights in 68 | all media and formats whether now known or hereafter created, and to make 69 | technical modifications necessary to do so. The Licensor waives and/or 70 | agrees not to assert any right or authority to forbid You from making 71 | technical modifications necessary to exercise the Licensed Rights, 72 | including technical modifications necessary to circumvent Effective 73 | Technological Measures. For purposes of this Public License, simply making 74 | modifications authorized by this Section 2(a)(4) never produces Adapted 75 | Material. Downstream recipients. Offer from the Licensor – Licensed 76 | Material. Every recipient of the Licensed Material automatically receives 77 | an offer from the Licensor to exercise the Licensed Rights under the terms 78 | and conditions of this Public License. No downstream restrictions. You 79 | may not offer or impose any additional or different terms or conditions 80 | on, or apply any Effective Technological Measures to, the Licensed 81 | Material if doing so restricts exercise of the Licensed Rights by any 82 | recipient of the Licensed Material. No endorsement. Nothing in this 83 | Public License constitutes or may be construed as permission to assert 84 | or imply that You are, or that Your use of the Licensed Material is, 85 | connected with, or sponsored, endorsed, or granted official status by, 86 | the Licensor or others designated to receive attribution as provided in 87 | Section 3(a)(1)(A)(i). Other rights. 88 | 89 | Moral rights, such as the right of integrity, are not licensed under 90 | this Public License, nor are publicity, privacy, and/or other similar 91 | personality rights; however, to the extent possible, the Licensor waives 92 | and/or agrees not to assert any such rights held by the Licensor to the 93 | limited extent necessary to allow You to exercise the Licensed Rights, but 94 | not otherwise. Patent and trademark rights are not licensed under this 95 | Public License. To the extent possible, the Licensor waives any right 96 | to collect royalties from You for the exercise of the Licensed Rights, 97 | whether directly or through a collecting society under any voluntary or 98 | waivable statutory or compulsory licensing scheme. In all other cases 99 | the Licensor expressly reserves any right to collect such royalties. 100 | Section 3 – License Conditions. 101 | 102 | Your exercise of the Licensed Rights is expressly made subject to the 103 | following conditions. 104 | 105 | Attribution. 106 | 107 | If You Share the Licensed Material (including in modified form), You must: 108 | 109 | retain the following if it is supplied by the Licensor with the Licensed 110 | Material: identification of the creator(s) of the Licensed Material and 111 | any others designated to receive attribution, in any reasonable manner 112 | requested by the Licensor (including by pseudonym if designated); a 113 | copyright notice; a notice that refers to this Public License; a notice 114 | that refers to the disclaimer of warranties; a URI or hyperlink to the 115 | Licensed Material to the extent reasonably practicable; indicate if You 116 | modified the Licensed Material and retain an indication of any previous 117 | modifications; and indicate the Licensed Material is licensed under this 118 | Public License, and include the text of, or the URI or hyperlink to, 119 | this Public License. You may satisfy the conditions in Section 3(a)(1) 120 | in any reasonable manner based on the medium, means, and context in which 121 | You Share the Licensed Material. For example, it may be reasonable to 122 | satisfy the conditions by providing a URI or hyperlink to a resource 123 | that includes the required information. If requested by the Licensor, 124 | You must remove any of the information required by Section 3(a)(1)(A) 125 | to the extent reasonably practicable. If You Share Adapted Material You 126 | produce, the Adapter's License You apply must not prevent recipients of 127 | the Adapted Material from complying with this Public License. Section 4 128 | – Sui Generis Database Rights. 129 | 130 | Where the Licensed Rights include Sui Generis Database Rights that apply 131 | to Your use of the Licensed Material: 132 | 133 | for the avoidance of doubt, Section 2(a)(1) grants You the right to 134 | extract, reuse, reproduce, and Share all or a substantial portion of the 135 | contents of the database; if You include all or a substantial portion of 136 | the database contents in a database in which You have Sui Generis Database 137 | Rights, then the database in which You have Sui Generis Database Rights 138 | (but not its individual contents) is Adapted Material; and You must comply 139 | with the conditions in Section 3(a) if You Share all or a substantial 140 | portion of the contents of the database. For the avoidance of doubt, 141 | this Section 4 supplements and does not replace Your obligations under 142 | this Public License where the Licensed Rights include other Copyright and 143 | Similar Rights. Section 5 – Disclaimer of Warranties and Limitation 144 | of Liability. 145 | 146 | Unless otherwise separately undertaken by the Licensor, to the 147 | extent possible, the Licensor offers the Licensed Material as-is and 148 | as-available, and makes no representations or warranties of any kind 149 | concerning the Licensed Material, whether express, implied, statutory, 150 | or other. This includes, without limitation, warranties of title, 151 | merchantability, fitness for a particular purpose, non-infringement, 152 | absence of latent or other defects, accuracy, or the presence or absence 153 | of errors, whether or not known or discoverable. Where disclaimers of 154 | warranties are not allowed in full or in part, this disclaimer may not 155 | apply to You. To the extent possible, in no event will the Licensor 156 | be liable to You on any legal theory (including, without limitation, 157 | negligence) or otherwise for any direct, special, indirect, incidental, 158 | consequential, punitive, exemplary, or other losses, costs, expenses, 159 | or damages arising out of this Public License or use of the Licensed 160 | Material, even if the Licensor has been advised of the possibility of 161 | such losses, costs, expenses, or damages. Where a limitation of liability 162 | is not allowed in full or in part, this limitation may not apply to You. 163 | The disclaimer of warranties and limitation of liability provided above 164 | shall be interpreted in a manner that, to the extent possible, most 165 | closely approximates an absolute disclaimer and waiver of all liability. 166 | Section 6 – Term and Termination. 167 | 168 | This Public License applies for the term of the Copyright and Similar 169 | Rights licensed here. However, if You fail to comply with this 170 | Public License, then Your rights under this Public License terminate 171 | automatically. Where Your right to use the Licensed Material has 172 | terminated under Section 6(a), it reinstates: 173 | 174 | automatically as of the date the violation is cured, provided it is 175 | cured within 30 days of Your discovery of the violation; or upon express 176 | reinstatement by the Licensor. For the avoidance of doubt, this Section 177 | 6(b) does not affect any right the Licensor may have to seek remedies 178 | for Your violations of this Public License. For the avoidance of doubt, 179 | the Licensor may also offer the Licensed Material under separate terms 180 | or conditions or stop distributing the Licensed Material at any time; 181 | however, doing so will not terminate this Public License. Sections 1, 182 | 5, 6, 7, and 8 survive termination of this Public License. Section 7 183 | – Other Terms and Conditions. 184 | 185 | The Licensor shall not be bound by any additional or different terms or 186 | conditions communicated by You unless expressly agreed. Any arrangements, 187 | understandings, or agreements regarding the Licensed Material not stated 188 | herein are separate from and independent of the terms and conditions of 189 | this Public License. Section 8 – Interpretation. 190 | 191 | For the avoidance of doubt, this Public License does not, and shall not be 192 | interpreted to, reduce, limit, restrict, or impose conditions on any use 193 | of the Licensed Material that could lawfully be made without permission 194 | under this Public License. To the extent possible, if any provision of 195 | this Public License is deemed unenforceable, it shall be automatically 196 | reformed to the minimum extent necessary to make it enforceable. If 197 | the provision cannot be reformed, it shall be severed from this Public 198 | License without affecting the enforceability of the remaining terms 199 | and conditions. No term or condition of this Public License will be 200 | waived and no failure to comply consented to unless expressly agreed 201 | to by the Licensor. Nothing in this Public License constitutes or may 202 | be interpreted as a limitation upon, or waiver of, any privileges and 203 | immunities that apply to the Licensor or You, including from the legal 204 | processes of any jurisdiction or authority. Creative Commons is not 205 | a party to its public licenses. Notwithstanding, Creative Commons may 206 | elect to apply one of its public licenses to material it publishes and 207 | in those instances will be considered the “Licensor.” The text of 208 | the Creative Commons public licenses is dedicated to the public domain 209 | under the CC0 Public Domain Dedication. Except for the limited purpose of 210 | indicating that material is shared under a Creative Commons public license 211 | or as otherwise permitted by the Creative Commons policies published at 212 | creativecommons.org/policies, Creative Commons does not authorize the 213 | use of the trademark “Creative Commons” or any other trademark or 214 | logo of Creative Commons without its prior written consent including, 215 | without limitation, in connection with any unauthorized modifications 216 | to any of its public licenses or any other arrangements, understandings, 217 | or agreements concerning use of licensed material. For the avoidance of 218 | doubt, this paragraph does not form part of the public licenses. 219 | 220 | Creative Commons may be contacted at creativecommons.org. 221 | -------------------------------------------------------------------------------- /barchart/barchart.go: -------------------------------------------------------------------------------- 1 | // barchart - bar chart 2 | package main 3 | 4 | import ( 5 | "encoding/xml" 6 | "flag" 7 | "fmt" 8 | "io" 9 | "math" 10 | "os" 11 | "strconv" 12 | "strings" 13 | 14 | "github.com/ajstarks/svgo" 15 | ) 16 | 17 | var ( 18 | width, height, fontsize, barheight, gutter, cornerRadius, labelimit int 19 | bgcolor, barcolor, title, inbar, valformat string 20 | showtitle, showdata, showgrid, showscale, endtitle, trace, stick bool 21 | ) 22 | 23 | const ( 24 | gstyle = "font-family:Calibri,sans-serif;font-size:%dpx" 25 | borderstyle = "stroke:lightgray;stroke-width:1px" 26 | scalestyle = "text-anchor:middle;font-size:75%" 27 | btitlestyle = "font-style:italic;font-size:150%;text-anchor:" 28 | notestyle = "font-style:italic;text-anchor:" 29 | datastyle = "text-anchor:end;fill:" 30 | titlestyle = "text-anchor:start;font-size:300%" 31 | labelstyle = "fill:black;baseline-shift:-25%" 32 | ) 33 | 34 | // a Barchart Defintion 35 | // 36 | // This is a note 37 | // More expository text 38 | // 39 | // 40 | // 41 | // 42 | // 43 | // 44 | 45 | type Barchart struct { 46 | Top int `xml:"top,attr"` 47 | Left int `xml:"left,attr"` 48 | Right int `xml:"right,attr"` 49 | Title string `xml:"title,attr"` 50 | Bdata []bdata `xml:"bdata"` 51 | Note []note `xml:"note"` 52 | } 53 | 54 | type bdata struct { 55 | Title string `xml:"title,attr"` 56 | Scale string `xml:"scale,attr"` 57 | Color string `xml:"color,attr"` 58 | Unit string `xml:"unit,attr"` 59 | Showdata bool `xml:"showdata,attr"` 60 | Showgrid bool `xml:"showgrid,attr"` 61 | Samebar bool `xml:"samebar,attr"` 62 | Bitem []bitem `xml:"bitem"` 63 | Bstack []bstack `xml:"bstack"` 64 | Note []note `xml:"note"` 65 | } 66 | 67 | type bitem struct { 68 | Name string `xml:"name,attr"` 69 | Value float64 `xml:"value,attr"` 70 | Color string `xml:"color,attr"` 71 | Samebar bool `xml:"samebar,attr"` 72 | } 73 | 74 | type bstack struct { 75 | Name string `xml:"name,attr"` 76 | Value string `xml:"value,attr"` 77 | Color string `xml:"color,attr"` 78 | } 79 | 80 | type note struct { 81 | Text string `xml:",chardata"` 82 | } 83 | 84 | // dobc does file i/o 85 | func dobc(location string, s *svg.SVG) { 86 | var f *os.File 87 | var err error 88 | if len(location) > 0 { 89 | f, err = os.Open(location) 90 | } else { 91 | f = os.Stdin 92 | } 93 | if err == nil { 94 | readbc(f, s) 95 | f.Close() 96 | } else { 97 | fmt.Fprintf(os.Stderr, "%v\n", err) 98 | os.Exit(1) 99 | } 100 | } 101 | 102 | // readbc reads and parses the XML specification 103 | func readbc(r io.Reader, s *svg.SVG) { 104 | var bc Barchart 105 | if err := xml.NewDecoder(r).Decode(&bc); err == nil { 106 | drawbc(bc, s) 107 | } else { 108 | fmt.Fprintf(os.Stderr, "%v\n", err) 109 | os.Exit(1) 110 | } 111 | } 112 | 113 | // drawbc draws the bar chart 114 | func drawbc(bg Barchart, canvas *svg.SVG) { 115 | 116 | if bg.Left == 0 { 117 | bg.Left = 250 118 | } 119 | if bg.Right == 0 { 120 | bg.Right = 50 121 | } 122 | if bg.Top == 0 { 123 | bg.Top = 50 124 | } 125 | if len(title) > 0 { 126 | bg.Title = title 127 | } 128 | labelimit = bg.Left / 8 129 | cr := cornerRadius 130 | maxwidth := width - (bg.Left + bg.Right) 131 | x := bg.Left 132 | y := bg.Top 133 | sep := 10 134 | color := barcolor 135 | scfmt := "%v" 136 | canvas.Title(bg.Title) 137 | 138 | // for each bdata element... 139 | for _, b := range bg.Bdata { 140 | if trace { 141 | fmt.Fprintf(os.Stderr, "# %s\n", b.Title) 142 | } 143 | // overide the color if specified 144 | if len(b.Color) > 0 { 145 | color = b.Color 146 | } else { 147 | color = barcolor 148 | } 149 | // extract the scale data from the XML attributes 150 | // if not specified, compute the scale factors 151 | sc := strings.Split(b.Scale, ",") 152 | var scalemin, scalemax, scaleincr float64 153 | 154 | switch { 155 | case len(sc) != 3: 156 | if len(b.Bitem) > 0 { 157 | scalemin, scalemax, scaleincr = scalevalues(b.Bitem) 158 | } 159 | if len(b.Bstack) > 0 { 160 | scalemin, scalemax, scaleincr = scalestack(b.Bstack) 161 | } 162 | case len(sc) == 3: 163 | var scerr error 164 | scalemin, scerr = strconv.ParseFloat(sc[0], 64) 165 | if scerr != nil { 166 | scalemin = 0 167 | } 168 | scalemax, scerr = strconv.ParseFloat(sc[1], 64) 169 | if scerr != nil { 170 | scalemax = 100 171 | } 172 | scaleincr, scerr = strconv.ParseFloat(sc[2], 64) 173 | if scerr != nil { 174 | scaleincr = 20 175 | } 176 | default: 177 | scalemin, scalemax, scaleincr = 0, 100, 20 178 | } 179 | // label the graph 180 | if trace { 181 | println("label") 182 | } 183 | canvas.Text(x, y, b.Title, btitlestyle+anchor()) 184 | 185 | y += sep * 2 186 | chartop := y 187 | 188 | // draw the data items 189 | canvas.Gstyle(datastyle + color) 190 | 191 | // stacked bars 192 | for _, stack := range b.Bstack { 193 | if trace { 194 | fmt.Fprintf(os.Stderr, "%s~%s\n", stack.Value, stack.Name) 195 | } 196 | stackdata := stackvalues(stack.Value) 197 | if len(stackdata) < 1 { 198 | continue 199 | } 200 | sx := x 201 | canvas.Text(x-sep, y+barheight/2, textlimit(stack.Name, labelimit), labelstyle) 202 | barop := colorange(1.0, 0.3, len(stackdata)) 203 | for ns, sd := range stackdata { 204 | dw := vmap(sd, scalemin, scalemax, 0, float64(maxwidth)) 205 | if len(stack.Color) > 0 { 206 | canvas.Roundrect(sx, y, int(dw), barheight, cr, cr, fmt.Sprintf("fill:%s;fill-opacity:%.2f", stack.Color, barop[ns])) 207 | } else { 208 | canvas.Roundrect(sx, y, int(dw), barheight, cr, cr, fmt.Sprintf("fill-opacity:%.2f", barop[ns])) 209 | } 210 | 211 | if (showdata || b.Showdata) && sd > 0 { 212 | var valuestyle = "fill-opacity:1;font-style:italic;font-size:75%;text-anchor:middle;baseline-shift:-25%;" 213 | var ditem string 214 | var datax int 215 | if len(b.Unit) > 0 { 216 | ditem = fmt.Sprintf(valformat+"%s", sd, b.Unit) 217 | } else { 218 | ditem = fmt.Sprintf(valformat, sd) 219 | } 220 | if len(inbar) > 0 { 221 | valuestyle += inbar 222 | } else { 223 | valuestyle += "fill:black" 224 | } 225 | datax = sx + int(dw)/2 226 | canvas.Text(datax, y+barheight/2, ditem, valuestyle) 227 | } 228 | sx += int(dw) 229 | } 230 | y += barheight + gutter 231 | } 232 | 233 | // plain bars 234 | for _, d := range b.Bitem { 235 | if trace { 236 | fmt.Fprintf(os.Stderr, "%.2f~%s\n", d.Value, d.Name) 237 | } 238 | canvas.Text(x-sep, y+barheight/2, textlimit(d.Name, labelimit), labelstyle) 239 | dw := vmap(d.Value, scalemin, scalemax, 0, float64(maxwidth)) 240 | var barop float64 241 | if b.Samebar { 242 | barop = 0.3 243 | } else { 244 | barop = 1.0 245 | } 246 | 247 | if stick { 248 | makestick(x, y, int(dw), d.Color, canvas) 249 | } else { 250 | makebar(x, y, int(dw), barheight, cr, d.Color, barop, canvas) 251 | } 252 | 253 | if showdata || b.Showdata { 254 | var valuestyle = "fill-opacity:1;font-style:italic;font-size:75%;text-anchor:start;baseline-shift:-25%;" 255 | var ditem string 256 | var datax int 257 | if len(b.Unit) > 0 { 258 | ditem = fmt.Sprintf(valformat+"%s", d.Value, b.Unit) 259 | } else { 260 | ditem = fmt.Sprintf(valformat, d.Value) 261 | } 262 | if len(inbar) > 0 { 263 | valuestyle += inbar 264 | datax = x + fontsize/2 265 | } else { 266 | valuestyle += "fill:black" 267 | datax = x + int(dw) + fontsize/2 268 | } 269 | canvas.Text(datax, y+barheight/2, ditem, valuestyle) 270 | } 271 | if !d.Samebar { 272 | y += barheight + gutter 273 | } 274 | } 275 | canvas.Gend() 276 | 277 | // draw the scale and borders 278 | chartbot := y + gutter 279 | if showgrid || b.Showgrid { 280 | canvas.Line(x, chartop, x+maxwidth, chartop, borderstyle) // top border 281 | canvas.Line(x, chartbot-gutter, x+maxwidth, chartbot-gutter, borderstyle) // bottom border 282 | } 283 | if showscale { 284 | if scaleincr < 1 { 285 | scfmt = "%.1f" 286 | } else { 287 | scfmt = "%0.f" 288 | } 289 | canvas.Gstyle(scalestyle) 290 | if trace { 291 | println(scalemin, scalemax, scaleincr) 292 | } 293 | if scaleincr > 0 && scalemin < scalemax { 294 | for sc := scalemin; sc <= scalemax; sc += scaleincr { 295 | scx := vmap(sc, scalemin, scalemax, 0, float64(maxwidth)) 296 | canvas.Text(x+int(scx), chartbot+fontsize, fmt.Sprintf(scfmt, sc)) 297 | if showgrid || b.Showgrid { 298 | canvas.Line(x+int(scx), chartbot, x+int(scx), chartop, borderstyle) // grid line 299 | } 300 | } 301 | } 302 | canvas.Gend() 303 | } 304 | 305 | // apply the note if present 306 | if len(b.Note) > 0 { 307 | canvas.Gstyle(notestyle + anchor()) 308 | y += fontsize * 2 309 | leading := 3 310 | for _, note := range b.Note { 311 | canvas.Text(bg.Left, y, note.Text) 312 | y += fontsize + leading 313 | } 314 | canvas.Gend() 315 | } 316 | y += sep * 7 // advance vertically for the next chart 317 | } 318 | // if requested, place the title below the last chart 319 | if showtitle && len(bg.Title) > 0 { 320 | y += fontsize * 2 321 | canvas.Text(bg.Left, y, bg.Title, titlestyle) 322 | } 323 | // apply overall note if present 324 | if len(bg.Note) > 0 { 325 | canvas.Gstyle(notestyle + anchor()) 326 | y += fontsize * 2 327 | leading := 3 328 | for _, note := range bg.Note { 329 | canvas.Text(bg.Left, y, note.Text) 330 | y += fontsize + leading 331 | } 332 | canvas.Gend() 333 | } 334 | } 335 | 336 | // nakebar draws the rectangle to represent the value 337 | func makebar(x, y, w, h, radius int, color string, op float64, canvas *svg.SVG) { 338 | if len(color) > 0 { 339 | canvas.Roundrect(x, y, w, h, radius, radius, fmt.Sprintf("fill:%s;fill-opacity:%.2f", color, op)) 340 | } else { 341 | canvas.Roundrect(x, y, w, barheight, radius, radius, fmt.Sprintf("fill-opacity:%.2f", op)) 342 | } 343 | } 344 | 345 | // makestick draws a dotted line ending with a circle to represent the value 346 | func makestick(x, y, w int, color string, canvas *svg.SVG) { 347 | y += fontsize * 60 / 100 348 | canvas.Line(x, y, x+w, y, "stroke-width:2;stroke-dasharray:3;stroke:rgb(170,170,170)") 349 | if len(color) > 0 { 350 | canvas.Circle(x+w, y, fontsize/4, "fill:"+color) 351 | } else { 352 | canvas.Circle(x+w, y, fontsize/4) 353 | } 354 | } 355 | 356 | func anchor() string { 357 | if endtitle { 358 | return "end" 359 | } 360 | return "start" 361 | } 362 | 363 | // vmap maps one interval to another 364 | func vmap(value float64, low1 float64, high1 float64, low2 float64, high2 float64) float64 { 365 | return low2 + (high2-low2)*(value-low1)/(high1-low1) 366 | } 367 | 368 | // maxitem finds the maxima is a collection of bar items 369 | func maxitem(data []bitem) float64 { 370 | if len(data) == 0 { 371 | return 100 372 | } 373 | max := -math.SmallestNonzeroFloat64 374 | for _, d := range data { 375 | if d.Value > max { 376 | max = d.Value 377 | } 378 | } 379 | return max 380 | } 381 | 382 | // maxstack finds the maxima is a stack of bars 383 | func maxstack(stacks []bstack) float64 { 384 | if len(stacks) == 0 { 385 | return 100 386 | } 387 | max := -math.SmallestNonzeroFloat64 388 | for _, s := range stacks { 389 | sv := stackvalues(s.Value) 390 | sum := 0.0 391 | for _, d := range sv { 392 | sum += d 393 | } 394 | if sum > max { 395 | max = sum 396 | } 397 | } 398 | return max 399 | } 400 | 401 | // scale values returns the min, max, increment from a set of bar items 402 | func scalevalues(data []bitem) (float64, float64, float64) { 403 | var m, max, increment float64 404 | rui := 5 405 | m = maxitem(data) 406 | max = roundup(m, 100) 407 | if max > 2 { 408 | increment = roundup(max/float64(rui), 10) 409 | } else { 410 | increment = 0.4 411 | } 412 | return 0, max, increment 413 | } 414 | 415 | // scalestack returns the min, max, increment from a stack of bars 416 | func scalestack(data []bstack) (float64, float64, float64) { 417 | var m, max, increment float64 418 | rui := 5 419 | m = maxstack(data) 420 | max = roundup(m, 100) 421 | if max > 2 { 422 | increment = roundup(max/float64(rui), 10) 423 | } else { 424 | increment = 0.4 425 | } 426 | return 0, max, increment 427 | } 428 | 429 | // roundup rounds a floating point number up 430 | func roundup(n float64, m int64) float64 { 431 | i := int64(n) 432 | if i <= 2 { 433 | return 2 434 | } 435 | for ; i%m != 0; i++ { 436 | } 437 | return float64(i) 438 | } 439 | 440 | // stack value returns the values from the value string of a stack 441 | func stackvalues(s string) []float64 { 442 | v := strings.Split(s, "/") 443 | if len(v) <= 0 { 444 | return nil 445 | } 446 | vals := make([]float64, len(v)) 447 | for i, x := range v { 448 | f, err := strconv.ParseFloat(x, 64) 449 | if err != nil { 450 | vals[i] = 0 451 | } else { 452 | vals[i] = f 453 | } 454 | } 455 | return vals 456 | } 457 | 458 | // colorange evenly distributes opacity across a range of values 459 | func colorange(start, end float64, n int) []float64 { 460 | v := make([]float64, n) 461 | v[0] = start 462 | v[n-1] = end 463 | if n == 2 { 464 | return v 465 | } 466 | incr := (end - start) / float64(n-1) 467 | for i := 1; i < n-1; i++ { 468 | v[i] = v[i-1] + incr 469 | } 470 | return v 471 | } 472 | 473 | // textlimit returns an elided string 474 | func textlimit(s string, n int) string { 475 | l := len(s) 476 | if l <= n { 477 | return s 478 | } 479 | 480 | return s[0:n-3] + "..." 481 | } 482 | 483 | // init sets up the command flags 484 | func init() { 485 | flag.StringVar(&bgcolor, "bg", "white", "background color") 486 | flag.StringVar(&barcolor, "bc", "rgb(200,200,200)", "bar color") 487 | flag.StringVar(&valformat, "vfmt", "%v", "value format") 488 | flag.IntVar(&width, "w", 1024, "width") 489 | flag.IntVar(&height, "h", 800, "height") 490 | flag.IntVar(&barheight, "bh", 20, "bar height") 491 | flag.IntVar(&gutter, "g", 5, "gutter") 492 | flag.IntVar(&cornerRadius, "cr", 0, "corner radius") 493 | flag.IntVar(&fontsize, "f", 18, "fontsize (px)") 494 | flag.BoolVar(&showscale, "showscale", false, "show scale") 495 | flag.BoolVar(&showgrid, "showgrid", false, "show grid") 496 | flag.BoolVar(&showdata, "showdata", true, "show data values") 497 | flag.BoolVar(&showtitle, "showtitle", false, "show title") 498 | flag.BoolVar(&endtitle, "endtitle", false, "align title to the end") 499 | flag.BoolVar(&trace, "trace", false, "show name/value pairs") 500 | flag.BoolVar(&stick, "stick", false, "use ball and stick") 501 | flag.StringVar(&inbar, "inbar", "", "data in bar format") 502 | flag.StringVar(&title, "t", "", "title") 503 | } 504 | 505 | // for every input file (or stdin), draw a bar graph 506 | // as specified by command flags 507 | func main() { 508 | flag.Parse() 509 | canvas := svg.New(os.Stdout) 510 | canvas.Start(width, height) 511 | canvas.Rect(0, 0, width, height, "fill:"+bgcolor) 512 | canvas.Gstyle(fmt.Sprintf(gstyle, fontsize)) 513 | if len(flag.Args()) == 0 { 514 | dobc("", canvas) 515 | } else { 516 | for _, f := range flag.Args() { 517 | dobc(f, canvas) 518 | } 519 | } 520 | canvas.Gend() 521 | canvas.End() 522 | } 523 | --------------------------------------------------------------------------------