func plotLine(xy plotter.XYs) (image.Image, error) { p, err := plot.New() if err != nil { return nil, err } p.HideAxes() p.BackgroundColor = &color.RGBA{0, 0, 0, 255} //s, err := NewSparkLines(xy) s, err := plotter.NewLine(xy) if err != nil { return nil, err } s.Color = &color.RGBA{0, 255, 0, 128} p.Add(s) // Draw the plot to an in-memory image. // _, rows, _ := terminal.GetSize(0) charWidth := optCharWidth charHeight := optCharHeight //width := cols * charWidth height := optRows * charHeight img := image.NewRGBA(image.Rect(0, 0, 5+(len(xy)*charWidth), height)) canvas := vgimg.NewImage(img) da := plot.MakeDrawArea(canvas) p.Draw(da) return img, nil }
func main() { rand.Seed(int64(0)) p, err := plot.New() if err != nil { panic(err) } p.Title.Text = "Plotutil example" p.X.Label.Text = "X" p.Y.Label.Text = "Y" err = plotutil.AddLinePoints(p, "First", randomPoints(15)) if err != nil { panic(err) } cnvs, err := vgximg.New(4*96, 4*96, "Example") if err != nil { panic(err) } p.Draw(plot.MakeDrawArea(cnvs)) cnvs.Paint() time.Sleep(5 * time.Second) err = plotutil.AddLinePoints(p, "Second", randomPoints(15), "Third", randomPoints(15)) if err != nil { panic(err) } p.Draw(plot.MakeDrawArea(cnvs)) cnvs.Paint() time.Sleep(10 * time.Second) // Save the plot to a PNG file. // if err := p.Save(4, 4, "points.png"); err != nil { // panic(err) // } }
// drawPlots draws the plots to an image. func drawPlots(img draw.Image) { c := vgimg.NewImage(img) da := plot.MakeDrawArea(c) textAreaSize := vg.Points(12) da.Min.Y += textAreaSize da.Size.Y -= textAreaSize left := da left.Size.X /= 2 ps[0].plot.Draw(left) ps[0].dataArea = ps[0].plot.DataDrawArea(left) right := da right.Min.X = left.Min.X + left.Size.X right.Size.X /= 2 ps[1].plot.Draw(right) ps[1].dataArea = ps[1].plot.DataDrawArea(right) }
// Report builds up a plot of the response times of the requests // in SVG format and writes it to out func (r *TimingsPlotReporter) Report(out io.Writer) error { timestamps := make([]time.Time, 0) timings := make([]time.Duration, 0) for e := r.responses.Front(); e != nil; e = e.Next() { r := e.Value.(*result) timestamps = append(timestamps, r.timestamp) timings = append(timings, r.timing) } p, err := plot.New() if err != nil { return err } pts := make(plotter.XYs, len(timestamps)) for i := 0; i < len(pts); i++ { pts[i].X = timestamps[i].Sub(timestamps[0]).Seconds() pts[i].Y = timings[i].Seconds() * 1000 } line, err := plotter.NewLine(pts) if err != nil { return err } line.Color = plotutil.Color(1) p.Add(line) p.X.Padding = vg.Length(3.0) p.X.Label.Text = "Time elapsed" p.Y.Padding = vg.Length(3.0) p.Y.Label.Text = "Latency (ms)" w, h := vg.Millimeters(float64(len(timestamps))), vg.Centimeters(12.0) canvas := vgsvg.New(w, h) p.Draw(plot.MakeDrawArea(canvas)) _, err = canvas.WriteTo(out) return err }
func drawPng(b *bytes.Buffer, p *plot.Plot, width, height float64) { w, h := vg.Inches(width), vg.Inches(height) c := vgimg.PngCanvas{Canvas: vgimg.New(w, h)} p.Draw(plot.MakeDrawArea(c)) c.WriteTo(b) }
func boxplot(path string, sets []set) error { var ( fiveEnds = make([]plotter.Values, len(sets)) threeEnds = make([]plotter.Values, len(sets)) err error ln int ) for i := range sets { fiveEnds[i], err = plotter.CopyValues(&normalised{vals: sets[i].fiveEnd}) if err != nil { return err } threeEnds[i], err = plotter.CopyValues(&normalised{vals: sets[i].threeEnd}) if err != nil { return err } if i == 0 { ln = fiveEnds[i].Len() } if fiveEnds[i].Len() != threeEnds[i].Len() || fiveEnds[i].Len() != ln { return errors.New("missing values") } } font, err := vg.MakeFont("Helvetica", 10) if err != nil { return err } titleFont, err := vg.MakeFont("Helvetica", 12) if err != nil { return err } style := plot.TextStyle{Color: color.Gray{0}, Font: font} p, err := plot.New() if err != nil { return err } p.Title.Text = titles[filter] p.Title.TextStyle = plot.TextStyle{Color: color.Gray{0}, Font: titleFont} p.X.Label.Text = "Length Offset" p.Y.Label.Text = "Relative Frequency" p.X.Label.TextStyle = style p.Y.Label.TextStyle = style p.X.Tick.Label = style p.Y.Tick.Label = style p.Legend.TextStyle = style type boxPair struct{ five, three *plotter.BoxPlot } var boxes []boxPair for i := 0; i < ln; i++ { fiveEnd := make(plotter.Values, len(sets)) threeEnd := make(plotter.Values, len(sets)) for j := range sets { fiveEnd[j] = fiveEnds[j][i] threeEnd[j] = threeEnds[j][i] } boxFivePrime, err := plotter.NewBoxPlot(1, float64(i), fiveEnd) // A non-zero width is required to prevent the creation failing. if err != nil { return err } boxFivePrime.MedianStyle.Width = 0.5 boxFivePrime.BoxStyle.Width = 0.75 boxFivePrime.BoxStyle.Color = plotutil.Color(0) boxThreePrime, err := plotter.NewBoxPlot(1, float64(i), threeEnd) // A non-zero width is required to prevent the creation failing. if err != nil { return err } boxThreePrime.MedianStyle.Width = 0.5 boxThreePrime.BoxStyle.Width = 0.75 boxThreePrime.BoxStyle.Color = plotutil.Color(1) boxes = append(boxes, boxPair{boxFivePrime, boxThreePrime}) p.Add(boxFivePrime, boxThreePrime) } p.Legend.Add("5'-end", &plotter.BarChart{Color: plotutil.Color(0)}) p.Legend.Add("3'-end", &plotter.BarChart{Color: plotutil.Color(1)}) p.Legend.Top = true p.NominalX(func() []string { n := make([]string, ln) for i := 0; i < ln; i++ { if v := i - maxLength; v%5 == 0 { n[i] = fmt.Sprint(v) } } return n }()...) p.X.Width = 0.5 p.X.Tick.Width = 0.5 p.X.Tick.Length = 8 p.Add(&plotter.Grid{Vertical: plotter.DefaultGridLineStyle}) c := vgsvg.New(vg.Centimeters(19), vg.Centimeters(10)) da := plot.MakeDrawArea(c) trX, _ := p.Transforms(&da) w := ((trX(float64(2*maxLength)) - trX(float64(0))) / vg.Length(2*maxLength)) / 3 for _, b := range boxes { b.five.Width = w b.five.Offset = -w / 2 b.three.Width = w b.three.Offset = w / 2 } p.Draw(da) f, err := os.Create(decorate(path, "boxplot.svg", filter)) if err != nil { return err } defer f.Close() _, err = c.WriteTo(f) return err }
func barchart(path string, data set) error { font, err := vg.MakeFont("Helvetica", 10) if err != nil { return err } titleFont, err := vg.MakeFont("Helvetica", 12) if err != nil { return err } style := plot.TextStyle{Color: color.Gray{0}, Font: font} p, err := plot.New() if err != nil { return err } p.Title.Text = titles[filter] p.Title.TextStyle = plot.TextStyle{Color: color.Gray{0}, Font: titleFont} p.X.Label.Text = "Length Offset" p.Y.Label.Text = "Relative Frequency" p.X.Label.TextStyle = style p.Y.Label.TextStyle = style p.X.Tick.Label = style p.Y.Tick.Label = style p.Legend.TextStyle = style barsFivePrime, err := plotter.NewBarChart(&normalised{vals: data.fiveEnd}, 1) // A non-zero width is required to prevent the creation failing. if err != nil { return err } barsFivePrime.LineStyle.Width = vg.Length(0) barsFivePrime.Color = plotutil.Color(0) barsThreePrime, err := plotter.NewBarChart(&normalised{vals: data.threeEnd}, 1) // A non-zero width is required to prevent the creation failing. if err != nil { return err } barsThreePrime.LineStyle.Width = vg.Length(0) barsThreePrime.Color = plotutil.Color(1) p.Add(barsFivePrime, barsThreePrime) p.Legend.Add("5'-end", barsFivePrime) p.Legend.Add("3'-end", barsThreePrime) p.Legend.Top = true p.NominalX(func() []string { n := make([]string, len(data.fiveEnd)) for i := range data.fiveEnd { if v := i - maxLength; v%5 == 0 { n[i] = fmt.Sprint(v) } } return n }()...) c := vgsvg.New(vg.Centimeters(19), vg.Centimeters(10)) da := plot.MakeDrawArea(c) trX, _ := p.Transforms(&da) w := ((trX(float64(2*maxLength)) - trX(float64(0))) / vg.Length(2*maxLength)) / 3 barsFivePrime.Width = w barsFivePrime.Offset = -w / 2 barsThreePrime.Width = w barsThreePrime.Offset = w / 2 p.Draw(da) f, err := os.Create(decorate(path, "barchart.svg", filter)) if err != nil { return err } defer f.Close() _, err = c.WriteTo(f) return err }
func HandleStats(process string, db *mgo.Database, w http.ResponseWriter, r *http.Request) error { r.ParseForm() amount := 2 chartwidth := 800 chartheight := 600 if r.Form.Get("amount") != "" { pamount, err := strconv.ParseInt(r.Form.Get("amount"), 10, 16) if err == nil { amount = int(pamount) } } if r.Form.Get("chartwidth") != "" { pchartwidth, err := strconv.ParseInt(r.Form.Get("chartwidth"), 10, 16) if err == nil { chartwidth = int(pchartwidth) } } if r.Form.Get("chartheight") != "" { pchartheight, err := strconv.ParseInt(r.Form.Get("chartheight"), 10, 16) if err == nil { chartheight = int(pchartheight) } } if chartwidth < 40 { chartwidth = 40 } if chartheight < 40 { chartheight = 40 } q := &info.StatsQuery{ Process: process, Data: info.SplitParams(r.Form.Get("data")), Period: r.Form.Get("period"), Filters: make(map[string]string), Groups: info.SplitParams(r.Form.Get("group")), Amount: amount, App: r.Form.Get("app"), } output := r.Form.Get("output") // json, chart // filters for fname, _ := range r.Form { if strings.HasPrefix(fname, "f_") { q.Filters[strings.TrimPrefix(fname, "f_")] = r.Form.Get(fname) } } res, err := info.QueryStats(db, q) if err != nil { return err } if output == "chart" { // output chart p, err := plot.New() if err != nil { return err } p.Title.Text = fmt.Sprintf("Chart %s - %s", r.Form.Get("data"), q.Period) p.X.Label.Text = fmt.Sprintf("Day (%s to %s)", res.StartDate.String(), res.EndDate.String()) //p.X.Tick.Marker = plot.ConstantTicks(res.Ticks()) for didx, ditem := range q.Data { if resinfo, ok := res.Result.(*info.InfoResult); ok { resinfo.SetPlotItem(ditem) err = InfoAddLinePoints(p, didx, ditem, resinfo) if err != nil { return err } } else if resgroup, ok := res.Result.(*info.InfoResultGroup); ok { for rgidx, rg := range resgroup.Group { rg.SetPlotItem(ditem) err = InfoAddLinePoints(p, didx*len(resgroup.Group)+rgidx, ditem+" - "+rg.GroupId, rg) if err != nil { return err } } } } w.Header().Add("Content-Type", "image/png") width := vg.Length(chartwidth) height := vg.Length(chartheight) c := vgimg.PngCanvas{vgimg.New(width, height)} p.Draw(plot.MakeDrawArea(c)) c.WriteTo(w) } else if output == "table" || output == "atable" { w.Header().Add("Content-Type", "text/html; charset=utf-8") w.Write([]byte("<table border=\"1\">")) for _, ditem := range q.Data { w.Write([]byte("<tr>")) w.Write([]byte(fmt.Sprintf("<td><b>%s</b></td>", html.EscapeString(ditem)))) if resinfo, ok := res.Result.(*info.InfoResult); ok { resinfo.SetPlotItem(ditem) for ridx, rdata := range resinfo.List { var dvalue string if output == "atable" { _, dd := resinfo.XY(ridx) if dd != 0 { dvalue = fmt.Sprintf("%.1f", dd) } else { dvalue = "0" } } else { dvalue = fmt.Sprintf("%v", rdata[ditem]) } if dvalue == "0" { dvalue = "-" } w.Write([]byte(fmt.Sprintf("<td align=\"center\">%s</td>", html.EscapeString(dvalue)))) } w.Write([]byte("</tr>")) } else if resgroup, ok := res.Result.(*info.InfoResultGroup); ok { w.Write([]byte("</tr>")) for _, rg := range resgroup.Group { w.Write([]byte(fmt.Sprintf("<tr><td>%s</td>", rg.GroupId))) rg.SetPlotItem(ditem) for ridx, rdata := range rg.List { var dvalue string if output == "atable" { _, dd := rg.XY(ridx) if dd != 0 { dvalue = fmt.Sprintf("%.1f", dd) } else { dvalue = "0" } } else { dvalue = fmt.Sprintf("%v", rdata[ditem]) } if dvalue == "0" { dvalue = "-" } w.Write([]byte(fmt.Sprintf("<td align=\"center\">%s</td>", html.EscapeString(dvalue)))) } w.Write([]byte("</tr>")) } } } w.Write([]byte("</table>")) } else { // output json data stenc, err := json.Marshal(InfoResponse{ErrorCode: 0, Data: res.Result}) if err != nil { return fmt.Errorf("Error encoding json data: %s", err) } w.Header().Add("Content-Type", "application/json; charset=utf-8") w.Write(stenc) } return nil }
func RunServer(config *Config) { // manually load fonts from resource for _, fontasset := range AssetNames() { if strings.HasPrefix(fontasset, "res/fonts/") { fontname := strings.TrimSuffix(path.Base(fontasset), path.Ext(fontasset)) fontbytes, err := Asset(fontasset) if err != nil { panic(err) } fontreader := bytes.NewReader(fontbytes) vg.LoadFont(fontname, fontreader) } } // create memory caches homeCache := cache.New(10*time.Minute, 5*time.Minute) categoryCache := cache.New(30*time.Minute, 30*time.Minute) detailCache := cache.New(15*time.Minute, 10*time.Minute) initialCheck := func(w http.ResponseWriter, r *http.Request) bool { // clear cache if requested nocache := r.Form.Get("nocache") if nocache == "1" { homeCache.Flush() categoryCache.Flush() detailCache.Flush() } return true } http.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) { http.Error(w, "Not found", 404) }) http.HandleFunc("/favicon.png", func(w http.ResponseWriter, r *http.Request) { http.Error(w, "Not found", 404) }) http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { //config.Logger.Printf("Connection: %s\n", r.URL.String()) var err error r.ParseForm() if !initialCheck(w, r) { return } csession := config.Session.Clone() defer csession.Close() cat := r.Form.Get("category") chart := r.Form.Get("chart") p_page := r.Form.Get("pg") page := 1 if p_page != "" { t_page, err := strconv.ParseInt(p_page, 10, 32) if err != nil { http.Error(w, err.Error(), 500) return } page = int(t_page) } if page < 1 { page = 1 } i := fstopinfo.NewInfo(config.Logger, csession) i.Database = config.Database // load categories var c fstopinfo.FSCategoryList catcache, catfound := categoryCache.Get("category") if catfound { c = catcache.(fstopinfo.FSCategoryList) } if c == nil { c, err = i.Categories() if err != nil { http.Error(w, err.Error(), 500) return } if c != nil { categoryCache.Set("category", c, 0) } } if c == nil { http.Error(w, "Could not load categories", 500) return } // load home var d fstopimp.FSTopStatsList var dcache interface{} var dfound bool var dname string if cat == "" { dname = "index" } else { // check if category exists if !c.Exists(cat) { http.Error(w, "Category not found", 404) return } dname = cat } dcache, dfound = homeCache.Get(dname) if dfound { d = dcache.(fstopimp.FSTopStatsList) } // data not on cache, load if d == nil { if cat == "" { d, err = i.Top(config.TopId) } else { d, err = i.TopCategory(config.TopId, cat) } if err != nil { http.Error(w, err.Error(), 500) return } if len(d) > 0 { homeCache.Set(dname, d, 0) } } pagecount := d.PageCount(config.PageSize) d = d.Paged(page, config.PageSize) w.Header().Add("Content-Type", "text/html; charset=utf-8") var body *bytes.Buffer = new(bytes.Buffer) tmpldata := map[string]interface{}{ "Title": config.Title, "page": page, "categories": c, } fmt.Fprintln(body, "<table class=\"main\">") fmt.Fprintln(body, "<tr><th>Chart</th><th>Title</th><th width=\"8%\">Added</th><th width=\"5%\">Score</th><th width=\"5%\">Count</th><th width=\"5%\">Comm.</th></tr>") for _, ii := range d { fmt.Fprintf(body, "<tr>\n") if chart == "" { fmt.Fprintf(body, "<td><img style=\"height: 107px;\" src=\"/chart?id=%s&size=short\"></td>", ii.Id) } fmt.Fprintf(body, "<td><a href=\"%s\">%s</a> <a href=\"/view?id=%s\">[data]</a> <a href=\"/chart?id=%s\">[chart]</a></td>"+ "<td align=\"center\" style=\"%s\">%s</td>"+ "<td align=\"right\">%d</td><td align=\"center\">%d</td><td align=\"center\">%d</td>\n", ii.Link, ii.Title, ii.Id, ii.Id, StyleAddDate(ii.Last.AddDate), FormatAddDate(ii.Last.AddDate), ii.Score, ii.Count, ii.Last.Comments) fmt.Fprintf(body, "</tr>\n") if chart == "1" { fmt.Fprintf(body, "<tr><td colspan=\"5\" align=\"center\"><img src=\"/chart?id=%s&size=small\"/></td></tr>\n", ii.Id) } } fmt.Fprintln(body, "</table>") pageparams := url.Values{} if cat != "" { pageparams.Add("category", cat) } if page > 1 { pageparams.Set("pg", "1") tmpldata["page_first"] = fmt.Sprintf("/?%s", pageparams.Encode()) } if page > 1 { pageparams.Set("pg", strconv.Itoa(page-1)) tmpldata["page_prev"] = fmt.Sprintf("/?%s", pageparams.Encode()) } if len(d) > 0 && page < pagecount { pageparams.Set("pg", strconv.Itoa(page+1)) tmpldata["page_next"] = fmt.Sprintf("/?%s", pageparams.Encode()) } if page != pagecount { pageparams.Set("pg", strconv.Itoa(pagecount)) tmpldata["page_last"] = fmt.Sprintf("/?%s", pageparams.Encode()) } tmpl := LoadTemplates("index") tmpldata["Body"] = template.HTML(body.String()) err = tmpl.ExecuteTemplate(w, "index", tmpldata) if err != nil { http.Error(w, "Error processing template", 500) return } }) http.HandleFunc("/view", func(w http.ResponseWriter, r *http.Request) { r.ParseForm() if !initialCheck(w, r) { return } csession := config.Session.Clone() defer csession.Close() w.Header().Add("Content-Type", "text/html; charset=utf-8") id := r.Form.Get("id") if id == "" { http.Error(w, "ID not sent", 500) return } i := fstopinfo.NewInfo(config.Logger, csession) i.Database = config.Database var d []*fstopinfo.FSInfoHistory var err error dcache, found := detailCache.Get(id) if found { d = dcache.([]*fstopinfo.FSInfoHistory) } if d == nil { d, err = i.History(id, config.HistoryDays) if err != nil { http.Error(w, err.Error(), 500) return } if d != nil { detailCache.Set(id, d, 0) } } if d == nil { http.Error(w, "Not found", 500) return } var body *bytes.Buffer = new(bytes.Buffer) fmt.Fprintln(body, "<table class=\"main\" border=\"1\">") fmt.Fprintln(body, "<tr><th>Date</th><th>Hour</th><th>Seeders</th><th>Leechers</th><th>Complete</th><th>Comments</th></tr>") var last *fstopinfo.FSInfoHistory first := true for _, ii := range d { //if ii.Item != nil { if ii.Item != nil && first { fmt.Fprintf(body, "<tr><td colspan=\"7\">%s <a href=\"%s\">[goto]</a></td></tr>", strings.TrimSpace(ii.Item.Title), ii.Item.Link) first = false } if last != nil { if ii.Item != nil && last.Item != nil { //fmt.Printf("%s - [%d] [%d] [%d]\n", item.Title, pi.Seeders-item.Last.Seeders, //pi.Leechers-item.Last.Leechers, pi.Complete-item.Last.Complete) seeders := int64(ii.Item.Seeders - last.Item.Seeders) leechers := int64(ii.Item.Leechers - last.Item.Leechers) complete := int64(ii.Item.Complete - last.Item.Complete) comments := int64(ii.Item.Comments - last.Item.Comments) fmt.Fprintf(body, "<tr><td>%s</td><td>%d</td><td>%d (%d)</td><td>%d (%d)</td><td>%d (%d)</td><td>%d (%d)</td></tr>", ii.Date, ii.Hour, ii.Item.Seeders, seeders, ii.Item.Leechers, leechers, ii.Item.Complete, complete, ii.Item.Comments, comments) } else if ii.Item != nil && last.Item == nil { fmt.Fprintf(body, "<tr><td>%s</td><td>%d</td><td>%d (-)</td><td>%d (-)</td><td>%d (-)</td><td>%d (-)</td></tr>", ii.Date, ii.Hour, ii.Item.Seeders, ii.Item.Leechers, ii.Item.Complete, ii.Item.Comments) } else { fmt.Fprintf(body, "<tr><td>%s</td><td>%d</td></tr>", ii.Date, ii.Hour) } } last = ii //} } fmt.Fprintln(body, "</table>") tmpl := LoadTemplates("base") tmpldata := map[string]interface{}{ "Title": config.Title, "Body": template.HTML(body.String()), } err = tmpl.ExecuteTemplate(w, "base", tmpldata) if err != nil { http.Error(w, "Error processing template", 500) return } }) http.HandleFunc("/chart", func(w http.ResponseWriter, r *http.Request) { r.ParseForm() if !initialCheck(w, r) { return } csession := config.Session.Clone() defer csession.Close() id := r.Form.Get("id") if id == "" { w.Header().Add("Content-Type", "text/html; charset=utf-8") http.Error(w, "ID not sent", 500) return } size := r.Form.Get("size") i := fstopinfo.NewInfo(config.Logger, csession) i.Database = config.Database var d []*fstopinfo.FSInfoHistory var err error dcache, found := detailCache.Get(id) if found { d = dcache.([]*fstopinfo.FSInfoHistory) } if d == nil { d, err = i.History(id, config.HistoryDays) if err != nil { w.Header().Add("Content-Type", "text/html; charset=utf-8") http.Error(w, err.Error(), 500) return } if d != nil { detailCache.Set(id, d, 0) } } if d == nil { w.Header().Add("Content-Type", "text/html; charset=utf-8") http.Error(w, "Not found", 500) return } p, err := plot.New() if err != nil { panic(err) } p.Title.Text = "Chart" p.X.Label.Text = "Time" p.Y.Label.Text = "Amount" c_seeders := make(plotter.XYs, 0) c_leechers := make(plotter.XYs, 0) c_complete := make(plotter.XYs, 0) c_comments := make(plotter.XYs, 0) var last *fstopinfo.FSInfoHistory first := true cttotal := int32(0) for _, ii := range d { if ii.Item != nil { cttotal++ if first { p.Title.Text = strings.TrimSpace(ii.Item.Title) first = false } if last != nil && last.Item != nil { c_seeders = append(c_seeders, struct{ X, Y float64 }{float64(cttotal), float64(ii.Item.Seeders)}) c_leechers = append(c_leechers, struct{ X, Y float64 }{float64(cttotal), float64(ii.Item.Leechers)}) c_complete = append(c_complete, struct{ X, Y float64 }{float64(cttotal), float64(ii.Item.Complete - last.Item.Complete)}) c_comments = append(c_comments, struct{ X, Y float64 }{float64(cttotal), float64(ii.Item.Comments)}) } last = ii } } w.Header().Add("Content-Type", "image/png") pl_seeders, err := plotter.NewLine(c_seeders) if err != nil { http.Error(w, err.Error(), 500) return } pl_seeders.LineStyle.Width = vg.Length(1) pl_seeders.LineStyle.Color = color.RGBA{R: 255, A: 255} p.Add(pl_seeders) p.Legend.Add("Seeders", pl_seeders) pl_leechers, err := plotter.NewLine(c_leechers) if err != nil { http.Error(w, err.Error(), 500) return } pl_leechers.LineStyle.Width = vg.Length(1) pl_leechers.LineStyle.Color = color.RGBA{G: 255, A: 255} p.Add(pl_leechers) p.Legend.Add("Leechers", pl_leechers) pl_complete, err := plotter.NewLine(c_complete) if err != nil { http.Error(w, err.Error(), 500) return } pl_complete.LineStyle.Width = vg.Length(1) pl_complete.LineStyle.Color = color.RGBA{B: 255, A: 255} p.Add(pl_complete) p.Legend.Add("@Complete", pl_complete) pl_comments, err := plotter.NewLine(c_comments) if err != nil { http.Error(w, err.Error(), 500) return } pl_comments.LineStyle.Width = vg.Length(1) pl_comments.LineStyle.Color = color.RGBA{R: 255, B: 255, A: 255} p.Add(pl_comments) p.Legend.Add("Comments", pl_comments) width := vg.Length(640) height := vg.Length(480) if size == "small" { width = vg.Length(640) height = vg.Length(160) } else if size == "short" { width = vg.Length(200) height = vg.Length(80) p.Title.Text = "" p.X.Label.Text = "" p.Y.Label.Text = "" } c := vgimg.PngCanvas{vgimg.New(width, height)} p.Draw(plot.MakeDrawArea(c)) c.WriteTo(w) }) http.HandleFunc("/res/", func(w http.ResponseWriter, r *http.Request) { p := strings.Replace(path.Clean(r.URL.Path), "/res/", "res/files/", 1) d, err := Asset(p) if err != nil { http.Error(w, "Error loading resource", 500) return } w.Header().Add("Content-Type", mime.TypeByExtension(path.Ext(p))) w.Write(d) //fmt.Fprintf(w, "RES %s\n", p) }) http.ListenAndServe(fmt.Sprintf("localhost:%d", config.Port), nil) }
func marshalPNG(r *http.Request, results []*metricData) []byte { p, err := plot.New() if err != nil { panic(err) } // set bg/fg colors bgcolor := string2Color(getString(r.FormValue("bgcolor"), "black")) p.BackgroundColor = bgcolor fgcolorstr := getString(r.FormValue("fgcolor"), "white") fgcolor := string2Color(fgcolorstr) p.Title.Color = fgcolor p.X.LineStyle.Color = fgcolor p.Y.LineStyle.Color = fgcolor p.X.Tick.LineStyle.Color = fgcolor p.Y.Tick.LineStyle.Color = fgcolor p.X.Tick.Label.Color = fgcolor p.Y.Tick.Label.Color = fgcolor p.X.Label.Color = fgcolor p.Y.Label.Color = fgcolor p.Legend.Color = fgcolor // set grid grid := plotter.NewGrid() grid.Vertical.Color = fgcolor grid.Horizontal.Color = fgcolor p.Add(grid) // line mode (ikruglow) TODO check values lineMode := getString(r.FormValue("lineMode"), "slope") // width and height width := getInt(r.FormValue("width"), 330) height := getInt(r.FormValue("height"), 250) // need different timeMarker's based on step size p.Title.Text = r.FormValue("title") p.X.Tick.Marker = makeTimeMarker(results[0].GetStepTime()) hideLegend := getBool(r.FormValue("hideLegend"), false) graphOnly := getBool(r.FormValue("graphOnly"), false) if graphOnly { p.HideAxes() } if len(results) == 1 && results[0].color == "" { results[0].color = fgcolorstr } var lines []plot.Plotter for i, r := range results { l := NewResponsePlotter(r) l.LineStyle.Color = fgcolor // consolidate datapoints l.maybeConsolidateData(width) if r.drawAsInfinite { l.lineMode = "drawAsInfinite" } else { l.lineMode = lineMode } if r.color != "" { l.Color = string2Color(r.color) } else { l.Color = plotutil.Color(i) } lines = append(lines, l) if !graphOnly && !hideLegend { p.Legend.Add(r.GetName(), l) } } p.Add(lines...) p.Y.Max *= 1.05 p.Y.Min *= 0.95 // Draw the plot to an in-memory image. img := image.NewRGBA(image.Rect(0, 0, width, height)) da := plot.MakeDrawArea(vgimg.NewImage(img)) p.Draw(da) var b bytes.Buffer if err := png.Encode(&b, img); err != nil { panic(err) } return b.Bytes() }