~ubuntu-branches/ubuntu/vivid/juju-core/vivid-updates

« back to all changes in this revision

Viewing changes to src/github.com/ajstarks/svgo/svgplot/svgplot.go

  • Committer: Package Import Robot
  • Author(s): Curtis C. Hovey
  • Date: 2015-09-29 19:43:29 UTC
  • mfrom: (47.1.4 wily-proposed)
  • Revision ID: package-import@ubuntu.com-20150929194329-9y496tbic30hc7vp
Tags: 1.24.6-0ubuntu1~15.04.1
Backport of 1.24.6 from wily. (LP: #1500916, #1497087)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
)
 
51
 
 
52
// init initializes command flags and sets default options
 
53
func init() {
 
54
 
 
55
        // boolean 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")
 
65
 
 
66
        // attributes
 
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")
 
75
 
 
76
        // sizes
 
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")
 
83
 
 
84
        // meta options
 
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")
 
93
 
 
94
        flag.Parse()
 
95
 
 
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
 
106
 
 
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
 
115
 
 
116
        plotnum["dotsize"] = *dotsize
 
117
        plotnum["linesize"] = *linesize
 
118
        plotnum["fontsize"] = *fontsize
 
119
        plotnum["xinterval"] = *xinterval
 
120
        plotnum["yinterval"] = *yinterval
 
121
        plotnum["barsize"] = *barsize
 
122
}
 
123
 
 
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)
 
127
}
 
128
 
 
129
// doplot opens a file and makes a plot
 
130
func doplot(x, y int, location string) {
 
131
        var f *os.File
 
132
        var err error
 
133
        if len(location) > 0 {
 
134
                if plotopt["showfile"] {
 
135
                        plotattr["label"] = location
 
136
                }
 
137
                f, err = os.Open(location)
 
138
        } else {
 
139
                f = os.Stdin
 
140
        }
 
141
        if err != nil {
 
142
                fmt.Fprintf(os.Stderr, "%v\n", err)
 
143
                return
 
144
        }
 
145
        nd, data := readxy(f)
 
146
        f.Close()
 
147
 
 
148
        if nd >= 2 {
 
149
                plot(x, y, plotw, ploth, ps, data)
 
150
        }
 
151
}
 
152
 
 
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) {
 
156
        nd := len(d)
 
157
        if nd < 2 {
 
158
                fmt.Fprintf(os.Stderr, "%d is not enough points to plot\n", len(d))
 
159
                return
 
160
        }
 
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 {
 
165
 
 
166
                if v.x > maxx {
 
167
                        maxx = v.x
 
168
                }
 
169
                if v.y > maxy {
 
170
                        maxy = v.y
 
171
                }
 
172
                if v.x < minx {
 
173
                        minx = v.x
 
174
                }
 
175
                if v.y < miny {
 
176
                        miny = v.y
 
177
                }
 
178
        }
 
179
        // Prepare for a area or line chart by allocating
 
180
        // polygon coordinates; for the hrizon plot, you need two extra coordinates
 
181
        // for the extrema.
 
182
        needpoly := settings.opt["area"] || settings.opt["connect"]
 
183
        var xpoly, ypoly []int
 
184
        if needpoly {
 
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
 
189
                xpoly[0] = x
 
190
                ypoly[0] = y + h
 
191
                xpoly[nd+1] = x + w
 
192
                ypoly[nd+1] = y + h
 
193
        }
 
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"])
 
197
        }
 
198
        // Loop through the data, drawing items as specified
 
199
        spacer := 10
 
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)))
 
205
 
 
206
                if needpoly {
 
207
                        xpoly[i+1] = xp
 
208
                        ypoly[i+1] = yp + h
 
209
                }
 
210
                if settings.opt["showbar"] {
 
211
                        canvas.Line(xp, yp+h, xp, y+h,
 
212
                                fmt.Sprintf(barfmt, settings.attr["barcolor"], settings.size["barsize"]))
 
213
                }
 
214
                if settings.opt["showdot"] {
 
215
                        canvas.Circle(xp, yp+h, settings.size["dotsize"], "fill:"+settings.attr["dotcolor"])
 
216
                }
 
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)
 
221
                        }
 
222
                }
 
223
        }
 
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"])
 
227
        }
 
228
 
 
229
        if settings.opt["connect"] {
 
230
                canvas.Polyline(xpoly[1:nd+1], ypoly[1:nd+1], linestyle+settings.attr["linecolor"])
 
231
        }
 
232
        // Put on the y axis labels, if specified
 
233
        if settings.opt["showy"] {
 
234
                bot := math.Floor(miny)
 
235
                top := math.Ceil(maxy)
 
236
                yrange := top - bot
 
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)
 
243
                }
 
244
                canvas.Gend()
 
245
        }
 
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"])
 
249
        }
 
250
 
 
251
        canvas.Gend()
 
252
}
 
253
 
 
254
// readxy reads coordinates (x,y float64 values) from a io.Reader
 
255
func readxy(f io.Reader) (int, []rawdata) {
 
256
        var (
 
257
                r     rawdata
 
258
                err   error
 
259
                n, nf int
 
260
        )
 
261
        data := make([]rawdata, 1)
 
262
        for ; err == nil; n++ {
 
263
                if n > 0 {
 
264
                        data = append(data, r)
 
265
                }
 
266
                nf, err = fmt.Fscan(f, &data[n].x, &data[n].y)
 
267
                if nf != 2 {
 
268
                        continue
 
269
                }
 
270
        }
 
271
        return n - 1, data[0 : n-1]
 
272
}
 
273
 
 
274
// plotgrid places plots on a grid, governed by a number of columns.
 
275
func plotgrid(x, y int, files []string) {
 
276
        px := x
 
277
        for i, f := range files {
 
278
                if i > 0 && i%plotc == 0 && !plotopt["sameplot"] {
 
279
                        px = x
 
280
                        y += (ploth + gutter)
 
281
                }
 
282
                doplot(px, y, f)
 
283
                if !plotopt["sameplot"] {
 
284
                        px += (plotw + gutter)
 
285
                }
 
286
        }
 
287
}
 
288
 
 
289
// main plots data from specified files or standard input in a
 
290
// grid where plotc specifies the number of columns.
 
291
func main() {
 
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, "")
 
297
        } else {
 
298
                plotgrid(beginx, beginy, filenames)
 
299
        }
 
300
        canvas.End()
 
301
}