termui
GitHub概要
スター13,393
ウォッチ285
フォーク798
作成日:2015年2月3日
言語:Go
ライセンス:MIT License
トピックス
なし
スター履歴
データ取得日時: 2025/7/25 11:09
termui
termuiは、ダッシュボードやモニタリングツールの開発に特化したGo言語のTUIライブラリです。チャート、グラフ、ゲージなどの豊富なデータ可視化ウィジェットを提供し、システムモニタリングやリアルタイムダッシュボードの構築に最適です。
特徴
データ可視化ウィジェット
- チャート: LineChart、BarChart、PieChart、SparkLine
- ゲージ: Gauge、Battery、Progress
- リスト: List、Table、Tree
- テキスト: Paragraph、Tabs
- レイアウト: Grid、Block
パフォーマンスとリアルタイム更新
- 効率的な描画: 差分レンダリング
- リアルタイムデータ: ストリーミングデータの表示
- メモリ効率: 最小限のメモリフットプリント
- 高速更新: 高頻度でのデータ更新に対応
使いやすさ
- シンプルなAPI: 直感的なウィジェット作成
- デフォルトテーマ: すぐに使える配色
- レスポンシブレイアウト: 画面サイズに適応
- イベントハンドリング: キーボード・マウスイベント
カスタマイズ性
- スタイル設定: 色、境界線、パディングのカスタマイズ
- カスタムウィジェット: 独自ウィジェットの作成
- テーマ: カスタムテーマの定義
- レイアウトシステム: 柔軟なグリッドレイアウト
基本的な使用方法
インストール
go get github.com/gizak/termui/v3
Hello World
package main
import (
"log"
ui "github.com/gizak/termui/v3"
"github.com/gizak/termui/v3/widgets"
)
func main() {
if err := ui.Init(); err != nil {
log.Fatalf("failed to initialize termui: %v", err)
}
defer ui.Close()
// パラグラフウィジェットの作成
p := widgets.NewParagraph()
p.Text = "Hello World!"
p.SetRect(0, 0, 25, 5)
p.Title = "Welcome"
p.BorderStyle.Fg = ui.ColorYellow
ui.Render(p)
// イベントループ
for e := range ui.PollEvents() {
if e.Type == ui.KeyboardEvent {
break
}
}
}
ダッシュボードの作成
package main
import (
"log"
"math"
"time"
ui "github.com/gizak/termui/v3"
"github.com/gizak/termui/v3/widgets"
)
func main() {
if err := ui.Init(); err != nil {
log.Fatalf("failed to initialize termui: %v", err)
}
defer ui.Close()
// ゲージ
g := widgets.NewGauge()
g.Title = "CPU使用率"
g.Percent = 50
g.SetRect(0, 0, 50, 3)
g.BarColor = ui.ColorRed
g.LabelStyle = ui.NewStyle(ui.ColorYellow)
// スパークライン
sl := widgets.NewSparkline()
sl.Title = "ネットワーク"
sl.Data = []float64{4, 2, 1, 6, 3, 9, 1, 4, 2, 15, 14, 9, 8, 6, 10, 13, 15, 12, 10, 5, 3, 6, 1}
sl.LineColor = ui.ColorCyan
sl.TitleStyle.Fg = ui.ColorWhite
slg := widgets.NewSparklineGroup(sl)
slg.Title = "トラフィック"
slg.SetRect(0, 3, 50, 8)
// バーチャート
bc := widgets.NewBarChart()
bc.Title = "メモリ使用状況"
bc.SetRect(0, 8, 50, 13)
bc.Labels = []string{"S0", "S1", "S2", "S3", "S4", "S5", "S6", "S7"}
bc.Data = []float64{12, 18, 15, 20, 22, 19, 23, 25}
bc.BarWidth = 5
bc.BarColors = []ui.Color{ui.ColorGreen, ui.ColorYellow, ui.ColorRed}
bc.LabelStyles = []ui.Style{ui.NewStyle(ui.ColorBlue)}
bc.NumStyles = []ui.Style{ui.NewStyle(ui.ColorWhite)}
// 初期レンダリング
ui.Render(g, slg, bc)
// 更新用タイマー
uiEvents := ui.PollEvents()
ticker := time.NewTicker(time.Second).C
// イベントループ
for {
select {
case e := <-uiEvents:
switch e.ID {
case "q", "<C-c>":
return
}
case <-ticker:
// データ更新
g.Percent = int(50 + 30*math.Sin(float64(time.Now().Unix())))
// スパークラインにデータ追加
sl.Data = append(sl.Data[1:], sl.Data[len(sl.Data)-1]+float64(10-20*math.Sin(float64(time.Now().Unix()))))
ui.Render(g, slg, bc)
}
}
}
リアルタイムチャート
package main
import (
"log"
"math"
"time"
ui "github.com/gizak/termui/v3"
"github.com/gizak/termui/v3/widgets"
)
func main() {
if err := ui.Init(); err != nil {
log.Fatalf("failed to initialize termui: %v", err)
}
defer ui.Close()
// ラインチャート
lc := widgets.NewPlot()
lc.Title = "サイン波"
lc.Data = make([][]float64, 1)
lc.SetRect(0, 0, 60, 15)
lc.AxesColor = ui.ColorWhite
lc.LineColors[0] = ui.ColorGreen
lc.Marker = widgets.MarkerDot
// データ生成関数
generateData := func() []float64 {
data := make([]float64, 100)
for i := 0; i < 100; i++ {
data[i] = 10 + 10*math.Sin(float64(i)/5.0+float64(time.Now().UnixNano())/1e9)
}
return data
}
lc.Data[0] = generateData()
// パイチャート
pc := widgets.NewPieChart()
pc.Title = "リソース配分"
pc.SetRect(60, 0, 100, 15)
pc.Data = []float64{.25, .25, .25, .25}
pc.AngleOffset = .15
pc.LabelFormatter = func(i int, v float64) string {
return fmt.Sprintf("%.0f%%", v*100)
}
ui.Render(lc, pc)
// 更新ループ
uiEvents := ui.PollEvents()
ticker := time.NewTicker(100 * time.Millisecond).C
for {
select {
case e := <-uiEvents:
switch e.ID {
case "q", "<C-c>":
return
}
case <-ticker:
lc.Data[0] = generateData()
ui.Render(lc)
}
}
}
グリッドレイアウト
package main
import (
"log"
ui "github.com/gizak/termui/v3"
"github.com/gizak/termui/v3/widgets"
)
func main() {
if err := ui.Init(); err != nil {
log.Fatalf("failed to initialize termui: %v", err)
}
defer ui.Close()
// ウィジェットの作成
p1 := widgets.NewParagraph()
p1.Title = "統計"
p1.Text = "CPU: 45%\nメモリ: 2.5GB / 8GB\nディスク: 120GB / 500GB"
p1.BorderStyle.Fg = ui.ColorYellow
p2 := widgets.NewParagraph()
p2.Title = "ログ"
p2.Text = "[INFO] システム起動\n[WARN] 高負荷を検出\n[INFO] 処理完了"
p2.BorderStyle.Fg = ui.ColorMagenta
l := widgets.NewList()
l.Title = "プロセス"
l.Rows = []string{
"[1] nginx",
"[2] postgres",
"[3] redis",
"[4] app-server",
}
l.SelectedRowStyle = ui.NewStyle(ui.ColorGreen)
l.BorderStyle.Fg = ui.ColorCyan
// グリッド定義
grid := ui.NewGrid()
termWidth, termHeight := ui.TerminalDimensions()
grid.SetRect(0, 0, termWidth, termHeight)
grid.Set(
ui.NewRow(1.0/2,
ui.NewCol(1.0/2, p1),
ui.NewCol(1.0/2, p2),
),
ui.NewRow(1.0/2,
ui.NewCol(1.0, l),
),
)
ui.Render(grid)
// イベント処理
previousKey := ""
uiEvents := ui.PollEvents()
for {
e := <-uiEvents
switch e.ID {
case "q", "<C-c>":
return
case "j", "<Down>":
l.ScrollDown()
case "k", "<Up>":
l.ScrollUp()
case "<C-d>":
l.ScrollHalfPageDown()
case "<C-u>":
l.ScrollHalfPageUp()
case "<C-f>":
l.ScrollPageDown()
case "<C-b>":
l.ScrollPageUp()
case "<Home>":
l.ScrollTop()
case "<End>":
l.ScrollBottom()
}
if previousKey == "g" {
switch e.ID {
case "g":
l.ScrollTop()
}
}
if e.ID == "g" {
previousKey = "g"
} else {
previousKey = ""
}
ui.Render(grid)
}
}
高度な機能
カスタムウィジェット
type CustomGauge struct {
widgets.Block
Percent int
BarColor ui.Color
Label string
}
func NewCustomGauge() *CustomGauge {
return &CustomGauge{
Block: *widgets.NewBlock(),
BarColor: ui.ColorGreen,
}
}
func (g *CustomGauge) Draw(buf *ui.Buffer) {
g.Block.Draw(buf)
// バーの描画
barWidth := int(float64(g.Inner.Dx()) * float64(g.Percent) / 100)
for x := g.Inner.Min.X; x < g.Inner.Min.X+barWidth; x++ {
for y := g.Inner.Min.Y; y < g.Inner.Max.Y; y++ {
buf.SetCell(ui.NewCell('█', ui.NewStyle(g.BarColor)), image.Pt(x, y))
}
}
// ラベル描画
label := fmt.Sprintf("%s: %d%%", g.Label, g.Percent)
labelX := g.Inner.Min.X + (g.Inner.Dx()-len(label))/2
labelY := g.Inner.Min.Y + g.Inner.Dy()/2
for i, r := range label {
buf.SetCell(ui.NewCell(r, ui.NewStyle(ui.ColorWhite)), image.Pt(labelX+i, labelY))
}
}
データストリーミング
func streamData(dataChan chan<- float64) {
for {
// 実際のデータソースから読み取り
value := rand.Float64() * 100
dataChan <- value
time.Sleep(100 * time.Millisecond)
}
}
func main() {
// ... 初期化コード ...
dataChan := make(chan float64, 100)
go streamData(dataChan)
data := make([]float64, 50)
for {
select {
case value := <-dataChan:
// データを更新
data = append(data[1:], value)
// チャートを更新
chart.Data[0] = data
ui.Render(chart)
case e := <-uiEvents:
if e.ID == "q" {
return
}
}
}
}
テーマのカスタマイズ
// カスタムテーマ定義
type Theme struct {
Background ui.Color
Border ui.Color
Title ui.Color
Text ui.Color
Highlight ui.Color
}
var DarkTheme = Theme{
Background: ui.ColorBlack,
Border: ui.ColorWhite,
Title: ui.ColorYellow,
Text: ui.ColorWhite,
Highlight: ui.ColorGreen,
}
func applyTheme(w *widgets.Paragraph, theme Theme) {
w.BorderStyle.Fg = theme.Border
w.TitleStyle.Fg = theme.Title
w.TextStyle.Fg = theme.Text
}
エコシステム
類似プロジェクト
- termdash: より高度なダッシュボードフレームワーク
- tcell: 低レベルターミナルライブラリ
- tui-go: 廃止されたが影響力のあったライブラリ
採用例
- gotop: システムモニタリングツール
- sampler: 設定ベースのモニタリングツール
- lazydocker: Docker管理ツール(一部で使用)
利点
- データ可視化: チャートとグラフのウィジェットが豊富
- ダッシュボード特化: モニタリングツールの構築が簡単
- シンプルAPI: 直感的で学習しやすい
- パフォーマンス: リアルタイムデータ表示に最適化
- カスタマイズ性: 独自ウィジェットの作成が可能
制約事項
- 用途限定: ダッシュボード以外の用途には向かない
- インタラクティブ性: 複雑な入力処理には不向き
- レイアウト: グリッドシステムが基本
- 更新頻度: v3以降の更新が停滞気味
他のライブラリとの比較
項目 | termui | Bubble Tea | tview |
---|---|---|---|
用途 | ダッシュボード | 汎用 | 汎用 |
データ可視化 | 優秀 | 要実装 | 基本的 |
学習コスト | 低 | 中 | 低 |
柔軟性 | 中 | 非常に高 | 高 |
アーキテクチャ | ウィジェット | Elm | ウィジェット |
まとめ
termuiは、Go言語でダッシュボードやモニタリングツールを構築するための優れたライブラリです。豊富なデータ可視化ウィジェット、シンプルなAPI、効率的なレンダリングにより、リアルタイムデータの表示やシステムモニタリングに最適です。汎用的なTUIアプリケーションには向きませんが、その専門性により、ダッシュボード開発においては最も生産的な選択肢の一つです。