// Approximate a circular arc using multiple // cubic Bézier curves, one for each π/2 segment. // // This is from: // http://hansmuller-flex.blogspot.com/2011/04/approximating-circular-arc-with-cubic.html func arc(p *pdf.Path, comp vg.PathComp) { x0 := comp.X + comp.Radius*vg.Length(math.Cos(comp.Start)) y0 := comp.Y + comp.Radius*vg.Length(math.Sin(comp.Start)) p.Line(pdfPoint(x0, y0)) a1 := comp.Start end := a1 + comp.Angle sign := 1.0 if end < a1 { sign = -1.0 } left := math.Abs(comp.Angle) // Square root of the machine epsilon for IEEE 64-bit floating // point values. This is the equality threshold recommended // in Numerical Recipes, if I recall correctly—it's small enough. const epsilon = 1.4901161193847656e-08 for left > epsilon { a2 := a1 + sign*math.Min(math.Pi/2, left) partialArc(p, comp.X, comp.Y, comp.Radius, a1, a2) left -= math.Abs(a2 - a1) a1 = a2 } }
// crosshair draws a plus at the given point. func crosshair(img draw.Image, x, y int, str string) { c := vgimg.NewImage(img) // drawPlots here because NewImage // clears the canvas. Instead, the canvas // should just be stored instead of being // recreated at each redraw. drawPlots(img) c.SetColor(color.RGBA{R: 255, A: 255}) xc := vg.Inches(float64(x) / c.DPI()) yc := vg.Inches(float64(y) / c.DPI()) radius := vg.Points(5) var p vg.Path p.Move(xc-radius, yc) p.Line(xc+radius, yc) c.Stroke(p) p = vg.Path{} p.Move(xc, yc+radius) p.Line(xc, yc-radius) c.Stroke(p) c.SetColor(color.Black) c.FillString(font, vg.Length(0), vg.Length(0), str) }
// Height returns the height of the text when using // the given font. func (sty TextStyle) Height(txt string) vg.Length { nl := textNLines(txt) if nl == 0 { return vg.Length(0) } e := sty.Font.Extents() return e.Height*vg.Length(nl-1) + e.Ascent }
// GlyphBoxes returns a slice of GlyphBoxes, // one for each of the labels, implementing the // plot.GlyphBoxer interface. func (l *Labels) GlyphBoxes(p *plot.Plot) []plot.GlyphBox { bs := make([]plot.GlyphBox, len(l.Labels)) for i, label := range l.Labels { bs[i].X = p.X.Norm(l.XYs[i].X) bs[i].Y = p.Y.Norm(l.XYs[i].Y) w := l.Width(label) h := l.Height(label) bs[i].Rect.Min.X = w*vg.Length(l.XAlign) + l.XOffset bs[i].Rect.Min.Y = h*vg.Length(l.YAlign) + l.YOffset bs[i].Rect.Size.X = w bs[i].Rect.Size.Y = h } return bs }
func (s *SparkLines) Plot(da plot.DrawArea, plt *plot.Plot) { trX, trY := plt.Transforms(&da) w := vg.Length(1) da.SetLineWidth(w) _, _, ymin, ymax := s.DataRange() for _, d := range s.XYs { perc := float64(d.Y-ymin) / float64(ymax-ymin) c := BrightColorGradient.GetInterpolatedColorFor((perc*-1+1)*0.5 + 0.6) da.SetColor(c) // Transform the data x, y coordinate of this bubble // to the corresponding drawing coordinate. x := trX(d.X) y := trY(d.Y * 0.9) //rad := vg.Length(10) var p vg.Path p.Move(x-w, y) p.Line(x-w, 0) //p.Close() da.Stroke(p) //da.StrokeLine2(*sty, x, 0, x, y) } }
func (pt *Dots) Plot(da plot.DrawArea, plt *plot.Plot) { trX, trY := plt.Transforms(&da) da.SetColor(pt.Color) for i := range pt.Y { // Transform the data x, y coordinate of this bubble // to the corresponding drawing coordinate. x := trX(pt.X[i]) y := trY(pt.Y[i]) // Get the radius of this bubble. The radius // is specified in drawing units (i.e., its size // is given as the final size at which it will // be drawn) so it does not need to be transformed. rad := vg.Length(2) // Fill a circle centered at x,y on the draw area. var p vg.Path p.Move(x+rad, y) p.Arc(x, y, rad, 0, 2*math.Pi) p.Close() da.Fill(p) } }
// draw draws the legend to the given DrawArea. func (l *Legend) draw(da DrawArea) { iconx := da.Min.X textx := iconx + l.ThumbnailWidth + l.TextStyle.Width(" ") xalign := 0.0 if !l.Left { iconx = da.Max().X - l.ThumbnailWidth textx = iconx - l.TextStyle.Width(" ") xalign = -1 } textx += l.XOffs iconx += l.XOffs enth := l.entryHeight() y := da.Max().Y - enth if !l.Top { y = da.Min.Y + (enth+l.Padding)*(vg.Length(len(l.entries))-1) } y += l.YOffs icon := &DrawArea{ Canvas: da.Canvas, Rect: Rect{Min: Point{iconx, y}, Size: Point{l.ThumbnailWidth, enth}}, } for _, e := range l.entries { for _, t := range e.thumbs { t.Thumbnail(icon) } yoffs := (enth - l.TextStyle.Height(e.text)) / 2 da.FillText(l.TextStyle, textx, icon.Min.Y+yoffs, xalign, 0, e.text) icon.Min.Y -= enth + l.Padding } }
// radius returns the radius of a bubble by linear interpolation. func (bs *Bubbles) radius(z float64) vg.Length { rng := bs.MaxRadius - bs.MinRadius if bs.MaxZ == bs.MinZ { return rng/2 + bs.MinRadius } d := (z - bs.MinZ) / (bs.MaxZ - bs.MinZ) return vg.Length(d)*rng + bs.MinRadius }
// FillText fills lines of text in the draw area. // The text is offset by its width times xalign and // its height times yalign. x and y give the bottom // left corner of the text befor e it is offset. func (da *DrawArea) FillText(sty TextStyle, x, y vg.Length, xalign, yalign float64, txt string) { txt = strings.TrimRight(txt, "\n") if len(txt) == 0 { return } da.SetColor(sty.Color) ht := sty.Height(txt) y += ht*vg.Length(yalign) - sty.Font.Extents().Ascent nl := textNLines(txt) for i, line := range strings.Split(txt, "\n") { xoffs := vg.Length(xalign) * sty.Font.Width(line) n := vg.Length(nl - i) da.FillString(sty.Font, x+xoffs, y+n*sty.Font.Size, line) } }
// Approximate a circular arc of fewer than π/2 // radians with cubic Bézier curve. func partialArc(p *pdf.Path, x, y, r vg.Length, a1, a2 float64) { a := (a2 - a1) / 2 x4 := r * vg.Length(math.Cos(a)) y4 := r * vg.Length(math.Sin(a)) x1 := x4 y1 := -y4 const k = 0.5522847498 // some magic constant f := k * vg.Length(math.Tan(a)) x2 := x1 + f*y4 y2 := y1 + f*x4 x3 := x2 y3 := -y2 // Rotate and translate points into position. ar := a + a1 sinar := vg.Length(math.Sin(ar)) cosar := vg.Length(math.Cos(ar)) x2r := x2*cosar - y2*sinar + x y2r := x2*sinar + y2*cosar + y x3r := x3*cosar - y3*sinar + x y3r := x3*sinar + y3*cosar + y x4 = r*vg.Length(math.Cos(a2)) + x y4 = r*vg.Length(math.Sin(a2)) + y p.Curve(pdfPoint(x2r, y2r), pdfPoint(x3r, y3r), pdfPoint(x4, y4)) }
// Approximate a circular arc using multiple // cubic Bézier curves, one for each π/2 segment. // // This is from: // http://hansmuller-flex.blogspot.com/2011/04/approximating-circular-arc-with-cubic.html func arc(p *pdf.Path, comp vg.PathComp) { x0 := comp.X + comp.Radius*vg.Length(math.Cos(comp.Start)) y0 := comp.Y + comp.Radius*vg.Length(math.Sin(comp.Start)) p.Line(pdfPoint(x0, y0)) a1 := comp.Start end := a1 + comp.Angle sign := 1.0 if end < a1 { sign = -1.0 } left := math.Abs(comp.Angle) for left > 0 { a2 := a1 + sign*math.Min(math.Pi/2, left) partialArc(p, comp.X, comp.Y, comp.Radius, a1, a2) left -= math.Abs(a2 - a1) a1 = a2 } }
// padX returns a DrawArea that is padded horizontally // so that glyphs will no be clipped. func padX(p *Plot, da DrawArea) DrawArea { glyphs := p.GlyphBoxes(p) l := leftMost(&da, glyphs) xAxis := horizontalAxis{p.X} glyphs = append(glyphs, xAxis.GlyphBoxes(p)...) r := rightMost(&da, glyphs) minx := da.Min.X - l.Min.X maxx := da.Max().X - (r.Min.X + r.Size.X) lx := vg.Length(l.X) rx := vg.Length(r.X) n := (lx*maxx - rx*minx) / (lx - rx) m := ((lx-1)*maxx - rx*minx + minx) / (lx - rx) return DrawArea{ Canvas: vg.Canvas(da), Rect: Rect{ Min: Point{X: n, Y: da.Min.Y}, Size: Point{X: m - n, Y: da.Size.Y}, }, } }
// padY returns a DrawArea that is padded vertically // so that glyphs will no be clipped. func padY(p *Plot, da DrawArea) DrawArea { glyphs := p.GlyphBoxes(p) b := bottomMost(&da, glyphs) yAxis := verticalAxis{p.Y} glyphs = append(glyphs, yAxis.GlyphBoxes(p)...) t := topMost(&da, glyphs) miny := da.Min.Y - b.Min.Y maxy := da.Max().Y - (t.Min.Y + t.Size.Y) by := vg.Length(b.Y) ty := vg.Length(t.Y) n := (by*maxy - ty*miny) / (by - ty) m := ((by-1)*maxy - ty*miny + miny) / (by - ty) return DrawArea{ Canvas: vg.Canvas(da), Rect: Rect{ Min: Point{Y: n, X: da.Min.X}, Size: Point{Y: m - n, X: da.Size.X}, }, } }
// tickLabelHeight returns height of the tick mark labels. func tickLabelHeight(sty TextStyle, ticks []Tick) vg.Length { maxHeight := vg.Length(0) for _, t := range ticks { if t.IsMinor() { continue } h := sty.Height(t.Label) if h > maxHeight { maxHeight = h } } return maxHeight }
// 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 }
// tickLabelWidth returns the width of the widest tick mark label. func tickLabelWidth(sty TextStyle, ticks []Tick) vg.Length { maxWidth := vg.Length(0) for _, t := range ticks { if t.IsMinor() { continue } w := sty.Width(t.Label) if w > maxWidth { maxWidth = w } } return maxWidth }
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) }
// Y returns the value of x, given in the unit range, // in the drawing coordinates of this draw area. // A value of 0, for example, will return the minimum // y value of the draw area and a value of 1 will // return the maximum. func (da *DrawArea) Y(y float64) vg.Length { return vg.Length(y)*(da.Max().Y-da.Min.Y) + da.Min.Y }
// X returns the value of x, given in the unit range, // in the drawing coordinates of this draw area. // A value of 0, for example, will return the minimum // x value of the draw area and a value of 1 will // return the maximum. func (da *DrawArea) X(x float64) vg.Length { return vg.Length(x)*(da.Max().X-da.Min.X) + da.Min.X }
// RingGlyph is a glyph that draws the outline of a circle. type RingGlyph struct{} // DrawGlyph implements the Glyph interface. func (RingGlyph) DrawGlyph(da *DrawArea, sty GlyphStyle, pt Point) { da.SetLineStyle(LineStyle{Color: sty.Color, Width: vg.Points(0.5)}) var p vg.Path p.Move(pt.X+sty.Radius, pt.Y) p.Arc(pt.X, pt.Y, sty.Radius, 0, 2*math.Pi) p.Close() da.Stroke(p) } const ( cosπover4 = vg.Length(.707106781202420) sinπover6 = vg.Length(.500000000025921) cosπover6 = vg.Length(.866025403769473) ) // SquareGlyph is a glyph that draws the outline of a square. type SquareGlyph struct{} // DrawGlyph implements the Glyph interface. func (SquareGlyph) DrawGlyph(da *DrawArea, sty GlyphStyle, pt Point) { da.SetLineStyle(LineStyle{Color: sty.Color, Width: vg.Points(0.5)}) x := (sty.Radius-sty.Radius*cosπover4)/2 + sty.Radius*cosπover4 var p vg.Path p.Move(pt.X-x, pt.Y-x) p.Line(pt.X+x, pt.Y-x) p.Line(pt.X+x, pt.Y+x)
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 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 main() { flag.Parse() if flag.NArg() < 2 { usage(os.Args[0]) } filename := flag.Arg(0) outputname := flag.Arg(1) variables := mapset.NewSet() for _, x := range strings.Split(plotvars, ",") { if x != "" { variables.Add(x) } } expressions := mapset.NewSet() for _, x := range strings.Split(expvars, ",") { if x != "" { expressions.Add(x) } } // variables := mapset.NewSetFromSlice(strings.Split(plotvars, ",")) // file, err := os.Open(filename, "rb") spice_plots, err := raw.ReadFile(filename) if err != nil { log.Fatal(err) } var prefix, extension string { i := strings.LastIndex(outputname, ".") prefix, extension = outputname[:i], outputname[i+1:] } var outplots map[string]*plot.Plot for n, plot := range spice_plots { fmt.Printf("%s: %s\n", plot.Title, plot.Name) // fmt.Printf("%+v\n", plot) // outplots := Plot(plots[len(plots)-1]) if variables.Cardinality() > 0 { fmt.Println("Plotting", variables.Cardinality(), "variables:", variables) outplots = PlotSome(plot, variables) } else { outplots = Plot(plot) } for i, plot := range outplots { newname := fmt.Sprintf("%s-%d-%v.%s", prefix, n, i, extension) mult := 1.0 multvg := vg.Length(mult) initLineWidth := plot.X.LineStyle.Width initFontSize := plot.X.Label.Font.Size initTickFontSize := plot.X.Tick.Label.Font.Size initTickLength := plot.X.Tick.Width plot.X.Label.Font.Size = multvg * initFontSize plot.Y.Label.Font.Size = multvg * initFontSize plot.X.Tick.Label.Font.Size = multvg * initTickFontSize plot.Y.Tick.Label.Font.Size = multvg * initTickFontSize plot.X.Tick.Width = multvg * initTickLength plot.Y.Tick.Width = multvg * initTickLength plot.X.LineStyle.Width = multvg * initLineWidth plot.Y.LineStyle.Width = multvg * initLineWidth fmt.Println("Outputting ", newname) if err := plot.Save(6*mult, 4*mult, newname); err != nil { panic(err) } } for _, x := range plot.Vectors { fmt.Printf("%s = %v\n", x.Name, x.Get(0)) } if expressions.Cardinality() > 0 { // && plot.NPoints == 1 { vt, err := PlotToVariableTable(plot) if err != nil { log.Fatal(err) } for x := range expressions.Iter() { s := x.(string) // If there is an error, just continue. if r, err := eval(s, vt); err == nil { fmt.Printf("%s = %v\n", s, r) } } } fmt.Println() } }
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 TestBubblesRadius(t *testing.T) { b := &Bubbles{ MinRadius: vg.Length(0), MaxRadius: vg.Length(1), } tests := []struct { minz, maxz, z float64 r vg.Length }{ {0, 0, 0, vg.Length(0.5)}, {1, 1, 1, vg.Length(0.5)}, {0, 1, 0, vg.Length(0)}, {0, 1, 1, vg.Length(1)}, {0, 1, 0.5, vg.Length(0.5)}, {0, 2, 1, vg.Length(0.5)}, {0, 4, 0, vg.Length(0)}, {0, 4, 1, vg.Length(0.25)}, {0, 4, 2, vg.Length(0.5)}, {0, 4, 3, vg.Length(0.75)}, {0, 4, 4, vg.Length(1)}, } for _, test := range tests { b.MinZ, b.MaxZ = test.minz, test.maxz if r := b.radius(test.z); r != test.r { t.Errorf("Got incorrect radius (%g) on %v", r, test) } } }
//ScaledRect returns a plotinum Rectangle, appropriately scaled to match the SubPlot's unit rectangle func (sp SubPlot) ScaledRect(width, height float64) plot.Rect { return plot.Rect{ Min: plot.Point{vg.Length(sp.Rect.X * width), vg.Length(sp.Rect.Y * height)}, Size: plot.Point{vg.Length(sp.Rect.Width * width), vg.Length(sp.Rect.Height * height)}, } }