Go 言語でもグラフを描きたい!

いや,決まったデータに対して棒グラフとか折れ線グラフとか簡単に描けないかなぁ,と思っただけなんスけどね。 ぶっちゃけ「グラフを描く」だけなら,それこそ gnuplot で必要十分なんだけど,今回もお遊びということで。

とはいえ自前でコードを書くのはアレなので,なにか手頃なパッケージはないかなぁ,とググってみたら Gonum ってのがいいらしい。 Gonum

Gonum contains libraries for matrices and linear algebra; statistics, probability distributions, and sampling; tools for function differentiation, integration, and optimization; network creation and analysis; and more.
via Gonum

とある通り,本来は数値計算パッケージなのだが, plot パッケージを使って簡単なグラフを描くこともできるらしい。 どんなグラフが描けるかについては以下が参考になる。

試しに何か描いてみる。

棒グラフを描いてみる

まずは普通に棒グラフを描いてみよう。 ちょっと長いが,こんな感じのコードでどうだろう。

package main

import (
	"fmt"
	"math"
	"os"

	"gonum.org/v1/plot"
	"gonum.org/v1/plot/plotter"
	"gonum.org/v1/plot/plotutil"
	"gonum.org/v1/plot/vg"
	"gonum.org/v1/plot/vg/draw"
)

type CaseData struct {
	Date     string
	NewCases int
}

var Dataset = []CaseData{
	{"2020-04-18", 628},
	{"2020-04-19", 566},
	{"2020-04-20", 390},
	{"2020-04-21", 368},
	{"2020-04-22", 377},
	{"2020-04-23", 423},
	{"2020-04-24", 469},
}

func main() {
	//import data
	labelX := []string{}
	dataY := plotter.Values{}
	for _, d := range Dataset {
		labelX = append(labelX, d.Date)
		dataY = append(dataY, (float64)(d.NewCases))
	}

	//new plot
	p, err := plot.New()
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		return
	}

	//new bar chart
	bar, err := plotter.NewBarChart(dataY, vg.Points(20))
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		return
	}
	bar.LineStyle.Width = vg.Length(0)
	bar.Color = plotutil.Color(2) //plotutil.SoftColors[2]
	bar.Offset = 0
	bar.Horizontal = false
	p.Add(bar)

	//legend
	p.Legend.Add("New confirmed cases by date", bar)
	p.Legend.Top = true   //top
	p.Legend.Left = false //right
	p.Legend.XOffs = 0
	p.Legend.YOffs = -10

	//labels of X
	p.NominalX(labelX...)
	p.X.Label.Text = "Date"
	p.X.Padding = 0
	p.X.Width = p.Y.Width
	p.X.Tick.Label.Rotation = math.Pi / 2.5
	p.X.Tick.Label.XAlign = draw.XRight
	p.X.Tick.Label.YAlign = draw.YCenter

	//labels of Y
	p.Y.Label.Text = "Cases"
	p.Y.Padding = 0
	p.Y.Min = 0
	p.Y.Max = 800

	//title
	p.Title.Text = "Confirmed COVID-2019 Cases in Japan"

	//output image
	if err := p.Save(15*vg.Centimeter, 15*vg.Centimeter, "bar-chart-1.png"); err != nil {
		fmt.Fprintln(os.Stderr, err)
		return
	}
}

実行結果はこんな感じ。

bar-chart-1.png
bar-chart-1.png

Gonum でも日本語を使いたい

plot/vg パッケージにはいくつかのフリー・フォントが含まれている。 どのようなフォントが使えるかについては vg.FontMap を参照すればよい。

package main

import (
	"fmt"

	"gonum.org/v1/plot/vg"
)

func main() {
	for k, v := range vg.FontMap {
		fmt.Println(k, ":", v)
	}
    //Output:
    //Helvetica-Oblique : LiberationSans-Italic
    //Helvetica-BoldOblique : LiberationSans-BoldItalic
    //Times-Italic : LiberationSerif-Italic
    //Courier : LiberationMono-Regular
    //Courier-Bold : LiberationMono-Bold
    //Courier-Oblique : LiberationMono-Italic
    //Helvetica : LiberationSans-Regular
    //Helvetica-Bold : LiberationSans-Bold
    //Times-BoldItalic : LiberationSerif-BoldItalic
    //Courier-BoldOblique : LiberationMono-BoldItalic
    //Times-Roman : LiberationSerif-Regular
    //Times-Bold : LiberationSerif-Bold
}

たとえば,先程のコードに

//default font
plot.DefaultFont = "Courier"
plotter.DefaultFont = "Courier"

を追加すれば

bar-chart-2.png
bar-chart-2.png

という感じに既定フォントを入れ換えることができる。

plot/vg パッケージに含まれるフォントは当然ながら日本語の字体を含んでいない。 日本語が使いたいのであれば,別途日本語フォントを読み込む必要がある1。 こんな感じ。

//import japanese fonts
b, err := ioutil.ReadFile("/usr/local/texlive/2020/texmf-dist/fonts/truetype/public/ipaex/ipaexg.ttf")
if err != nil {
    fmt.Fprintln(os.Stderr, err)
    return
}
ft, err := truetype.Parse(b)
if err != nil {
    fmt.Fprintln(os.Stderr, err)
    return
}
fontName := "ipaex"
vg.AddFont(fontName, ft)

//default font
plot.DefaultFont = fontName
plotter.DefaultFont = fontName

今回は TeX Live 2020 に収録されている IPAex フォントを流用してみた。 これを使って,更にラベルのいくつかを日本語に置き換えたのがこちら。

bar-chart-3.png
bar-chart-3.png

またグラフの各要素に対して個別にフォントを指定するのであれば

//set fonts
font, err := vg.MakeFont(fontName, 10)
if err != nil {
    fmt.Fprintln(os.Stderr, err)
    return
}
p.Title.Font = font
p.X.Label.Font = font
p.Y.Label.Font = font
p.X.Tick.Label.Font = font
p.Y.Tick.Label.Font = font
p.Legend.Font = font

のように書ける。

フォントの読み込みには github.com/golang/freetype/truetype パッケージを使っているのだが,どうも TTC (TrueType Collections) ファイルには対応していない模様。 たとえば Ubuntu 19.10 に収録されている NOTO フォントを使おうとしたが

b, err := ioutil.ReadFile("/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc")
if err != nil {
    fmt.Fprintln(os.Stderr, err)
    return
}
ft, err := truetype.Parse(b)
if err != nil {
    fmt.Fprintln(os.Stderr, err) //Output: freetype: invalid TrueType format: bad TTF version
    return
}

実行時エラーになってしまった。 ちなみに OTF ファイルにも対応してない。 まぁ,文章を書くんじゃないから IPA フォントでも十分だよね。

ブックマーク

参考図書

photo
プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)
Alan A.A. Donovan (著), Brian W. Kernighan (著), 柴田 芳樹 (翻訳)
丸善出版 2016-06-20
単行本(ソフトカバー)
4621300253 (ASIN), 9784621300251 (EAN), 4621300253 (ISBN), 9784621300251 (ISBN)
評価     

著者のひとりは(あの「バイブル」とも呼ばれる)通称 “K&R” の K のほうである。この本は Go 言語の教科書と言ってもいいだろう。

reviewed by Spiegel on 2016-07-13 (powered by PA-APIv5)


  1. OS のフォント・キャッシュが使えればいいんだけど plot/vg パッケージ単体では無理みたい。どなたかいい方法があれば教えてください。 ↩︎