Obsah
1. Jazyk Go a textový terminál ve funkci základního prvku uživatelského rozhraní (2.část)
2. Od knihovny aurora ke knihovně cfmt
3. Funkce nabízené knihovnou cfmt
4. Konce řádků ve vypisovaných zprávách
5. Kombinace funkcí z knihoven fmt a cfmt
6. Vytvoření řetězce se zprávou a současně i řídicími kódy pro změnu barvy
7. Naformátování zprávy společně s jejím obarvením
9. Knihovna asciigraph aneb jednoduché grafy vykreslené do textového terminálu
10. Vykreslení jednoduchého grafu z předpočítaných hodnot
12. Automatické odvození hodnot na x-ové ose
13. Automatické odvození hodnot na y-ové ose
14. Změna sklonu průběhu v nakresleném grafu
16. Nastavení dalších parametrů grafu
17. Hodnoty NaN a jejich vliv na vykreslení grafu
18. Nekonečna a jejich vliv na vykreslení grafu
19. Repositář s demonstračními příklady
1. Jazyk Go a textový terminál ve funkci základního prvku uživatelského rozhraní (2.část)
V dnešním článku se, podobně jako v článku předchozím, postupně zaměříme na popis knihoven určených pro práci s textovým terminálem z programovacího jazyka Go. Tyto knihovny se od sebe budou odlišovat svým zaměřením a můžeme je zhruba rozdělit do pěti kategorií:
- Knihovny zlepšující logování a podporující barevné zvýraznění logovacích zpráv na terminálu. Sem spadá například balíček zerolog popsaný minule.
- Knihovny určené pro podporu různých barev popředí a pozadí textu, změnu stylu textu atd. Příkladem může být balíček aurora zmíněný minule, popř. cfmt popsaný dnes, knihovna nazvaná go-colortext apod.
- Knihovny pro zobrazení jednoduchých textových dialogů a boxů se zprávami. Tuto problematiku řeší balíček box-cli-maker.
- Knihovny pro tvorbu grafů, „teploměrů“ atd. na ploše terminálu. Příkladem je balíček asciigraph. I tímto balíčkem se budeme zabývat v dnešním článku.
- Knihovny určené pro tvorbu plnohodnotných textových uživatelských rozhraní (TUI). Příkladem mohou být balíčky termui, termbox-go, progressbar atd.
Obrázek 1: Zobrazení logovacích informací s různou úrovní provedené přes knihovnu zerolog.
2. Od knihovny aurora ke knihovně cfmt
Ve druhé polovině předchozího článku jsme si ukázali základní možnosti použití knihovny nazvané aurora (což je pro knihovnu obarvující výstup na terminálu příhodné jméno). Tato knihovna umožňuje explicitní specifikaci barev textu, popř. jeho stylu. Pro tyto účely se používá objekt získaný konstruktorem aurora.NewAurora:
package main import ( "flag" "fmt" "github.com/logrusorgru/aurora" ) var colorizer aurora.Aurora func init() { var colors = flag.Bool("colors", false, "enable or disable colors") flag.Parse() colorizer = aurora.NewAurora(*colors) } func main() { fmt.Println(colorizer.Red("Test")) fmt.Println(colorizer.Green("Test")) fmt.Println(colorizer.Blue("Test")) fmt.Println(colorizer.Cyan("Test")) fmt.Println(colorizer.Magenta("Test")) fmt.Println(colorizer.Yellow("Test")) fmt.Println() fmt.Println(colorizer.Bold(colorizer.Red("Test"))) fmt.Println(colorizer.Bold(colorizer.Green("Test"))) fmt.Println(colorizer.Bold(colorizer.Blue("Test"))) fmt.Println(colorizer.Bold(colorizer.Cyan("Test"))) fmt.Println(colorizer.Bold(colorizer.Magenta("Test"))) fmt.Println(colorizer.Bold(colorizer.Yellow("Test"))) }
S výsledkem:
Obrázek 2: Knihovna aurora: kombinace tučného písma se specifikací barvy.
Možnosti poskytované touto knihovnou jsou sice široké, ale vyžadují relativně velké zásahy do zdrojového kódu; navíc je obarvení prováděno na poměrně nízké úrovni („toto má být červeně“, „tento text se zeleným pozadím“). Z důvodu zjednodušení a určité standardizace proto nad samotnou aurorou vznikla knihovna nazvaná cfmt, která obsahuje několik funkcí, jejichž názvy jsou sémantické – říkají tedy, jakého typu je určitá zpráva a nikoli, jak přesně má být obarvena. Tento přístup a ostatně i jména funkcí, jsou převzaty z Bootstrapu. Podívejme se nyní na jednoduchý příklad použití knihovny cfmt:
package main import ( "github.com/mingrammer/cfmt" ) func main() { cfmt.Success("Success message") cfmt.Info("Info message") cfmt.Warning("Warning message") cfmt.Error("Error message") }
S tímto výsledkem:
Obrázek 3: Výsledek zavolání čtyř základních funkcí z knihovny cfmt.
3. Funkce nabízené knihovnou cfmt
Funkce, které jsou nabízeny knihovnou cfmt, můžeme rozdělit podle toho, jaké zprávy zobrazují (úspěšná operace, informace, varování, chyby) a jak je zobrazují – s nebo bez konců řádku, s formátováním, s výstupem do řetězce, souboru atd.:
Typ funkce | Úspěšná operace | Informace | Varování | Chyba |
---|---|---|---|---|
tisk obarvené zprávy | Success | Info | Warning | Error |
přidání konce řádku | Successln | Infoln | Warningln | Errorln |
výstup do řetězce | Ssuccess | Sinfo | Swarning | Serror |
výstup do řetězce + konec řádku | Ssuccessln | Sinfoln | Swarningln | Serrorln |
naformátování zprávy | Successf | Infof | Warningf | Errorf |
výstup do souboru | Fsuccess | Finfo | Fwarning | Ferror |
výstup do souboru + konec řádku | Fsuccessln | Finfoln | Fwarningln | Ferrorln |
výstup do souboru s naformátováním | Fsuccessf | Finfof | Fwarningf | Ferrorf |
Znak „S“ na začátku názvu tedy značí výstup do řetězce zatímco „F“ výstup do souboru. Naproti tomu koncovka „ln“ označuje funkce, které provádí odřádkování a „f“ funkce umožňující naformátování zprávy.
4. Konce řádků ve vypisovaných zprávách
Výše zmíněné funkce Success, Info, Warning a Error z knihovny cfmt odpovídají svým chováním funkci Print ze standardního balíčku fmt, pochopitelně až na ten rozdíl, že text obarvují. Pokud tedy budeme potřebovat jednotlivé zprávy vypsat na nové řádky, je jedním z možných řešení explicitní zařazení znaku „\n“ do vypisovaných zpráv (v případě logování přes zerolog je to pochopitelně zbytečné):
package main import ( "github.com/mingrammer/cfmt" ) func main() { cfmt.Success("Success message\n") cfmt.Info("Info message\n") cfmt.Warning("Warning message\n") cfmt.Error("Error message\n") }
S výsledkem:
Obrázek 4: Explicitní zařazení znaku „\n“ do vypisovaných zpráv.
Ve standardní knihovně fmt je vedle funkce Print dostupná i funkce Println, která odřádkování provede automaticky. I pro tuto funkci existují v knihovně cfmt obdobné funkce končící na „ln“, které jsou ukázány v dalším demonstračním příkladu:
package main import ( "github.com/mingrammer/cfmt" ) func main() { cfmt.Successln("Success message") cfmt.Infoln("Info message") cfmt.Warningln("Warning message") cfmt.Errorln("Error message") }
S výsledkem:
Obrázek 5: Příklad použití funkcí *ln, které automaticky odřádkují.
5. Kombinace funkcí z knihoven fmt a cfmt
Jednou z předností knihovny cfmt je záruka, že volání jejích funkcí je možné bez problémů proložit s voláním funkcí ze standardní knihovny fmt. To ve skutečnosti pro jiné knihovny nemusí platit, a to například kvůli odlišnému řešení bufferů, specifikám různých operačních systémů atd. Následující příklad, který kombinuje volání funkcí z obou výše zmíněných knihoven, je tedy nejenom plně funkční, ale i přenositelný na různé operační systémy a platformy:
package main import ( "fmt" "github.com/mingrammer/cfmt" ) func main() { fmt.Print("1st line: ") cfmt.Successln("Success message") fmt.Print("2nd line: ") cfmt.Infoln("Info message") fmt.Print("3rd line: ") cfmt.Warningln("Warning message") fmt.Print("4th line: ") cfmt.Errorln("Error message") fmt.Println() fmt.Println("That's all...") }
Nyní by měl výsledek vypadat takto:
Obrázek 6: Prokládané volání funkcí z knihoven fmt a cfmt.
6. Vytvoření řetězce se zprávou a současně i řídicími kódy pro změnu barvy
Další čtyři funkce z knihovny cfmt začínají znakem „s“, takže připomínají funkci Sprint ze standardní knihovny fmt. Podobnost názvů v tomto případě pochopitelně není náhodná, protože funkce Ssuccessln, Sinfo, Swarningln a Serror skutečně převedou zprávu i s příslušnými řídicími kódy do řetězce, který lze následně použít libovolnou další funkcí jazyka Go (resp. pochopitelně funkcí, která na svém vstupu řetězec očekává):
package main import ( "fmt" "github.com/mingrammer/cfmt" ) func main() { var msg string msg = cfmt.Ssuccess("Success message") fmt.Printf("1st line: %s\n", msg) msg = cfmt.Sinfo("Info message") fmt.Printf("2nd line: %s\n", msg) msg = cfmt.Swarning("Warning message") fmt.Printf("3rd line: %s\n", msg) msg = cfmt.Serror("Error message") fmt.Printf("4th line: %s\n", msg) fmt.Println() fmt.Println("That's all...") }
Výsledek běhu tohoto příkladu může vypadat následovně:
Obrázek 7: Výpis řetězců získaných funkcemi Ssuccess, Sinfo, Swarning a Serror.
Podle očekávání existují i varianty těchto funkcí končících na „ln“, které za zprávu automaticky přidají znak pro konec řádku:
package main import ( "fmt" "github.com/mingrammer/cfmt" ) func main() { var msg string msg = cfmt.Ssuccessln("Success message") fmt.Printf("1st line: %s", msg) msg = cfmt.Sinfoln("Info message") fmt.Printf("2nd line: %s", msg) msg = cfmt.Swarningln("Warning message") fmt.Printf("3rd line: %s", msg) msg = cfmt.Serrorln("Error message") fmt.Printf("4th line: %s", msg) fmt.Println() fmt.Println("That's all...") }
Výsledek běhu tohoto příkladu může vypadat následovně:
Obrázek 7: Výpis řetězců získaných funkcemi Ssuccessln, Sinfoln, Swarningln a Serrorln.
7. Naformátování zprávy společně s jejím obarvením
Čtveřice funkcí nazvaných Successf, Infof, Warningf a Errorf se chová podobně jako funkce Printf ze standardní knihovny fmt – funkce očekává, že prvním parametrem bude formátovací řetězec, na jehož základě se zkontrolují a zpracují případné další parametry. Výstup je následně obarven podle toho, která konkrétní funkce byla právě zavolána. Chování si můžeme odzkoušet na dalším demonstračním příkladu, který vyhodnotí, zda je vstupní číselná hodnota příliš malá, velká, nebo „právě akorát“:
package main import ( "fmt" "github.com/mingrammer/cfmt" ) func main() { for i := 1; i <= 10; i++ { switch { case i < 5: cfmt.Warningf("Value too low: %d\n", i) case i == 5: cfmt.Successf("An ideal value: %d\n", i) case i == 10: cfmt.Errorf("Too high!!! %d\n", i) case i > 5: cfmt.Infof("Bit higher then necessary: %d\n", i) default: cfmt.Errorf("Can't happen %d\n", i) } } fmt.Println() fmt.Println("That's all...") }
Výsledek po spuštění příkladu:
Obrázek 8: Naformátované a obarvené zprávy.
Pro zajímavost se ještě můžeme podívat, jak vlastně vypadají řetězce produkované knihovnou cfmt, a to na úrovni jednotlivých bajtů. Pomůžeme si následujícím příkladem, který vypíše obsah jednotlivých řetězců převedených na pole bajtů s využitím standardní knihovny encoding/hex:
package main import ( "encoding/hex" "fmt" "github.com/mingrammer/cfmt" ) func printEncoded(s string) { bytes := []byte(s) fmt.Printf("Encoded:\n%s\n", hex.Dump(bytes)) } func main() { var msg string msg = cfmt.Ssuccessln("Success message") fmt.Printf("1st line: %s", msg) printEncoded(msg) msg = cfmt.Sinfoln("Info message") fmt.Printf("2nd line: %s", msg) printEncoded(msg) msg = cfmt.Swarningln("Warning message") fmt.Printf("3rd line: %s", msg) printEncoded(msg) msg = cfmt.Serrorln("Error message") fmt.Printf("4th line: %s", msg) printEncoded(msg) fmt.Println() fmt.Println("That's all...") }
Výsledek po spuštění příkladu:
Obrázek 9: Naformátované a obarvené zprávy společně s jejich binárním obsahem vypsaným ve formě „hex dumpu“.
8. Výstup do souboru
Knihovna cfmt podporuje i výpis obarvených zpráv přímo do souboru. Pro tuto činnost se používají funkce začínající na „F“, přesněji řečeno v praxi především funkce Fsuccessln, Finfoln, Fwarningln a Ferrorln, popř. jejich varianty bez „ln“. Podívejme se na způsob použití těchto funkcí:
package main import ( "log" "os" "github.com/mingrammer/cfmt" ) func main() { file, err := os.Create("./temp.txt") if err != nil { log.Fatal(err) } defer file.Close() cfmt.Fsuccessln(file, "Success message") cfmt.Finfoln(file, "Info message") cfmt.Fwarningln(file, "Warning message") cfmt.Ferrorln(file, "Error message") }
K dispozici jsou i funkce zajišťující výstup naformátované zprávy do souboru. Tyto funkce začínají znakem „F“ a končí znakem „f“. Příklad z předchozí kapitoly tedy můžeme snadno upravit:
package main import ( "fmt" "log" "os" "github.com/mingrammer/cfmt" ) func main() { file, err := os.Create("./temp.txt") if err != nil { log.Fatal(err) } defer file.Close() for i := 1; i <= 10; i++ { switch { case i < 5: cfmt.Fwarningf(file, "Value too low: %d\n", i) case i == 5: cfmt.Fsuccessf(file, "An ideal value: %d\n", i) case i == 10: cfmt.Ferrorf(file, "Too high!!! %d\n", i) case i > 5: cfmt.Finfof(file, "Bit higher then necessary: %d\n", i) default: cfmt.Ferrorf(file, "Can't happen %d\n", i) } } fmt.Println() fmt.Println("That's all...") }
9. Knihovna asciigraph aneb jednoduché grafy vykreslené do textového terminálu
Druhá knihovna pro jazyk Go, se kterou se dnes seznámíme, se jmenuje asciigraph. Je to přiléhavé a současně i zavádějící jméno, protože tato knihovna sice umožňuje vykreslení grafů (průběhů funkcí či naměřených hodnot) na textovém terminálu, ovšem pro tento účel využívá znaky z Unicode a nikoli z jeho (maličké) podmnožiny ASCII. Praktické použití této knihovny je triviální, protože postačuje funkci nazvané Plot předat data, která se mají vykreslit (konkrétně se jedná o řez polem typu float64) a výsledkem je řetězec s požadovaným grafem. V případě potřeby je pochopitelně možné předat funkci Plot i další parametry grafu. Výsledný řetězec je možné zobrazit na terminálu, uložit do souboru, poslat přes HTTP uživateli atd.
10. Vykreslení jednoduchého grafu z předpočítaných hodnot
První demonstrační příklad založený na knihovně asciigraph je převzat přímo ze stránek tohoto projektu a byl pouze nepatrně upraven. Je v něm ukázáno, jak lze vykreslit graf hodnot typu float64, což je v jazyce Go obdoba céčkovského datového typu double. Pro samotné vykreslení stačí zavolat funkci Plot a výsledný řetězec vypsat standardní funkcí fmt.Println:
package main import ( "fmt" "github.com/guptarohit/asciigraph" ) func main() { data := []float64{3, 4, 5, 6, 9, 8, 5, 8, 6, 10, 2, 7, 2, 5, 6} graph := asciigraph.Plot(data) fmt.Println(graph) }
Výsledek vypadá následovně:
10.00 ┤ ╭╮ 9.00 ┤ ╭╮ ││ 8.00 ┤ │╰╮╭╮││ 7.00 ┤ │ │││││╭╮ 6.00 ┤ ╭╯ ││╰╯│││ ╭ 5.00 ┤ ╭╯ ╰╯ │││╭╯ 4.00 ┤╭╯ ││││ 3.00 ┼╯ ││││ 2.00 ┤ ╰╯╰╯
11. Vykreslení průběhu funkce
Pokusme se nyní o vykreslení průběhu funkce, přičemž jednotlivé hodnoty vypočteme v programové smyčce a uložíme do pole s prvky typu float64:
package main import ( "fmt" "github.com/guptarohit/asciigraph" ) const SAMPLES = 40 func main() { var values [SAMPLES]float64 for i := 0; i < SAMPLES; i++ { values[i] = float64(i % 10) } graph := asciigraph.Plot(values) fmt.Println(graph) }
Zdrojový kód tohoto příkladu sice vypadá bezchybně, ale nepůjde přeložit. Je tomu tak z toho důvodu, že se do funkce Plot snažíme předat pole (array) prvků typu float64 a nikoli řez (slice)
./02-computed-values.go:17:26: cannot use values (type [40]float64) as type []float64 in argument to asciigraph.Plot
Řešením je (podle očekávání) získání řezu z pole, což je operace, která nevyžaduje kopii dat a je tedy velmi rychlá a paměťově efektivní:
package main import ( "fmt" "github.com/guptarohit/asciigraph" ) const SAMPLES = 40 func main() { var values [SAMPLES]float64 for i := 0; i < SAMPLES; i++ { values[i] = float64(i % 10) } graph := asciigraph.Plot(values[:]) fmt.Println(graph) }
Nyní již bude vše plně funkční:
9.00 ┼ ╭╮ ╭╮ ╭╮ ╭ 8.00 ┤ ╭╯│ ╭╯│ ╭╯│ ╭╯ 7.00 ┤ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ 6.00 ┤ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ 5.00 ┤ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ 4.00 ┤ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ 3.00 ┤ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ 2.00 ┤ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ 1.00 ┤╭╯ │╭╯ │╭╯ │╭╯ 0.00 ┼╯ ╰╯ ╰╯ ╰╯
12. Automatické odvození hodnot na x-ové ose
V případě, že se při tvorbě grafu nespecifikují jeho parametry, je jeho šířka (měřená ve znacích) přímo odvozena od počtu hodnot, které jsou funkci Plot předány. Mnohdy to znamená, že graf je širší než okno terminálu a jeho řádky jsou zalomeny, což je pochopitelně situace, které je nutné se v praxi vyhnout:
package main import ( "fmt" "github.com/guptarohit/asciigraph" ) const SAMPLES = 200 func main() { var values [SAMPLES]float64 for i := 0; i < SAMPLES; i++ { values[i] = float64(i / 2 % 16) } graph := asciigraph.Plot(values[:]) fmt.Println(graph) }
Graf bude nyní na šířku měřit 207 znaků, což u většiny uživatelů přesáhne nastavenou (i maximální možnou) šířku okna terminálu:
15.00 ┼ ╭─╮ ╭─╮ ╭─╮ ╭─╮ ╭─╮ ╭─╮ 14.00 ┤ ╭─╯ │ ╭─╯ │ ╭─╯ │ ╭─╯ │ ╭─╯ │ ╭─╯ │ 13.00 ┤ ╭─╯ │ ╭─╯ │ ╭─╯ │ ╭─╯ │ ╭─╯ │ ╭─╯ │ 12.00 ┤ ╭─╯ │ ╭─╯ │ ╭─╯ │ ╭─╯ │ ╭─╯ │ ╭─╯ │ 11.00 ┤ ╭─╯ │ ╭─╯ │ ╭─╯ │ ╭─╯ │ ╭─╯ │ ╭─╯ │ 10.00 ┤ ╭─╯ │ ╭─╯ │ ╭─╯ │ ╭─╯ │ ╭─╯ │ ╭─╯ │ 9.00 ┤ ╭─╯ │ ╭─╯ │ ╭─╯ │ ╭─╯ │ ╭─╯ │ ╭─╯ │ 8.00 ┤ ╭─╯ │ ╭─╯ │ ╭─╯ │ ╭─╯ │ ╭─╯ │ ╭─╯ │ 7.00 ┤ ╭─╯ │ ╭─╯ │ ╭─╯ │ ╭─╯ │ ╭─╯ │ ╭─╯ │ 6.00 ┤ ╭─╯ │ ╭─╯ │ ╭─╯ │ ╭─╯ │ ╭─╯ │ ╭─╯ │ 5.00 ┤ ╭─╯ │ ╭─╯ │ ╭─╯ │ ╭─╯ │ ╭─╯ │ ╭─╯ │ 4.00 ┤ ╭─╯ │ ╭─╯ │ ╭─╯ │ ╭─╯ │ ╭─╯ │ ╭─╯ │ 3.00 ┤ ╭─╯ │ ╭─╯ │ ╭─╯ │ ╭─╯ │ ╭─╯ │ ╭─╯ │ ╭─ 2.00 ┤ ╭─╯ │ ╭─╯ │ ╭─╯ │ ╭─╯ │ ╭─╯ │ ╭─╯ │ ╭─╯ 1.00 ┤ ╭─╯ │ ╭─╯ │ ╭─╯ │ ╭─╯ │ ╭─╯ │ ╭─╯ │ ╭─╯ 0.00 ┼─╯ ╰─╯ ╰─╯ ╰─╯ ╰─╯ ╰─╯ ╰─╯
13. Automatické odvození hodnot na y-ové ose
I rozsah hodnot na y-ové ose a tedy i výška grafu je při implicitním nastavení odvozena od vstupních hodnot předávaných do funkce Plot. Toto chování knihovny asciigraph si můžeme demonstrovat na příkladu, ve kterém se postupně zvětšuje rozsah hodnot (obor hodnot) vykreslované funkce. I příslušné grafy se budou postupně zvyšovat:
package main import ( "fmt" "github.com/guptarohit/asciigraph" ) const SAMPLES = 64 func displayGraph(modulus int) { var values [SAMPLES]float64 for i := 0; i < SAMPLES; i++ { values[i] = float64(i % modulus) } graph := asciigraph.Plot(values[:]) fmt.Println(graph) } func main() { for modulus := 2; modulus <= 64; modulus *= 2 { displayGraph(modulus) fmt.Println("\n") } }
Postupná změna výšky grafu je jasně patrná:
1.00 ┼╭╮╭╮╭╮╭╮╭╮╭╮╭╮╭╮╭╮╭╮╭╮╭╮╭╮╭╮╭╮╭╮╭╮╭╮╭╮╭╮╭╮╭╮╭╮╭╮╭╮╭╮╭╮╭╮╭╮╭╮╭╮╭ 0.00 ┼╯╰╯╰╯╰╯╰╯╰╯╰╯╰╯╰╯╰╯╰╯╰╯╰╯╰╯╰╯╰╯╰╯╰╯╰╯╰╯╰╯╰╯╰╯╰╯╰╯╰╯╰╯╰╯╰╯╰╯╰╯╰╯ 3.00 ┼ ╭╮ ╭╮ ╭╮ ╭╮ ╭╮ ╭╮ ╭╮ ╭╮ ╭╮ ╭╮ ╭╮ ╭╮ ╭╮ ╭╮ ╭╮ ╭ 2.00 ┤ ╭╯│ ╭╯│ ╭╯│ ╭╯│ ╭╯│ ╭╯│ ╭╯│ ╭╯│ ╭╯│ ╭╯│ ╭╯│ ╭╯│ ╭╯│ ╭╯│ ╭╯│ ╭╯ 1.00 ┤╭╯ │╭╯ │╭╯ │╭╯ │╭╯ │╭╯ │╭╯ │╭╯ │╭╯ │╭╯ │╭╯ │╭╯ │╭╯ │╭╯ │╭╯ │╭╯ 0.00 ┼╯ ╰╯ ╰╯ ╰╯ ╰╯ ╰╯ ╰╯ ╰╯ ╰╯ ╰╯ ╰╯ ╰╯ ╰╯ ╰╯ ╰╯ ╰╯ 7.00 ┼ ╭╮ ╭╮ ╭╮ ╭╮ ╭╮ ╭╮ ╭╮ ╭ 6.00 ┤ ╭╯│ ╭╯│ ╭╯│ ╭╯│ ╭╯│ ╭╯│ ╭╯│ ╭╯ 5.00 ┤ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ 4.00 ┤ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ 3.00 ┤ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ 2.00 ┤ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ 1.00 ┤╭╯ │╭╯ │╭╯ │╭╯ │╭╯ │╭╯ │╭╯ │╭╯ 0.00 ┼╯ ╰╯ ╰╯ ╰╯ ╰╯ ╰╯ ╰╯ ╰╯ 15.00 ┼ ╭╮ ╭╮ ╭╮ ╭ 14.00 ┤ ╭╯│ ╭╯│ ╭╯│ ╭╯ 13.00 ┤ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ 12.00 ┤ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ 11.00 ┤ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ 10.00 ┤ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ 9.00 ┤ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ 8.00 ┤ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ 7.00 ┤ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ 6.00 ┤ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ 5.00 ┤ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ 4.00 ┤ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ 3.00 ┤ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ 2.00 ┤ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ 1.00 ┤╭╯ │╭╯ │╭╯ │╭╯ 0.00 ┼╯ ╰╯ ╰╯ ╰╯ 31.00 ┼ ╭╮ ╭ 30.00 ┤ ╭╯│ ╭╯ 29.00 ┤ ╭╯ │ ╭╯ 28.00 ┤ ╭╯ │ ╭╯ 27.00 ┤ ╭╯ │ ╭╯ 26.00 ┤ ╭╯ │ ╭╯ 25.00 ┤ ╭╯ │ ╭╯ 24.00 ┤ ╭╯ │ ╭╯ 23.00 ┤ ╭╯ │ ╭╯ 22.00 ┤ ╭╯ │ ╭╯ 21.00 ┤ ╭╯ │ ╭╯ 20.00 ┤ ╭╯ │ ╭╯ 19.00 ┤ ╭╯ │ ╭╯ 18.00 ┤ ╭╯ │ ╭╯ 17.00 ┤ ╭╯ │ ╭╯ 16.00 ┤ ╭╯ │ ╭╯ 15.00 ┤ ╭╯ │ ╭╯ 14.00 ┤ ╭╯ │ ╭╯ 13.00 ┤ ╭╯ │ ╭╯ 12.00 ┤ ╭╯ │ ╭╯ 11.00 ┤ ╭╯ │ ╭╯ 10.00 ┤ ╭╯ │ ╭╯ 9.00 ┤ ╭╯ │ ╭╯ 8.00 ┤ ╭╯ │ ╭╯ 7.00 ┤ ╭╯ │ ╭╯ 6.00 ┤ ╭╯ │ ╭╯ 5.00 ┤ ╭╯ │ ╭╯ 4.00 ┤ ╭╯ │ ╭╯ 3.00 ┤ ╭╯ │ ╭╯ 2.00 ┤ ╭╯ │ ╭╯ 1.00 ┤╭╯ │╭╯ 0.00 ┼╯ ╰╯
14. Změna sklonu průběhu v nakresleném grafu
Ještě si vyzkoušejme, jakým způsobem se knihovna asciigraph vyrovná se změnou sklonu nakresleného průběhu funkce. V případě grafického výstupu (ovšem s dostatečným rozlišením) to je pochopitelně snadná úloha, ovšem v textovém terminálu je situace složitější:
package main import ( "fmt" "github.com/guptarohit/asciigraph" ) const SAMPLES = 64 func displayGraph(modulus int, slope int) { var values [SAMPLES]float64 for i := 0; i < SAMPLES; i++ { values[i] = float64(i / slope % modulus) } graph := asciigraph.Plot(values[:]) fmt.Println(graph) } func main() { const modulus = 16 for slope := 1; slope <= 5; slope++ { displayGraph(modulus, slope) fmt.Println("\n") } }
Z výsledků je zřetelné, že se stále používá stejná skupina Unicode znaků, nezávisle na tom, jaký je sklon:
15.00 ┼ ╭╮ ╭╮ ╭╮ ╭ 14.00 ┤ ╭╯│ ╭╯│ ╭╯│ ╭╯ 13.00 ┤ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ 12.00 ┤ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ 11.00 ┤ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ 10.00 ┤ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ 9.00 ┤ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ 8.00 ┤ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ 7.00 ┤ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ 6.00 ┤ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ 5.00 ┤ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ 4.00 ┤ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ 3.00 ┤ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ 2.00 ┤ ╭╯ │ ╭╯ │ ╭╯ │ ╭╯ 1.00 ┤╭╯ │╭╯ │╭╯ │╭╯ 0.00 ┼╯ ╰╯ ╰╯ ╰╯ 15.00 ┼ ╭─╮ ╭─ 14.00 ┤ ╭─╯ │ ╭─╯ 13.00 ┤ ╭─╯ │ ╭─╯ 12.00 ┤ ╭─╯ │ ╭─╯ 11.00 ┤ ╭─╯ │ ╭─╯ 10.00 ┤ ╭─╯ │ ╭─╯ 9.00 ┤ ╭─╯ │ ╭─╯ 8.00 ┤ ╭─╯ │ ╭─╯ 7.00 ┤ ╭─╯ │ ╭─╯ 6.00 ┤ ╭─╯ │ ╭─╯ 5.00 ┤ ╭─╯ │ ╭─╯ 4.00 ┤ ╭─╯ │ ╭─╯ 3.00 ┤ ╭─╯ │ ╭─╯ 2.00 ┤ ╭─╯ │ ╭─╯ 1.00 ┤ ╭─╯ │ ╭─╯ 0.00 ┼─╯ ╰─╯ 15.00 ┼ ╭──╮ 14.00 ┤ ╭──╯ │ 13.00 ┤ ╭──╯ │ 12.00 ┤ ╭──╯ │ 11.00 ┤ ╭──╯ │ 10.00 ┤ ╭──╯ │ 9.00 ┤ ╭──╯ │ 8.00 ┤ ╭──╯ │ 7.00 ┤ ╭──╯ │ 6.00 ┤ ╭──╯ │ 5.00 ┤ ╭──╯ │ ╭ 4.00 ┤ ╭──╯ │ ╭──╯ 3.00 ┤ ╭──╯ │ ╭──╯ 2.00 ┤ ╭──╯ │ ╭──╯ 1.00 ┤ ╭──╯ │ ╭──╯ 0.00 ┼──╯ ╰──╯ 15.00 ┼ ╭─── 14.00 ┤ ╭───╯ 13.00 ┤ ╭───╯ 12.00 ┤ ╭───╯ 11.00 ┤ ╭───╯ 10.00 ┤ ╭───╯ 9.00 ┤ ╭───╯ 8.00 ┤ ╭───╯ 7.00 ┤ ╭───╯ 6.00 ┤ ╭───╯ 5.00 ┤ ╭───╯ 4.00 ┤ ╭───╯ 3.00 ┤ ╭───╯ 2.00 ┤ ╭───╯ 1.00 ┤ ╭───╯ 0.00 ┼───╯ 12.00 ┼ ╭─── 11.00 ┤ ╭────╯ 10.00 ┤ ╭────╯ 9.00 ┤ ╭────╯ 8.00 ┤ ╭────╯ 7.00 ┤ ╭────╯ 6.00 ┤ ╭────╯ 5.00 ┤ ╭────╯ 4.00 ┤ ╭────╯ 3.00 ┤ ╭────╯ 2.00 ┤ ╭────╯ 1.00 ┤ ╭────╯ 0.00 ┼────╯
15. Záporné hodnoty v grafu
Prozatím jsme v grafu používali funkce (resp. jejich navzorkované hodnoty), které byly kladné. Ovšem knihovna asciigraph pochopitelně podporuje i záporné hodnoty, o čemž se můžeme velmi snadno přesvědčit po spuštění následujícího demonstračního příkladu:
package main import ( "fmt" "github.com/guptarohit/asciigraph" ) const SAMPLES = 64 func displayGraph(modulus int, slope int) { var values [SAMPLES]float64 for i := 0; i < SAMPLES; i++ { values[i] = float64((i - SAMPLES/2) / slope % modulus) } graph := asciigraph.Plot(values[:]) fmt.Println(graph) } func main() { const modulus = 16 for slope := 1; slope <= 5; slope++ { displayGraph(modulus, slope) fmt.Println("\n") } }
S výsledky:
15.00 ┤ ╭╮ ╭ 14.00 ┤ ╭╯│ ╭╯ 13.00 ┤ ╭╯ │ ╭╯ 12.00 ┤ ╭╯ │ ╭╯ 11.00 ┤ ╭╯ │ ╭╯ 10.00 ┤ ╭╯ │ ╭╯ 9.00 ┤ ╭╯ │ ╭╯ 8.00 ┤ ╭╯ │ ╭╯ 7.00 ┤ ╭╯ │ ╭╯ 6.00 ┤ ╭╯ │ ╭╯ 5.00 ┤ ╭╯ │ ╭╯ 4.00 ┤ ╭╯ │ ╭╯ 3.00 ┤ ╭╯ │ ╭╯ 2.00 ┤ ╭╯ │ ╭╯ 1.00 ┤ ╭╯ │╭╯ 0.00 ┼╮ ╭╮ ╭╯ ╰╯ -1.00 ┤│ ╭╯│ ╭╯ -2.00 ┤│ ╭╯ │ ╭╯ -3.00 ┤│ ╭╯ │ ╭╯ -4.00 ┤│ ╭╯ │ ╭╯ -5.00 ┤│ ╭╯ │ ╭╯ -6.00 ┤│ ╭╯ │ ╭╯ -7.00 ┤│ ╭╯ │ ╭╯ -8.00 ┤│ ╭╯ │ ╭╯ -9.00 ┤│ ╭╯ │ ╭╯ -10.00 ┤│ ╭╯ │ ╭╯ -11.00 ┤│ ╭╯ │ ╭╯ -12.00 ┤│ ╭╯ │ ╭╯ -13.00 ┤│ ╭╯ │ ╭╯ -14.00 ┤│╭╯ │╭╯ -15.00 ┤╰╯ ╰╯
16. Nastavení dalších parametrů grafu
Prozatím jsme ve všech předchozích demonstračních příkladech používali implicitní parametry grafu, které byly odvozeny od hodnot předaných funkci Plot. Na základě počtu hodnot se stanovila šířka grafu a podle rozsahu hodnot pak jeho výška. Ovšem tyto dva parametry lze specifikovat explicitně (ve znacích):
width := asciigraph.Width(40) height := asciigraph.Height(20)
Dále je možné specifikovat popisek grafu:
caption := asciigraph.Caption(fmt.Sprintf("Modulus: %d, slope: %d", modulus, slope))
Všechna tato nastavení realizují rozhraní Interface je možné je před funkci Plot, a to v libovolném pořadí:
graph := asciigraph.Plot(values[:], width, height, caption)
Následuje ukázka využití těchto explicitně zadaných parametrů při vykreslení grafů:
package main import ( "fmt" "github.com/guptarohit/asciigraph" ) const SAMPLES = 64 func displayGraph(modulus int, slope int) { var values [SAMPLES]float64 for i := 0; i < SAMPLES; i++ { values[i] = float64((i - SAMPLES/2) / slope % modulus) } width := asciigraph.Width(40) height := asciigraph.Height(20) caption := asciigraph.Caption(fmt.Sprintf("Modulus: %d, slope: %d", modulus, slope)) graph := asciigraph.Plot(values[:], width, height, caption) fmt.Println(graph) } func main() { const modulus = 16 for slope := 1; slope <= 5; slope++ { displayGraph(modulus, slope) fmt.Println("\n") } }
S výsledky:
15.00 ┤ ╭╮ ╭ 13.53 ┤ ╭╯│ ╭╯ 12.06 ┤ ╭╯ │ ╭╯ 10.59 ┤ ╭╯ │ ╭╯ 9.12 ┤ ╭╯ │ ╭╯ 7.65 ┤ ╭╯ │ ╭╯ 6.18 ┤ ╭╯ │ ╭╯ 4.72 ┤ │ │ ╭╯ 3.25 ┤ ╭╯ │ │ 1.78 ┤ ╭╯ │╭╯ 0.31 ┼╮ ╭╯ ╰╯ -1.16 ┤│ ╭╮ ╭╯ -2.63 ┤│ ╭╯╰╮ ╭╯ -4.10 ┤│ ╭╯ │ ╭╯ -5.57 ┤│ ╭╯ │ ╭╯ -7.04 ┤│ ╭╯ │ ╭╯ -8.51 ┤│ ╭╯ │ ╭╯ -9.98 ┤│ │ │ ╭╯ -11.45 ┤│ ╭╯ │ │ -12.92 ┤│╭╯ │╭╯ -14.38 ┤╰╯ ╰╯ Modulus: 16, slope: 1 6.00 ┤ ╭ 5.40 ┤ ╭╯ 4.80 ┤ ╭──╯ 4.20 ┤ ╭─╯ 3.60 ┤ ╭╯ 3.00 ┤ ╭──╯ 2.40 ┤ │ 1.80 ┤ ╭──╯ 1.20 ┤ ╭──╯ 0.60 ┤ │ 0.00 ┼ ╭────╯ -0.60 ┤ ╭╯ -1.20 ┤ ╭──╯ -1.80 ┤ ╭─╯ -2.40 ┤ ╭╯ -3.00 ┤ ╭──╯ -3.60 ┤ │ -4.20 ┤ ╭──╯ -4.80 ┤ ╭──╯ -5.40 ┤ │ -6.00 ┼─╯ Modulus: 16, slope: 5
17. Hodnoty NaN a jejich vliv na vykreslení grafu
Mezi hodnoty reprezentovatelné datovým typem float64 patří i NaN neboli „Not a Number“. Ty se mohou objevit jak ve vstupních datech, tak i při výpočtech (přesně dle normy IEEE 754). Bude tedy vhodné zjistit, jak se existence těchto hodnot projeví na vykresleném grafu:
package main import ( "fmt" "math" "github.com/guptarohit/asciigraph" ) func main() { data := []float64{3, 4, 5, 6, 9, 8, 5, math.NaN(), 6, 10, 2, 7, 2, 5, 6} graph := asciigraph.Plot(data) fmt.Println(graph) }
Graf vykreslený knihovnou asciigraph vypadá takto:
10.00 ┤ ╭╮ 9.00 ┤ ╭╮ ││ 8.00 ┤ │╰╮ ││ 7.00 ┤ │ │ ││╭╮ 6.00 ┤ ╭╯ │ ╶╯│││ ╭ 5.00 ┤ ╭╯ ╰╴ │││╭╯ 4.00 ┤╭╯ ││││ 3.00 ┼╯ ││││ 2.00 ┤ ╰╯╰╯
18. Nekonečna a jejich vliv na vykreslení grafu
Datový typ float64 ovšem podporuje i kladné a záporné nekonečno. To v jazyku Go získáme zavoláním funkce math.Inf(), přičemž znaménko vstupní hodnoty určuje, zda se bude jednat o nekonečno kladné nebo záporné. Opět se pokusíme vytvořit graf s těmito hodnotami:
package main import ( "fmt" "math" "github.com/guptarohit/asciigraph" ) func main() { data := []float64{3, 4, 5, 6, 9, 8, 5, math.Inf(1), 6, 10, 2, math.Inf(-1), 2, 5, 6} graph := asciigraph.Plot(data) fmt.Println(graph) }
Tentokrát ovšem nebude vykreslení úspěšné, což znamená, že je nutné tyto vstupní hodnoty filtrovat ještě před pokusem o vykreslení grafu:
panic: runtime error: index out of range [-9223372036854775808] goroutine 1 [running]: github.com/guptarohit/asciigraph.Plot(0xc000055ec8, 0xf, 0xf, 0x0, 0x0, 0x0, 0xc000010260, 0xc000076ef0) /home/ptisnovs/go/src/github.com/guptarohit/asciigraph/asciigraph.go:109 +0x16fd main.main() /home/ptisnovs/src/go-root/article_77/asciigraph/10-infinity.go:12 +0xac exit status 2
19. Repositář s demonstračními příklady
Zdrojové kódy všech dnes použitých demonstračních příkladů byly uloženy do nového Git repositáře, který je dostupný na adrese https://github.com/tisnik/go-root (stále na GitHubu :-). V případě, že nebudete chtít klonovat celý repositář (ten je ovšem – alespoň prozatím – velmi malý, dnes má přibližně stovku kilobajtů), můžete namísto toho použít odkazy na jednotlivé demonstrační příklady, které naleznete v následující tabulce:
20. Odkazy na Internetu
- Jazyk Go a textový terminál ve funkci základního prvku uživatelského rozhraní
https://www.root.cz/clanky/jazyk-go-a-textovy-terminal-ve-funkci-zakladniho-prvku-uzivatelskeho-rozhrani/ - Tvorba aplikací a her s textovým uživatelským rozhraním s využitím knihovny Blessed
https://www.root.cz/clanky/tvorba-aplikaci-a-her-s-textovym-uzivatelskym-rozhranim-s-vyuzitim-knihovny-blessed/ - Tvorba aplikací a her s textovým rozhraním s knihovnou Blessed (dokončení)
https://www.root.cz/clanky/tvorba-aplikaci-a-her-s-textovym-rozhranim-s-knihovnou-blessed-dokonceni/ - ANSI Escape Code – Colors
https://en.wikipedia.org/wiki/ANSI_escape_code#Colors - A curated list of awesome Go frameworks, libraries and software
https://awesome-go.com/ - Aurora
https://github.com/logrusorgru/aurora - colourize
https://github.com/TreyBastian/colourize - go-colortext
https://github.com/daviddengcn/go-colortext - blessed na PyPi
https://pypi.org/project/blessed/ - blessed na GitHubu
https://github.com/jquast/blessed - Blessed documentation!
https://blessed.readthedocs.io/en/latest/ - termbox-go na GitHubu
https://github.com/nsf/termbox-go - termui na GitHubu
https://github.com/gizak/termui - blessed na GitHubu
https://github.com/chjj/blessed - blessed-contrib na GitHubu
https://github.com/yaronn/blessed-contrib - tui-rs na GitHubu
https://github.com/fdehau/tui-rs - asciigraph
https://github.com/guptarohit/asciigraph - Standardní balíček text/tabwriter
https://golang.org/pkg/text/tabwriter/ - Elastic tabstops: A better way to indent and align code
https://nickgravgaard.com/elastic-tabstops/ - ASCII Table Writer
https://github.com/olekukonko/tablewriter - TablePrinter
https://github.com/lensesio/tableprinter - go-pretty
https://github.com/jedib0t/go-pretty - What are the drawbacks of elastic tabstops?
https://softwareengineering.stackexchange.com/questions/137290/what-are-the-drawbacks-of-elastic-tabstops - Elastic tabstop editors and plugins
https://stackoverflow.com/questions/28652/elastic-tabstop-editors-and-plugins - Příkaz gofmt
https://golang.org/cmd/gofmt/ - Spaces vs. Tabs: A 20-Year Debate Reignited by Google’s Golang
https://thenewstack.io/spaces-vs-tabs-a-20-year-debate-and-now-this-what-the-hell-is-wrong-with-go/ - zerolog
https://github.com/rs/zerolog - Zero Allocation JSON Logger na Go.doc
https://pkg.go.dev/github.com/rs/zerolog?utm_source=godoc - cfmt
https://github.com/mingrammer/cfmt - box-cli-maker
https://github.com/Delta456/box-cli-maker - Who uses zerolog
https://github.com/rs/zerolog/wiki/Who-uses-zerolog - Go Progress Bar
https://github.com/ermanimer/progress_bar