使用 plot 繪制圖表

作為一個程序員,很多時候雖然我喜歡盯著 console 輸出的一堆數(shù)字看一些系統(tǒng)變化指標(biāo),但俗話說,一圖勝千言,如果能自動的將很多數(shù)據(jù)生成圖表展示,會更加清晰明了,而且能直接從變化的曲線上面得知更多的信息。這也就是我特別喜歡 Prometheus + Grafana 的原因。

但很多項目,尤其是臨時的一些測試項目,我不可能為了看一個數(shù)據(jù)圖表就搭建一套 Prometheus + Grafana 系統(tǒng),那樣效率太低,更多時候,我還是希望能有一個更簡單的工具將一些數(shù)據(jù)展示出來。

幸運(yùn)的是,我們可以通過 plot 非常方便的做到。plot 是一個用 Go 語言實(shí)現(xiàn)的繪圖庫,我們可以通過它繪制非常豐富的圖表,并且可以輸出成多種格式。另外,plot 還提供了非常方便的 interface,我們可以通過它來定制自己的圖表。

簡單示例

我們可以通過 plot 自己提供的 plotutil 工具繪制簡單的圖形。

Line and Points

因為最近剛在看可汗學(xué)院的微觀經(jīng)濟(jì)學(xué),所以就以 price 和 quantity demand 來作為第一個例子,價格和需求數(shù)量通常是成反比的關(guān)系,繪制的圖形應(yīng)該是一條下降的曲線。為了簡化代碼行數(shù),這里特意去掉了錯誤處理。

import (
    "github.com/gonum/plot"
    "github.com/gonum/plot/plotter"
    "github.com/gonum/plot/plotutil"
    "github.com/gonum/plot/vg"
)

func main() {
    p, _ := plot.New()

    p.Title.Text = "Hello Price"
    p.X.Label.Text = "Quantity Demand"
    p.Y.Label.Text = "Price"

    points := plotter.XYs{
        {2.0, 60000.0},
        {4.0, 40000.0},
        {6.0, 30000.0},
        {8.0, 25000.0},
        {10.0, 23000.0},
    }

    plotutil.AddLinePoints(p, points)

    p.Save(4*vg.Inch, 4*vg.Inch, "price.png")
}

執(zhí)行之后,我們就能可以得到一個命名為 price 的 png 文件,看起來就是這樣的:

Histograms

在用 Prometheus 和 Grafana 的時候,我其實(shí)對一些 histogram 的 metric 的展示不怎么滿意,因為 Grafana 的 X 軸是時間相關(guān)的,所以并不能展示柱狀圖,只能使用 histogram_quantile 或者其他函數(shù)得到相關(guān)的變化曲線,用 plot 則可以非常方便的將 Prometheus 的數(shù)據(jù)拿出來畫圖。

一個簡單的例子:

import (
    "github.com/gonum/plot"
    "github.com/gonum/plot/plotter"
    "github.com/gonum/plot/vg"
)

func main() {
    p, _ := plot.New()
    p.Title.Text = "Histogram"

    bins := plotter.XYs{
        {10, 10},
        {20, 20},
        {30, 50},
        {40, 20},
        {50, 10},
    }

    h, _ := plotter.NewHistogram(bins, 5)
    p.Add(h)

    p.Save(4*vg.Inch, 4*vg.Inch, "histogram.png")
}

柱狀圖如下:

自定制 Plotter

Plotter

除了使用 plot 提供的圖表之外,我們還可以非常方便定制自己的圖表。這里我們簡單的畫一個邊長為 20 的正方形。首先定義 Sqaures:

type Squares struct {
    plotter.XYs
}

Squares 里面就只有一批 points,用來表示各個正方形的中心 point。然后我們實(shí)現(xiàn) Plotter 的 Plot 函數(shù),如下:


func (s *Squares) Plot(c draw.Canvas, plt *plot.Plot) {
    trX, trY := plt.Transforms(&c)

    c.SetColor(color.RGBA{R: 196, B: 128, A: 255})

    r := vg.Length(10.0)
    for _, p := range s.XYs {
        p1 := vg.Point{trX(p.X) - r, trY(p.Y) - r}
        p2 := vg.Point{trX(p.X) - r, trY(p.Y) + r}
        p3 := vg.Point{trX(p.X) + r, trY(p.Y) + r}
        p4 := vg.Point{trX(p.X) + r, trY(p.Y) - r}

        var p vg.Path
        p.Move(p1)
        p.Line(p2)
        p.Line(p3)
        p.Line(p4)
        p.Line(p1)
        p.Close()
        c.Fill(p)
    }
}

上面的 Plot 函數(shù)里面,我們使用 plt.Transforms(&c) ,得到兩個轉(zhuǎn)換函數(shù),能夠?qū)⒑竺嬲叫蔚?point 轉(zhuǎn)換到實(shí)際的 canvas 的 point 上面。

func main() {
    points := plotter.XYs{
        {2, 2},
        {4, 4},
        {6, 6},
        {8, 8},
        {10, 10},
    }

    s := Squares{points}

    p, _ := plot.New()
    p.Title.Text = "Squares"
    p.X.Label.Text = "X"
    p.Y.Label.Text = "Y"

    p.X.Min = 0
    p.X.Max = 20
    p.Y.Min = 0
    p.Y.Max = 20

    p.Add(&s)
    p.Save(4*vg.Inch, 4*vg.Inch, "squares.png")
}

創(chuàng)建一個 Sqaures,然后執(zhí)行,得到圖表:

DataRanger

在上面的例子中,我們使用了類似 p.X.Min = 0, p.X.Max = 20 的方式來設(shè)置整個圖表的范圍,但實(shí)際我們更希望能動態(tài)的調(diào)整,因為我們不可能預(yù)估到實(shí)際的范圍到底有多大,這里可以使用 DataRange 來實(shí)現(xiàn):

func (s *Squares) DataRange() (float64, float64, float64, float64) {
    return plotter.XYRange(s.XYs)
}

得到圖表如下:

GlyphBoxer

雖然通過 DataRange 能解決范圍的問題,但我們又發(fā)現(xiàn),一些正方形在邊界上面被切掉了,這主要是因為我們是以正方形的中心繪制的,一個解決方法就是 DataRange 返回更大的區(qū)間,能夠覆蓋掉整個正方向。但另一個更好的辦法就是使用 GlyphBoxes,用來顯示的告訴要繪制的圖表的位置和大小。

func (s *Squares) GlyphBoxes(plt *plot.Plot) []plot.GlyphBox {
    boxes := make([]plot.GlyphBox, len(s.XYs))

    r := vg.Length(10.0)
    for i, p := range s.XYs {
        boxes[i].X = plt.X.Norm(p.X)
        boxes[i].Y = plt.Y.Norm(p.Y)
        boxes[i].Rectangle = vg.Rectangle{
            Min: vg.Point{X: -r, Y: -r},
            Max: vg.Point{X: +r, Y: +r},
        }
    }
    return boxes
}

現(xiàn)在看起來就是這樣了:

后記

可以看到,使用 plot,我們可以非常方便的繪制圖表。當(dāng)然,plot 的功能遠(yuǎn)遠(yuǎn)不止上面說的那么簡單,譬如我們可以直接獲取 Prometheus 的數(shù)據(jù)然后繪圖,在發(fā)送給 Slack,或者畫一個 PieChart

后面,我們也會考慮在一些內(nèi)部的系統(tǒng)上面使用 plot,譬如性能測試框架,每次提交之后,跑很多性能測試,收集到每次的性能測試結(jié)果,使用 plot 繪圖展示等等。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容