1
//svgplot -- plot data (a stream of x,y coordinates)
13
"github.com/ajstarks/svgo"
16
// rawdata defines data as float64 x,y coordinates
22
type options map[string]bool
23
type attributes map[string]string
24
type measures map[string]int
26
// plotset defines plot metadata
34
canvas = svg.New(os.Stdout)
36
plotattr = attributes{}
38
ps = plotset{plotopt, plotattr, plotnum}
39
plotw, ploth, plotc, gwidth, gheight, gutter, beginx, beginy int
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%"
52
// init initializes command flags and sets default options
56
showx := flag.Bool("showx", false, "show the xaxis")
57
showy := flag.Bool("showy", false, "show the yaxis")
58
showbar := flag.Bool("showbar", false, "show data bars")
59
area := flag.Bool("area", false, "area chart")
60
connect := flag.Bool("connect", true, "connect data points")
61
showdot := flag.Bool("showdot", false, "show dots")
62
showbg := flag.Bool("showbg", true, "show the background color")
63
showfile := flag.Bool("showfile", false, "show the filename")
64
sameplot := flag.Bool("sameplot", false, "plot on the same frame")
67
bgcolor := flag.String("bgcolor", "rgb(240,240,240)", "plot background color")
68
barcolor := flag.String("barcolor", "gray", "bar color")
69
dotcolor := flag.String("dotcolor", "black", "dot color")
70
linecolor := flag.String("linecolor", "gray", "line color")
71
areacolor := flag.String("areacolor", "gray", "area color")
72
font := flag.String("font", "Calibri,sans", "font")
73
labelcolor := flag.String("labelcolor", "black", "label color")
74
plotlabel := flag.String("label", "", "plot label")
77
dotsize := flag.Int("dotsize", 2, "dot size")
78
linesize := flag.Int("linesize", 2, "line size")
79
barsize := flag.Int("barsize", 2, "bar size")
80
fontsize := flag.Int("fontsize", 11, "font size")
81
xinterval := flag.Int("xint", 10, "x axis interval")
82
yinterval := flag.Int("yint", 4, "y axis interval")
85
flag.IntVar(&beginx, "bx", 100, "initial x")
86
flag.IntVar(&beginy, "by", 50, "initial y")
87
flag.IntVar(&plotw, "pw", 500, "plot width")
88
flag.IntVar(&ploth, "ph", 500, "plot height")
89
flag.IntVar(&plotc, "pc", 2, "plot columns")
90
flag.IntVar(&gutter, "gutter", ploth/10, "gutter")
91
flag.IntVar(&gwidth, "width", 1024, "canvas width")
92
flag.IntVar(&gheight, "height", 768, "canvas height")
96
// fill in the plotset -- all options, attributes, and sizes
97
plotopt["showx"] = *showx
98
plotopt["showy"] = *showy
99
plotopt["showbar"] = *showbar
100
plotopt["area"] = *area
101
plotopt["connect"] = *connect
102
plotopt["showdot"] = *showdot
103
plotopt["showbg"] = *showbg
104
plotopt["showfile"] = *showfile
105
plotopt["sameplot"] = *sameplot
107
plotattr["bgcolor"] = *bgcolor
108
plotattr["barcolor"] = *barcolor
109
plotattr["linecolor"] = *linecolor
110
plotattr["dotcolor"] = *dotcolor
111
plotattr["areacolor"] = *areacolor
112
plotattr["font"] = *font
113
plotattr["label"] = *plotlabel
114
plotattr["labelcolor"] = *labelcolor
116
plotnum["dotsize"] = *dotsize
117
plotnum["linesize"] = *linesize
118
plotnum["fontsize"] = *fontsize
119
plotnum["xinterval"] = *xinterval
120
plotnum["yinterval"] = *yinterval
121
plotnum["barsize"] = *barsize
124
// fmap maps world data to document coordinates
125
func fmap(value float64, low1 float64, high1 float64, low2 float64, high2 float64) float64 {
126
return low2 + (high2-low2)*(value-low1)/(high1-low1)
129
// doplot opens a file and makes a plot
130
func doplot(x, y int, location string) {
133
if len(location) > 0 {
134
if plotopt["showfile"] {
135
plotattr["label"] = location
137
f, err = os.Open(location)
142
fmt.Fprintf(os.Stderr, "%v\n", err)
145
nd, data := readxy(f)
149
plot(x, y, plotw, ploth, ps, data)
153
// plot places a plot at the specified location with the specified dimemsions
154
// usinng the specified settings, using the specified data
155
func plot(x, y, w, h int, settings plotset, d []rawdata) {
158
fmt.Fprintf(os.Stderr, "%d is not enough points to plot\n", len(d))
161
// Compute the minima and maxima
162
maxx, minx := d[0].x, d[0].x
163
maxy, miny := d[0].y, d[0].y
164
for _, v := range d {
179
// Prepare for a area or line chart by allocating
180
// polygon coordinates; for the hrizon plot, you need two extra coordinates
182
needpoly := settings.opt["area"] || settings.opt["connect"]
183
var xpoly, ypoly []int
185
xpoly = make([]int, nd+2)
186
ypoly = make([]int, nd+2)
187
// preload the extrema of the polygon,
188
// the bottom left and bottom right of the plot's rectangle
194
// Draw the plot's bounding rectangle
195
if settings.opt["showbg"] && !settings.opt["sameplot"] {
196
canvas.Rect(x, y, w, h, "fill:"+settings.attr["bgcolor"])
198
// Loop through the data, drawing items as specified
200
canvas.Gstyle(fmt.Sprintf(globalfmt,
201
settings.attr["font"], settings.size["fontsize"], settings.size["linesize"]))
202
for i, v := range d {
203
xp := int(fmap(v.x, minx, maxx, float64(x), float64(x+w)))
204
yp := int(fmap(v.y, miny, maxy, float64(y), float64(y-h)))
210
if settings.opt["showbar"] {
211
canvas.Line(xp, yp+h, xp, y+h,
212
fmt.Sprintf(barfmt, settings.attr["barcolor"], settings.size["barsize"]))
214
if settings.opt["showdot"] {
215
canvas.Circle(xp, yp+h, settings.size["dotsize"], "fill:"+settings.attr["dotcolor"])
217
if settings.opt["showx"] {
218
if i%settings.size["xinterval"] == 0 {
219
canvas.Text(xp, (y+h)+(spacer*2), fmt.Sprintf("%d", int(v.x)), "text-anchor:middle")
220
canvas.Line(xp, (y + h), xp, (y+h)+spacer, ticfmt)
224
// Done constructing the points for the area or line plots, display them in one shot
225
if settings.opt["area"] {
226
canvas.Polygon(xpoly, ypoly, "fill:"+settings.attr["areacolor"])
229
if settings.opt["connect"] {
230
canvas.Polyline(xpoly[1:nd+1], ypoly[1:nd+1], linestyle+settings.attr["linecolor"])
232
// Put on the y axis labels, if specified
233
if settings.opt["showy"] {
234
bot := math.Floor(miny)
235
top := math.Ceil(maxy)
237
interval := yrange / float64(settings.size["yinterval"])
238
canvas.Gstyle(labelfmt)
239
for yax := bot; yax <= top; yax += interval {
240
yaxp := fmap(yax, bot, top, float64(y), float64(y-h))
241
canvas.Text(x-spacer, int(yaxp)+h, fmt.Sprintf("%.1f", yax), textfmt)
242
canvas.Line(x-spacer, int(yaxp)+h, x, int(yaxp)+h)
246
// Finally, tack on the label, if specified
247
if len(settings.attr["label"]) > 0 {
248
canvas.Text(x, y+spacer, settings.attr["label"], "font-size:120%;fill:"+settings.attr["labelcolor"])
254
// readxy reads coordinates (x,y float64 values) from a io.Reader
255
func readxy(f io.Reader) (int, []rawdata) {
261
data := make([]rawdata, 1)
262
for ; err == nil; n++ {
264
data = append(data, r)
266
nf, err = fmt.Fscan(f, &data[n].x, &data[n].y)
271
return n - 1, data[0 : n-1]
274
// plotgrid places plots on a grid, governed by a number of columns.
275
func plotgrid(x, y int, files []string) {
277
for i, f := range files {
278
if i > 0 && i%plotc == 0 && !plotopt["sameplot"] {
280
y += (ploth + gutter)
283
if !plotopt["sameplot"] {
284
px += (plotw + gutter)
289
// main plots data from specified files or standard input in a
290
// grid where plotc specifies the number of columns.
292
canvas.Start(gwidth, gheight)
293
canvas.Rect(0, 0, gwidth, gheight, "fill:white")
294
filenames := flag.Args()
295
if len(filenames) == 0 {
296
doplot(beginx, beginy, "")
298
plotgrid(beginx, beginy, filenames)