func (s *Sparkline) Draw(dst draw.Image) { if len(s.data) == 0 { return } min := s.data[0] max := s.data[0] for _, n := range s.data[1:] { if n < min { min = n } if n > max { max = n } } // try to be helpful when there's limited data if max < 1.0 { max = 1.0 } if min > max-1.0 { min = max - 1.0 if min < 0.0 { min = 0.0 } } bounds := dst.Bounds() dx, dy := bounds.Dx(), bounds.Dy() tmp := image.NewRGBA(image.Rectangle{Max: image.Point{X: dx, Y: dy}}) p := raster.NewRGBAPainter(tmp) r := raster.NewRasterizer(dx, dy) r.UseNonZeroWinding = true var q raster.Path q.Start(s.scale(0, s.data[0], min, max, dx, dy)) for i, n := range s.data[1:] { pt := s.scale(i+1, n, min, max, dx, dy) q.Add1(pt) } const strokeWidth = fixed.Int26_6(5 << 6) r.AddStroke(q, strokeWidth, raster.RoundCapper, raster.RoundJoiner) p.SetColor(s.fg) r.Rasterize(p) r.Clear() q.Clear() headPt := s.scale(len(s.data)-1, s.data[len(s.data)-1], min, max, dx, dy) q.Start(headPt) // miniscule nudge so something actually is output q.Add1(headPt.Add(fixed.Point26_6{X: 1, Y: 1})) const headWidth = fixed.Int26_6(8 << 6) r.AddStroke(q, headWidth, raster.RoundCapper, raster.RoundJoiner) // TODO really decide between uint64 vs float32 vs uint32 etc // value := uint64((s.data[len(s.data)-1] - min) / max * float32(^uint64(0))) value := uint64(s.data[len(s.data)-1]) headColor := PickColor(s.thresholds, value) p.SetColor(headColor) r.Rasterize(p) draw.Draw(dst, bounds, tmp, image.ZP, draw.Over) }
func Handler(w http.ResponseWriter, req *http.Request) { const size = 500 c := image.NewRGBA(image.Rect(0, 0, size, size)) white := &image.Uniform{C: color.White} draw.Draw(c, c.Bounds(), white, image.ZP, draw.Src) p := raster.NewRGBAPainter(c) p.SetColor(color.Black) r := raster.NewRasterizer(500, 500) r.UseNonZeroWinding = true var path raster.Path path.Start(nudge(fixed.P(50, 50))) path.Add1(nudge(fixed.P(50, 450))) path.Add1(nudge(fixed.P(450, 450))) r.AddStroke(path, fixed.I(2), raster.ButtCapper, raster.BevelJoiner) r.Rasterize(p) r.Clear() p.SetColor(color.Gray16{0x7FFF}) path.Clear() r.Clear() path.Start(nudge(fixed.P(450, 450))) path.Add1(nudge(fixed.P(450, 50))) path.Add1(nudge(fixed.P(50, 50))) r.AddStroke(path, fixed.I(1), raster.ButtCapper, raster.BevelJoiner) r.Rasterize(p) p.SetColor(color.Gray16{0x7FFF}) for x := 50; x <= 450; x += 50 { path.Clear() path.Start(nudge(fixed.P(x, 450))) path.Add1(nudge(fixed.P(x, 460))) r.AddStroke(path, fixed.I(1), raster.ButtCapper, raster.BevelJoiner) } for x := 50; x <= 450; x += 50 { path.Clear() path.Start(nudge(fixed.P(50, x))) path.Add1(nudge(fixed.P(40, x))) r.AddStroke(path, fixed.I(1), raster.ButtCapper, raster.BevelJoiner) } r.Rasterize(p) p.SetColor(color.RGBA{0x00, 0x00, 0xFF, 0xFF}) r.Clear() path.Clear() path.Start(nudge(fixed.P(50, 450))) path.Add1(nudge(fixed.P(450, 50))) r.AddStroke(path, fixed.I(1), raster.ButtCapper, raster.BevelJoiner) r.Rasterize(p) p.SetColor(color.RGBA{0xCC, 0x00, 0x00, 0xFF}) r.Clear() window := fixed.Rectangle26_6{fixed.P(50, 450), fixed.P(450, 50)} // path = plotPath(ratSin, big.NewRat(0, 1), big.NewRat(-1, 1), big.NewRat(10, 1), big.NewRat(1, 1), window) // path = plotPath(ratProb, big.NewRat(0, 1), big.NewRat(0, 1), big.NewRat(1, 1<<57), big.NewRat(1, 1<<57), window) var lo, hi *big.Rat var locap, hicap, scalecap string id, _ := strconv.Atoi(req.FormValue("x")) switch id { case 0: lo = big.NewRat(1<<53-1<<3, 1<<54) hi = big.NewRat(1<<53+1<<3, 1<<54) locap = "1/2 - 1/2^51" hicap = "1/2 + 1/2^51" scalecap = "1/2^53" case 1: lo = big.NewRat(1<<54-1<<4, 1<<54) hi = big.NewRat(1<<54, 1<<54) locap = "1 - 1/2^50" hicap = "1" scalecap = "1/2^53" case 2: lo = big.NewRat(0, 1<<54) hi = big.NewRat(1<<4, 1<<54) locap = "0" hicap = "1/2^50" scalecap = "1/2^53" case 3: lo = big.NewRat(0, 1<<54) hi = big.NewRat(1<<2, 1<<62) locap = "0" hicap = "1/2^59" scalecap = "1/2^63" } mode, _ := strconv.Atoi(req.FormValue("mode")) path = plotPath(ratProb(mode), lo, lo, hi, hi, window) r.AddStroke(path, fixed.I(1), raster.ButtCapper, raster.BevelJoiner) r.Rasterize(p) var modestr string switch mode { case 0: modestr = "original behavior (can return 1)" case 1: modestr = "new behavior (too much 0)" case 2: modestr = "retry loop" } data, err := ioutil.ReadFile("/tmp/luxisr.ttf") if err != nil { panic(err) } tfont, err := freetype.ParseFont(data) if err != nil { panic(err) } const pt = 10 ft := freetype.NewContext() ft.SetDst(c) ft.SetDPI(100) ft.SetFont(tfont) ft.SetFontSize(float64(pt)) ft.SetSrc(image.NewUniform(color.Black)) ft.SetClip(image.Rect(0, 0, 0, 0)) wid, err := ft.DrawString(locap, freetype.Pt(0, 0)) if err != nil { panic(err) } pp := freetype.Pt(50, 480) pp.X -= wid.X / 2 ft.SetClip(c.Bounds()) ft.DrawString(locap, pp) ft.SetClip(image.Rect(0, 0, 0, 0)) wid, err = ft.DrawString(hicap, freetype.Pt(0, 0)) if err != nil { panic(err) } pp = freetype.Pt(450, 480) pp.X -= wid.X / 2 ft.SetClip(c.Bounds()) ft.DrawString(hicap, pp) r.Clear() p.SetColor(color.Black) path.Clear() const dy = 5 path.Start(nudge(fixed.P(400, 400))) path.Add1(nudge(fixed.P(400, 400-dy))) path.Add1(nudge(fixed.P(400, 400+dy))) path.Add1(nudge(fixed.P(400, 400))) path.Add1(nudge(fixed.P(450, 400))) path.Add1(nudge(fixed.P(450, 400-dy))) path.Add1(nudge(fixed.P(450, 400+dy))) path.Add1(nudge(fixed.P(450, 400))) r.AddStroke(path, fixed.I(2), raster.ButtCapper, raster.BevelJoiner) r.Rasterize(p) ft.SetClip(image.Rect(0, 0, 0, 0)) wid, err = ft.DrawString(scalecap, freetype.Pt(0, 0)) if err != nil { panic(err) } pp = freetype.Pt(425, 420) pp.X -= wid.X / 2 ft.SetClip(c.Bounds()) ft.DrawString(scalecap, pp) ft.SetClip(image.Rect(0, 0, 0, 0)) wid, err = ft.DrawString(modestr, freetype.Pt(0, 0)) if err != nil { panic(err) } pp = freetype.Pt(250, 490) pp.X -= wid.X / 2 ft.SetClip(c.Bounds()) ft.DrawString(modestr, pp) w.Write(pngEncode(c)) }