go float64 比较_Go 每日一库之 plot
Go 每日一庫之 plot
簡介
本文介紹 Go 語言的一個非常強(qiáng)大、好用的繪圖庫——plot。plot內(nèi)置了很多常用的組件,基本滿足日常需求。同時,它也提供了定制化的接口,可以實現(xiàn)我們的個性化需求。plot主要用于將數(shù)據(jù)可視化,便于我們觀察、比較。
快速使用
先安裝:
$ go get gonum.org/v1/plot/...復(fù)制代碼后使用:
package mainimport ( "log" "math/rand" "gonum.org/v1/plot" "gonum.org/v1/plot/plotter" "gonum.org/v1/plot/plotutil" "gonum.org/v1/plot/vg")func main() { rand.Seed(int64(0)) p, err := plot.New() if err != nil { log.Fatal(err) } p.Title.Text = "Get Started" p.X.Label.Text = "X" p.Y.Label.Text = "Y" err = plotutil.AddLinePoints(p, "First", randomPoints(15), "Second", randomPoints(15), "Third", randomPoints(15)) if err != nil { log.Fatal(err) } if err = p.Save(4*vg.Inch, 4*vg.Inch, "points.png"); err != nil { log.Fatal(err) }}func randomPoints(n int) plotter.XYs { points := make(plotter.XYs, n) for i := range points { if i == 0 { points[i].X = rand.Float64() } else { points[i].X = points[i-1].X + rand.Float64() } points[i].Y = points[i].X + 10 * rand.Float64() } return points}復(fù)制代碼程序運行輸出points.png圖片文件:
plot的使用比較直觀。首先,調(diào)用plot.New()創(chuàng)建一個“畫布”,畫布結(jié)構(gòu)如下:
// Plot is the basic type representing a plot.type Plot struct { Title struct { Text string Padding vg.Length draw.TextStyle } BackgroundColor color.Color X, Y Axis Legend Legend plotters []Plotter}復(fù)制代碼然后,通過直接給畫布結(jié)構(gòu)字段賦值,設(shè)置圖像的屬性。例如p.Title.Text = "Get Started設(shè)置圖像標(biāo)題內(nèi)容;p.X.Label.Text = "X",p.Y.Label.Text = "Y"設(shè)置圖像的 X 和 Y 軸的標(biāo)簽名。
再然后,使用plotutil或者其他子包的方法在畫布上繪制,上面代碼中調(diào)用AddLinePoints()繪制了 3 條折線。
最后保存圖像,上面代碼中調(diào)用p.Save()方法將圖像保存到文件中。
更多圖形
gonum/plot將不同層次的接口封裝到特定的子包中:
- plot:提供了布局和繪圖的簡單接口;
- plotter:使用plot提供的接口實現(xiàn)了一組標(biāo)準(zhǔn)的繪圖器,例如散點圖、條形圖、箱狀圖等。可以使用plotter提供的接口實現(xiàn)自己的繪圖器;
- plotutil:為繪制常見圖形提供簡便的方法;
- vg:封裝各種后端,并提供了一個通用矢量圖形 API。
條形圖
條形圖通過相同寬度條形的高度或長短來表示數(shù)據(jù)的大小關(guān)系。將相同類型的數(shù)據(jù)放在一起比較能非常直觀地看出不同,我們經(jīng)常在比較幾個庫的性能時使用條形圖。下面我們采用json-iter/go的 GitHub 倉庫中用來比較jsoniter、easyjson、std三個 JSON 庫性能的數(shù)據(jù)來繪制條形圖:
package mainimport ( "log" "gonum.org/v1/plot" "gonum.org/v1/plot/plotter" "gonum.org/v1/plot/plotutil" "gonum.org/v1/plot/vg")func main() { std := plotter.Values{35510, 1960, 99} easyjson := plotter.Values{8499, 160, 4} jsoniter := plotter.Values{5623, 160, 3} p, err := plot.New() if err != nil { log.Fatal(err) } p.Title.Text = "jsoniter vs easyjson vs std" p.Y.Label.Text = "" w := vg.Points(20) stdBar, err := plotter.NewBarChart(std, w) if err != nil { log.Fatal(err) } stdBar.LineStyle.Width = vg.Length(0) stdBar.Color = plotutil.Color(0) stdBar.Offset = -w easyjsonBar, err := plotter.NewBarChart(easyjson, w) if err != nil { log.Fatal(err) } easyjsonBar.LineStyle.Width = vg.Length(0) easyjsonBar.Color = plotutil.Color(1) jsoniterBar, err := plotter.NewBarChart(jsoniter, w) if err != nil { log.Fatal(err) } jsoniterBar.LineStyle.Width = vg.Length(0) jsoniterBar.Color = plotutil.Color(2) jsoniterBar.Offset = w p.Add(stdBar, easyjsonBar, jsoniterBar) p.Legend.Add("std", stdBar) p.Legend.Add("easyjson", easyjsonBar) p.Legend.Add("jsoniter", jsoniterBar) p.Legend.Top = true p.NominalX("ns/op", "allocation bytes", "allocation times") if err = p.Save(5*vg.Inch, 5*vg.Inch, "barchart.png"); err != nil { log.Fatal(err) }}復(fù)制代碼首先生成值列表,我們在最開始的例子中生成了二維坐標(biāo)列表plotter.XYs,實際上還有三維坐標(biāo)列表plotter.XYZs。
然后,調(diào)用plotter.NewBarChart()分別為三組數(shù)據(jù)生成條形圖。w = vg.Points(20)用來設(shè)置條形的寬度。LineStyle.Width設(shè)置線寬,這個實際上是邊框的寬度。Color設(shè)置顏色。Offset設(shè)置偏移,因為每組對應(yīng)位置的條形放在一起顯示更好比較,將stdBar.Offset設(shè)置為-w會讓其向左偏移一個條形的寬度;easyjson偏移不設(shè)置,默認(rèn)為 0,不偏移;jsoniter偏移設(shè)置為w,向右偏移一個條形的寬度。最終它們緊挨著顯示。
然后,將 3 個條形圖添加到畫布上。緊接著,設(shè)置它們的圖例,并將其顯示在頂部。
最后調(diào)用p.Save()保存圖片。
程序運行生成下面的圖片:
可以很直觀地看到j(luò)soniter的性能、內(nèi)存占用、內(nèi)存分配次數(shù)各方面都是頂尖的。可能用同一種維度的數(shù)據(jù),數(shù)量級相差不大,圖像會好看點(┬_┬)。
注意plotter.Color(2)這類用法。plot預(yù)定義了一組顏色值,如果我們想要使用它們,可以直接傳入索引獲取對應(yīng)的顏色,更多的是為了區(qū)分不同的圖形(例如上面的 3 個條形圖用了 3 個不同的索引):
// src/gonum.org/v1/plot/plotutil/plotutil.govar DefaultColors = SoftColorsvar SoftColors = []color.Color{ rgb(241, 90, 96), rgb(122, 195, 106), rgb(90, 155, 212), rgb(250, 167, 91), rgb(158, 103, 171), rgb(206, 112, 88), rgb(215, 127, 180),}func Color(i int) color.Color { n := len(DefaultColors) if i < 0 { return DefaultColors[i%n+n] } return DefaultColors[i%n]}復(fù)制代碼除了顏色,還有形狀plotter.Shape(i)和劃線模式plotter.Dashes(i)。
vg.Length(0)有所不同,這個只是將 0 轉(zhuǎn)換為vg.Length類型!
函數(shù)圖像
plot可以繪制函數(shù)圖像!
func main() { p, err := plot.New() if err != nil { log.Fatal(err) } p.Title.Text = "Functions" p.X.Label.Text = "X" p.Y.Label.Text = "Y" square := plotter.NewFunction(func(x float64) float64 { return x * x }) square.Color = plotutil.Color(0) sqrt := plotter.NewFunction(func(x float64) float64 { return 10 * math.Sqrt(x) }) sqrt.Dashes = []vg.Length{vg.Points(1), vg.Points(2)} sqrt.Width = vg.Points(1) sqrt.Color = plotutil.Color(1) exp := plotter.NewFunction(func(x float64) float64 { return math.Pow(2, x) }) exp.Dashes = []vg.Length{vg.Points(2), vg.Points(3)} exp.Width = vg.Points(2) exp.Color = plotutil.Color(2) sin := plotter.NewFunction(func(x float64) float64 { return 10*math.Sin(x) + 50 }) sin.Dashes = []vg.Length{vg.Points(3), vg.Points(4)} sin.Width = vg.Points(3) sin.Color = plotutil.Color(3) p.Add(square, sqrt, exp, sin) p.Legend.Add("x^2", square) p.Legend.Add("10*sqrt(x)", sqrt) p.Legend.Add("2^x", exp) p.Legend.Add("10*sin(x)+50", sin) p.Legend.ThumbnailWidth = 0.5 * vg.Inch p.X.Min = 0 p.X.Max = 10 p.Y.Min = 0 p.Y.Max = 100 if err = p.Save(4*vg.Inch, 4*vg.Inch, "functions.png"); err != nil { log.Fatal(err) }}復(fù)制代碼首先調(diào)用plotter.NewFunction()創(chuàng)建一個函數(shù)圖像。它接受一個函數(shù),單輸入?yún)?shù)float64,單輸出參數(shù)float64,故只能畫出單自變量的函數(shù)圖像。接著為函數(shù)圖像設(shè)置了三個屬性Dashes(劃線)、Width(線寬)和Color(顏色)。默認(rèn)使用連續(xù)的線條來繪制函數(shù),如圖中的平方函數(shù)。可以通過設(shè)置Dashes讓plot繪制不連續(xù)的線條,Dashes接受兩個長度值,第一個長度表示間隔距離,第二個長度表示連續(xù)線的長度。這里也使用到了plotutil.Color(i)依次使用前 4 個預(yù)定義的顏色。
創(chuàng)建畫布、設(shè)置圖例這些都與前面的相同。這里還通過p.X和p.Y的Min/Max屬性限制了圖像繪制的坐標(biāo)范圍。
運行程序生成圖像:
氣泡圖
使用plot可以畫出非常好看的氣泡圖:
func main() { n := 10 bubbleData := randomTriples(n) minZ, maxZ := math.Inf(1), math.Inf(-1) for _, xyz := range bubbleData { if xyz.Z > maxZ { maxZ = xyz.Z } if xyz.Z < minZ { minZ = xyz.Z } } p, err := plot.New() if err != nil { log.Fatal(err) } p.Title.Text = "Bubbles" p.X.Label.Text = "X" p.Y.Label.Text = "Y" bs, err := plotter.NewScatter(bubbleData) if err != nil { log.Fatal(err) } bs.GlyphStyleFunc = func(i int) draw.GlyphStyle { c := color.RGBA{R: 196, B: 128, A: 255} var minRadius, maxRadius = vg.Points(1), vg.Points(20) rng := maxRadius - minRadius _, _, z := bubbleData.XYZ(i) d := (z - minZ) / (maxZ - minZ) r := vg.Length(d)*rng + minRadius return draw.GlyphStyle{Color: c, Radius: r, Shape: draw.CircleGlyph{}} } p.Add(bs) if err = p.Save(4*vg.Inch, 4*vg.Inch, "bubble.png"); err != nil { log.Fatal(err) }}func randomTriples(n int) plotter.XYZs { data := make(plotter.XYZs, n) for i := range data { if i == 0 { data[i].X = rand.Float64() } else { data[i].X = data[i-1].X + 2*rand.Float64() } data[i].Y = data[i].X + 10*rand.Float64() data[i].Z = data[i].X } return data}復(fù)制代碼我們生成一組三維坐標(biāo)點,調(diào)用plotter.NewScatter()生成散點圖。我們設(shè)置了GlyphStyleFunc鉤子函數(shù),在繪制每個點之前都會調(diào)用它,它返回一個draw.GlyphStyle類型,plot會根據(jù)返回的這個對象來繪制。我們的例子中,每次我們都返回一個表示圓形的draw.GlyphStyle對象,通過Z坐標(biāo)與最大、最小坐標(biāo)的比例映射到[vg.Points(1),vg.Points(20)]區(qū)間中得到半徑。
生成的圖像:
同樣地,我們可以返回正方形的draw.GlyphStyle的對象來繪制“方形圖”,只需要把鉤子函數(shù)GlyphStyleFunc的返回語句做些修改:
return draw.GlyphStyle{Color: c, Radius: r, Shape: draw.SquareGlyph{}}復(fù)制代碼即可繪制“方形圖”:
實際應(yīng)用
下面我們應(yīng)用之前文章中介紹的gopsutil和本文中的plot搭建一個網(wǎng)頁,可以實時觀察機(jī)器的 CPU 和內(nèi)存占用:
func index(w http.ResponseWriter, r *http.Request) { t, err := template.ParseFiles("index.html") if err != nil { log.Fatal(err) } t.Execute(w, nil)}func image(w http.ResponseWriter, r *http.Request) { monitor.WriteTo(w)}func main() { mux := http.NewServeMux() mux.HandleFunc("/", index) mux.HandleFunc("/image", image) go monitor.Run() s := &http.Server{ Addr: ":8080", Handler: mux, } if err := s.ListenAndServe(); err != nil { log.Fatal(err) }}復(fù)制代碼首先,我們編寫了一個 HTTP 服務(wù)器,監(jiān)聽在 8080 端口。設(shè)置兩個路由,/顯示主頁,/image調(diào)用Monitor的方法生成 CPU 和內(nèi)存占用圖返回。Monitor結(jié)構(gòu)稍后會介紹。index.html的內(nèi)容如下:
Monitor 復(fù)制代碼頁面比較簡單,就顯示了一張圖片。然后在 JS 中啟動一個 500ms 的定時器,每隔 500ms 就重新請求一次圖片替換現(xiàn)有的圖片。我在設(shè)置img.src屬性時在后面添加了一個隨機(jī)數(shù),這是為了防止緩存導(dǎo)致得到的可能不是最新的圖片。
下面看看Monitor的結(jié)構(gòu):
type Monitor struct { Mem []float64 CPU []float64 MaxRecord int Lock sync.Mutex}func NewMonitor(max int) *Monitor { return &Monitor{ MaxRecord: max, }}var monitor = NewMonitor(50)復(fù)制代碼這個結(jié)構(gòu)中記錄了最近的 50 條記錄。每隔 500ms 會收集一次 CPU 和內(nèi)存的占用情況,記錄到CPU和Mem字段中:
func (m *Monitor) Collect() { mem, err := mem.VirtualMemory() if err != nil { log.Fatal(err) } cpu, err := cpu.Percent(500*time.Millisecond, false) if err != nil { log.Fatal(err) } m.Lock.Lock() defer m.Lock.Unlock() m.Mem = append(m.Mem, mem.UsedPercent) m.CPU = append(m.CPU, cpu[0])}func (m *Monitor) Run() { for { m.Collect() time.Sleep(500 * time.Millisecond) }}復(fù)制代碼當(dāng) HTTP 請求/image路由時,根據(jù)目前已經(jīng)收集到的CPU和Mem數(shù)據(jù)生成圖片返回:
func (m *Monitor) WriteTo(w io.Writer) { m.Lock.Lock() defer m.Lock.Unlock() cpuData := make(plotter.XYs, len(m.CPU)) for i, p := range m.CPU { cpuData[i].X = float64(i + 1) cpuData[i].Y = p } memData := make(plotter.XYs, len(m.Mem)) for i, p := range m.Mem { memData[i].X = float64(i + 1) memData[i].Y = p } p, err := plot.New() if err != nil { log.Fatal(err) } cpuLine, err := plotter.NewLine(cpuData) if err != nil { log.Fatal(err) } cpuLine.Color = plotutil.Color(1) memLine, err := plotter.NewLine(memData) if err != nil { log.Fatal(err) } memLine.Color = plotutil.Color(2) p.Add(cpuLine, memLine) p.Legend.Add("cpu", cpuLine) p.Legend.Add("mem", memLine) p.X.Min = 0 p.X.Max = float64(m.MaxRecord) p.Y.Min = 0 p.Y.Max = 100 wc, err := p.WriterTo(4*vg.Inch, 4*vg.Inch, "png") if err != nil { log.Fatal(err) } wc.WriteTo(w)}復(fù)制代碼運行服務(wù)器:
$ go run main.go復(fù)制代碼打開瀏覽器,輸入localhost:8080,觀察圖片變化:
總結(jié)
本文介紹了強(qiáng)大的繪圖庫plot,最后通過一個監(jiān)控程序結(jié)尾。限于篇幅,plot提供的多種繪圖類型未能一一介紹。plot還支持svg/pdf等多種格式的保存。感興趣的童鞋可自行研究。
大家如果發(fā)現(xiàn)好玩、好用的 Go 語言庫,歡迎到 Go 每日一庫 GitHub 上提交 issue
總結(jié)
以上是生活随笔為你收集整理的go float64 比较_Go 每日一库之 plot的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python提供什么机制处理程序运行错误
- 下一篇: z变换公式表_如何使用标准正态分布表?