termui

TUIDashboardChartsMonitoringGo

GitHub概要

gizak/termui

Golang terminal dashboard

スター13,393
ウォッチ285
フォーク798
作成日:2015年2月3日
言語:Go
ライセンス:MIT License

トピックス

なし

スター履歴

gizak/termui Star History
データ取得日時: 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以降の更新が停滞気味

他のライブラリとの比較

項目termuiBubble Teatview
用途ダッシュボード汎用汎用
データ可視化優秀要実装基本的
学習コスト
柔軟性非常に高
アーキテクチャウィジェットElmウィジェット

まとめ

termuiは、Go言語でダッシュボードやモニタリングツールを構築するための優れたライブラリです。豊富なデータ可視化ウィジェット、シンプルなAPI、効率的なレンダリングにより、リアルタイムデータの表示やシステムモニタリングに最適です。汎用的なTUIアプリケーションには向きませんが、その専門性により、ダッシュボード開発においては最も生産的な選択肢の一つです。