mirror of
https://github.com/navidys/tvxwidgets.git
synced 2025-04-24 13:48:51 +08:00
Compare commits
122 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
7b0dba28a6 | ||
![]() |
f7a965b9b1 | ||
![]() |
7a7632ee73 | ||
![]() |
5c75618ec1 | ||
![]() |
657ca691c4 | ||
![]() |
5c8253e8d9 | ||
![]() |
487c87b304 | ||
![]() |
d80a351c3c | ||
![]() |
9dc06ec28d | ||
![]() |
04fd610c5b | ||
![]() |
8a454d1c5e | ||
![]() |
b6def86254 | ||
![]() |
f97e54d849 | ||
![]() |
3ddb90bd10 | ||
![]() |
091fcae5c5 | ||
![]() |
12e9f953a3 | ||
![]() |
fb7e4a8141 | ||
![]() |
362ed76a2b | ||
![]() |
a6b632c4f6 | ||
![]() |
7e165355fc | ||
![]() |
00c6cca493 | ||
![]() |
543712cedb | ||
![]() |
329fcead2e | ||
![]() |
ca09c00451 | ||
![]() |
2036aacbcb | ||
![]() |
253e22ef1a | ||
![]() |
ac5e17721f | ||
![]() |
d3a7c777e0 | ||
![]() |
63d5ed1494 | ||
![]() |
0d4a60f69d | ||
![]() |
ed9012b1bf | ||
![]() |
5d0440879d | ||
![]() |
bb6ca0a79c | ||
![]() |
bba8fe74ca | ||
![]() |
6119df590d | ||
![]() |
004135d96f | ||
![]() |
cb36b86f14 | ||
![]() |
d3cf38290d | ||
![]() |
d46f48fbe0 | ||
![]() |
d3dc728102 | ||
![]() |
40e5944bdc | ||
![]() |
3cfbbb446a | ||
![]() |
718914020b | ||
![]() |
a89f40eb4c | ||
![]() |
d550a262e6 | ||
![]() |
e468a23392 | ||
![]() |
83dbdb49e6 | ||
![]() |
7e3024f613 | ||
![]() |
4632165f80 | ||
![]() |
9d6693060a | ||
![]() |
eafbe24268 | ||
![]() |
e95a6c091d | ||
![]() |
89b22f5d1d | ||
![]() |
5f3f2f99fc | ||
![]() |
6ec082d4f7 | ||
![]() |
444dcd17a9 | ||
![]() |
170954e0d7 | ||
![]() |
01b00ebe51 | ||
![]() |
3dd7ecf34b | ||
![]() |
ddfd01579e | ||
![]() |
1dfc3feec1 | ||
![]() |
ef7a0912d9 | ||
![]() |
4aedeab3b0 | ||
![]() |
1110c53694 | ||
![]() |
9cfa279d8d | ||
![]() |
5625bd86d5 | ||
![]() |
43529b16f2 | ||
![]() |
c35dc08b42 | ||
![]() |
984a311125 | ||
![]() |
335e9835f3 | ||
![]() |
a65ef0c1c5 | ||
![]() |
224c39bcd2 | ||
![]() |
3a2e991738 | ||
![]() |
1946bda711 | ||
![]() |
f1922269c0 | ||
![]() |
aa9f7893de | ||
![]() |
d5dda69eaa | ||
![]() |
770f88c853 | ||
![]() |
8987c247bb | ||
![]() |
7ee00ea004 | ||
![]() |
903d9edb12 | ||
![]() |
09f153c313 | ||
![]() |
c5e08f5545 | ||
![]() |
383bde043c | ||
![]() |
b555c093da | ||
![]() |
e3a8e9ee6f | ||
![]() |
24c34d8655 | ||
![]() |
578159ac6f | ||
![]() |
458cbc6c7c | ||
![]() |
0cac81fb0c | ||
![]() |
f98902679a | ||
![]() |
f797d8124c | ||
![]() |
e4b840b0d7 | ||
![]() |
87b5f8820b | ||
![]() |
78b9f38c5a | ||
![]() |
29c7875502 | ||
![]() |
03accf7634 | ||
![]() |
e9eb5cdbdc | ||
![]() |
f4a44f8b74 | ||
![]() |
bb2e442902 | ||
![]() |
c1149f4730 | ||
![]() |
9f6c73d584 | ||
![]() |
9a8419ba8b | ||
![]() |
852d3a1c2b | ||
![]() |
8c8addff08 | ||
![]() |
2ef68bb07e | ||
![]() |
f0f35dace5 | ||
![]() |
f50a0fb83b | ||
![]() |
36e3d86da4 | ||
![]() |
89ca96e1d6 | ||
![]() |
113ea8b938 | ||
![]() |
d640b32096 | ||
![]() |
1c80e02217 | ||
![]() |
55176a3e08 | ||
![]() |
fe7c2ef80c | ||
![]() |
172e1e9661 | ||
![]() |
6ef8527c29 | ||
![]() |
20d0b5b64b | ||
![]() |
0d11f83a24 | ||
![]() |
f492db7976 | ||
![]() |
bbacde1cec | ||
![]() |
d8a9aeb20e |
50
.cirrus.yml
50
.cirrus.yml
@ -1,50 +0,0 @@
|
||||
---
|
||||
|
||||
env:
|
||||
DEST_BRANCH: "main"
|
||||
CIRRUS_SHELL: "/bin/bash"
|
||||
|
||||
timeout_in: 30m
|
||||
|
||||
# Run on PRs and main branch post submit only. Don't run tests when tagging.
|
||||
only_if: $CIRRUS_TAG == '' && ($CIRRUS_PR != '' || $CIRRUS_BRANCH == 'main')
|
||||
|
||||
clone_script: &full_clone |
|
||||
if [ -z "$CIRRUS_PR" ]; then
|
||||
git clone --recursive --branch=$CIRRUS_BRANCH https://x-access-token:${CIRRUS_REPO_CLONE_TOKEN}@github.com/${CIRRUS_REPO_FULL_NAME}.git $CIRRUS_WORKING_DIR
|
||||
git reset --hard $CIRRUS_CHANGE_IN_REPO
|
||||
else
|
||||
git clone --recursive https://x-access-token:${CIRRUS_REPO_CLONE_TOKEN}@github.com/${CIRRUS_REPO_FULL_NAME}.git $CIRRUS_WORKING_DIR
|
||||
git fetch origin pull/$CIRRUS_PR/head:pull/$CIRRUS_PR
|
||||
git reset --hard $CIRRUS_CHANGE_IN_REPO
|
||||
fi
|
||||
|
||||
precommit_test_task:
|
||||
name: "Precommit"
|
||||
alias: precommit
|
||||
clone_script: *full_clone
|
||||
container:
|
||||
image: python:3.10
|
||||
script: |
|
||||
python3 -m pip install pre-commit
|
||||
pre-commit run -a
|
||||
|
||||
gofmt_task:
|
||||
name: "Gofmt"
|
||||
alias: gofmt
|
||||
clone_script: *full_clone
|
||||
container:
|
||||
image: golang:1.18
|
||||
script: |
|
||||
SRC=$(find . -type f -name '*.go' -not -path "./vendor/*")
|
||||
gofmt -w ${SRC}
|
||||
|
||||
golangci_lint_task:
|
||||
name: "Golangci-lint"
|
||||
alias: lint
|
||||
clone_script: *full_clone
|
||||
container:
|
||||
image: golang:1.18
|
||||
script: |
|
||||
make .install.golangci-lint
|
||||
make lint
|
16
.github/workflows/go.yml
vendored
16
.github/workflows/go.yml
vendored
@ -10,13 +10,11 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
# cannot use 1.21.X latest version since golangci-lint has an issue.
|
||||
go-version: '1.21.4'
|
||||
go-version: '>=1.22'
|
||||
cache: false
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
version: v1.51
|
||||
- run: |
|
||||
make .install.golangci-lint
|
||||
make lint
|
||||
|
||||
unit_test:
|
||||
name: Unit test
|
||||
@ -25,7 +23,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '>=1.19.0'
|
||||
go-version: '>=1.22'
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
@ -43,9 +41,9 @@ jobs:
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
with:
|
||||
file: .coverage/coverprofile
|
||||
name: codecov-umbrella
|
||||
fail_ci_if_error: true
|
||||
slug: navidys/tvxwidgets
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
7
.github/workflows/pr.yml
vendored
7
.github/workflows/pr.yml
vendored
@ -40,8 +40,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
# cannot use 1.21.X latest version since golangci-lint has an issue.
|
||||
go-version: '1.21.4'
|
||||
go-version: '>=1.22'
|
||||
cache: false
|
||||
- run: |
|
||||
make .install.golangci-lint
|
||||
@ -55,7 +54,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '>=1.19.0'
|
||||
go-version: '>=1.22'
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
@ -78,7 +77,7 @@ jobs:
|
||||
with:
|
||||
file: .coverage/coverprofile
|
||||
name: codecov-umbrella
|
||||
fail_ci_if_error: true
|
||||
fail_ci_if_error: false
|
||||
|
||||
goreportcard:
|
||||
name: update reportcard
|
||||
|
@ -1,30 +1,28 @@
|
||||
run:
|
||||
timeout: 10m
|
||||
deadline: 5m
|
||||
skip-dirs:
|
||||
- demos
|
||||
skip-files:
|
||||
- ".*_test.go"
|
||||
linters:
|
||||
enable-all: true
|
||||
disable:
|
||||
- varnamelen
|
||||
- exhaustruct
|
||||
- depguard
|
||||
# deprecated
|
||||
- gomnd
|
||||
- execinquery
|
||||
- exportloopref
|
||||
- rowserrcheck
|
||||
- wastedassign
|
||||
- structcheck
|
||||
- deadcode
|
||||
- varcheck
|
||||
- nosnakecase
|
||||
- ifshort
|
||||
- golint
|
||||
- maligned
|
||||
- interfacer
|
||||
- scopelint
|
||||
- exhaustivestruct
|
||||
linters-settings:
|
||||
errcheck:
|
||||
check-blank: false
|
||||
ignore: fmt:.*
|
||||
exclude-functions:
|
||||
- fmt:.*
|
||||
nolintlint:
|
||||
require-specific: true
|
||||
|
||||
issues:
|
||||
exclude-dirs:
|
||||
- demos
|
||||
exclude-files:
|
||||
- ".*_test.go"
|
||||
|
@ -5,7 +5,7 @@
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
identity and expression, level of experience, education, socioeconomic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
|
2
Makefile
2
Makefile
@ -32,7 +32,7 @@ install.tools: .install.pre-commit .install.codespell .install.golangci-lint .in
|
||||
|
||||
.PHONY: .install.golangci-lint
|
||||
.install.golangci-lint:
|
||||
VERSION=1.51.1 ./hack/install_golangci.sh
|
||||
VERSION=1.61.0 ./hack/install_golangci.sh
|
||||
|
||||
#=================================================
|
||||
# Testing (units, functionality, ...) targets
|
||||
|
39
barchart.go
39
barchart.go
@ -1,7 +1,7 @@
|
||||
package tvxwidgets
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
@ -67,8 +67,8 @@ func (c *BarChart) Draw(screen tcell.Screen) { //nolint:funlen,cyclop
|
||||
x, y, width, height := c.Box.GetInnerRect()
|
||||
|
||||
maxValY := y + 1
|
||||
xAxisStartY := y + height - 2 //nolint:gomnd
|
||||
barStartY := y + height - 3 //nolint:gomnd
|
||||
xAxisStartY := y + height - 2 //nolint:mnd
|
||||
barStartY := y + height - 3 //nolint:mnd
|
||||
borderPadding := 0
|
||||
|
||||
if c.hasBorder {
|
||||
@ -76,7 +76,7 @@ func (c *BarChart) Draw(screen tcell.Screen) { //nolint:funlen,cyclop
|
||||
}
|
||||
// set max value if not set
|
||||
c.initMaxValue()
|
||||
maxValueSr := fmt.Sprintf("%d", c.maxVal)
|
||||
maxValueSr := strconv.Itoa(c.maxVal)
|
||||
maxValLenght := len(maxValueSr) + 1
|
||||
|
||||
if maxValLenght < barChartYAxisLabelWidth {
|
||||
@ -108,7 +108,7 @@ func (c *BarChart) Draw(screen tcell.Screen) { //nolint:funlen,cyclop
|
||||
tview.PrintJoinedSemigraphics(screen, x+maxValLenght-1, xAxisStartY, '0', axesLabelStyle)
|
||||
|
||||
mxValRune := []rune(maxValueSr)
|
||||
for i := 0; i < len(mxValRune); i++ {
|
||||
for i := range mxValRune {
|
||||
tview.PrintJoinedSemigraphics(screen, x+borderPadding+i, maxValY, mxValRune[i], axesLabelStyle)
|
||||
}
|
||||
|
||||
@ -123,23 +123,23 @@ func (c *BarChart) Draw(screen tcell.Screen) { //nolint:funlen,cyclop
|
||||
}
|
||||
// set labels
|
||||
r := []rune(item.label)
|
||||
for j := 0; j < len(r); j++ {
|
||||
for j := range r {
|
||||
tview.PrintJoinedSemigraphics(screen, startX+j, labelY, r[j], axesLabelStyle)
|
||||
}
|
||||
// bar style
|
||||
bStyle := tcell.StyleDefault.Background(c.GetBackgroundColor()).Foreground(item.color)
|
||||
barHeight := c.getHeight(valueMaxHeight, item.value)
|
||||
|
||||
for k := 0; k < barHeight; k++ {
|
||||
for l := 0; l < c.barWidth; l++ {
|
||||
for k := range barHeight {
|
||||
for l := range c.barWidth {
|
||||
tview.PrintJoinedSemigraphics(screen, startX+l, barStartY-k, fullBlockRune, bStyle)
|
||||
}
|
||||
}
|
||||
// bar value
|
||||
vSt := fmt.Sprintf("%d", item.value)
|
||||
vSt := strconv.Itoa(item.value)
|
||||
vRune := []rune(vSt)
|
||||
|
||||
for i := 0; i < len(vRune); i++ {
|
||||
for i := range vRune {
|
||||
tview.PrintJoinedSemigraphics(screen, startX+i, barStartY-barHeight, vRune[i], bStyle)
|
||||
}
|
||||
|
||||
@ -170,8 +170,8 @@ func (c *BarChart) SetRect(x, y, width, height int) {
|
||||
}
|
||||
|
||||
// SetMaxValue sets maximum value of bars.
|
||||
func (c *BarChart) SetMaxValue(max int) {
|
||||
c.maxVal = max
|
||||
func (c *BarChart) SetMaxValue(maxValue int) {
|
||||
c.maxVal = maxValue
|
||||
}
|
||||
|
||||
// SetAxesColor sets axes x and y lines color.
|
||||
@ -193,9 +193,22 @@ func (c *BarChart) AddBar(label string, value int, color tcell.Color) {
|
||||
})
|
||||
}
|
||||
|
||||
// RemoveBar removes a bar item from the bar chart.
|
||||
func (c *BarChart) RemoveBar(label string) {
|
||||
bars := c.bars[:0]
|
||||
|
||||
for _, barItem := range c.bars {
|
||||
if barItem.label != label {
|
||||
bars = append(bars, barItem)
|
||||
}
|
||||
}
|
||||
|
||||
c.bars = bars
|
||||
}
|
||||
|
||||
// SetBarValue sets bar values.
|
||||
func (c *BarChart) SetBarValue(name string, value int) {
|
||||
for i := 0; i < len(c.bars); i++ {
|
||||
for i := range c.bars {
|
||||
if c.bars[i].label == name {
|
||||
c.bars[i].value = value
|
||||
}
|
||||
|
@ -204,20 +204,9 @@ func main() {
|
||||
}
|
||||
|
||||
moveSinData := func(data [][]float64) [][]float64 {
|
||||
n := 220
|
||||
newData := make([][]float64, 2)
|
||||
newData[0] = make([]float64, n)
|
||||
newData[1] = make([]float64, n)
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
if i+1 < len(data[0]) {
|
||||
newData[0][i] = data[0][i+1]
|
||||
}
|
||||
if i+1 < len(data[1]) {
|
||||
newData[1][i] = data[1][i+1]
|
||||
}
|
||||
}
|
||||
|
||||
newData[0] = rotate(data[0], -1)
|
||||
newData[1] = rotate(data[1], -1)
|
||||
return newData
|
||||
}
|
||||
|
||||
@ -330,3 +319,23 @@ func newBarChart() *tvxwidgets.BarChart {
|
||||
|
||||
return barGraph
|
||||
}
|
||||
|
||||
// Source: https://stackoverflow.com/questions/50833673/rotate-array-in-go/79079760#79079760
|
||||
// rotate rotates the given slice by k positions to the left or right.
|
||||
func rotate[T any](slice []T, k int) []T {
|
||||
if len(slice) == 0 {
|
||||
return slice
|
||||
}
|
||||
|
||||
var r int
|
||||
if k > 0 {
|
||||
r = len(slice) - k%len(slice)
|
||||
} else {
|
||||
kAbs := int(math.Abs(float64(k)))
|
||||
r = kAbs % len(slice)
|
||||
}
|
||||
|
||||
slice = append(slice[r:], slice[:r]...)
|
||||
|
||||
return slice
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ func main() {
|
||||
})
|
||||
bmLineChart.SetMarker(tvxwidgets.PlotMarkerBraille)
|
||||
bmLineChart.SetData(sinData)
|
||||
bmLineChart.SetDrawXAxisLabel(false)
|
||||
|
||||
dmLineChart := tvxwidgets.NewPlot()
|
||||
dmLineChart.SetBorder(true)
|
||||
@ -72,6 +73,7 @@ func main() {
|
||||
dmScatterPlot.SetPlotType(tvxwidgets.PlotTypeScatter)
|
||||
dmScatterPlot.SetMarker(tvxwidgets.PlotMarkerDot)
|
||||
dmScatterPlot.SetData(scatterPlotData)
|
||||
dmScatterPlot.SetDrawYAxisLabel(false)
|
||||
|
||||
bmScatterPlot := tvxwidgets.NewPlot()
|
||||
bmScatterPlot.SetBorder(true)
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 46 KiB |
1
demos/plot_custom_range/README.md
Normal file
1
demos/plot_custom_range/README.md
Normal file
@ -0,0 +1 @@
|
||||

|
119
demos/plot_custom_range/main.go
Normal file
119
demos/plot_custom_range/main.go
Normal file
@ -0,0 +1,119 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/navidys/tvxwidgets"
|
||||
"github.com/rivo/tview"
|
||||
"math"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
app := tview.NewApplication()
|
||||
|
||||
sinData := func() [][]float64 {
|
||||
n := 220
|
||||
data := make([][]float64, 2)
|
||||
data[0] = make([]float64, n)
|
||||
data[1] = make([]float64, n)
|
||||
for i := 0; i < n; i++ {
|
||||
data[0][i] = math.Sin(float64(i+1) / 5)
|
||||
// Avoid taking Cos(0) because it creates a high point of 2 that
|
||||
// will never be hit again and makes the graph look a little funny
|
||||
data[1][i] = math.Cos(float64(i+1) / 5)
|
||||
}
|
||||
return data
|
||||
}()
|
||||
|
||||
bmLineChart := tvxwidgets.NewPlot()
|
||||
bmLineChart.SetBorder(true)
|
||||
bmLineChart.SetTitle("line chart (braille mode)")
|
||||
bmLineChart.SetLineColor([]tcell.Color{
|
||||
tcell.ColorSteelBlue,
|
||||
tcell.ColorGreen,
|
||||
})
|
||||
bmLineChart.SetMarker(tvxwidgets.PlotMarkerBraille)
|
||||
bmLineChart.SetYAxisAutoScaleMin(false)
|
||||
bmLineChart.SetYAxisAutoScaleMax(false)
|
||||
bmLineChart.SetYRange(-1.5, 1.5)
|
||||
bmLineChart.SetData(sinData)
|
||||
|
||||
bmLineChart.SetDrawXAxisLabel(false)
|
||||
|
||||
dmLineChart := tvxwidgets.NewPlot()
|
||||
dmLineChart.SetBorder(true)
|
||||
dmLineChart.SetTitle("line chart (dot mode)")
|
||||
dmLineChart.SetLineColor([]tcell.Color{
|
||||
tcell.ColorDarkOrange,
|
||||
})
|
||||
dmLineChart.SetAxesLabelColor(tcell.ColorGold)
|
||||
dmLineChart.SetAxesColor(tcell.ColorGold)
|
||||
dmLineChart.SetMarker(tvxwidgets.PlotMarkerDot)
|
||||
dmLineChart.SetDotMarkerRune('\u25c9')
|
||||
|
||||
sampleData1 := []float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
|
||||
sampleData2 := []float64{10, 9, 8, 7, 6, 5, 4, 3, 2, 1}
|
||||
|
||||
dotModeChartData := [][]float64{sampleData1}
|
||||
dotModeChartData[0] = append(dotModeChartData[0], sampleData2...)
|
||||
dotModeChartData[0] = append(dotModeChartData[0], sampleData1[:5]...)
|
||||
dotModeChartData[0] = append(dotModeChartData[0], sampleData2[5:]...)
|
||||
dotModeChartData[0] = append(dotModeChartData[0], sampleData1[:7]...)
|
||||
dotModeChartData[0] = append(dotModeChartData[0], sampleData2[3:]...)
|
||||
dmLineChart.SetYAxisAutoScaleMin(false)
|
||||
dmLineChart.SetYAxisAutoScaleMax(false)
|
||||
dmLineChart.SetYRange(0, 3)
|
||||
dmLineChart.SetData(dotModeChartData)
|
||||
|
||||
scatterPlotData := make([][]float64, 2)
|
||||
scatterPlotData[0] = []float64{1, 2, 3, 4, 5}
|
||||
scatterPlotData[1] = sinData[1][4:]
|
||||
dmScatterPlot := tvxwidgets.NewPlot()
|
||||
|
||||
dmScatterPlot.SetBorder(true)
|
||||
dmScatterPlot.SetTitle("scatter plot (dot mode)")
|
||||
dmScatterPlot.SetLineColor([]tcell.Color{
|
||||
tcell.ColorMediumSlateBlue,
|
||||
tcell.ColorLightSkyBlue,
|
||||
})
|
||||
dmScatterPlot.SetPlotType(tvxwidgets.PlotTypeScatter)
|
||||
dmScatterPlot.SetMarker(tvxwidgets.PlotMarkerDot)
|
||||
dmScatterPlot.SetYAxisAutoScaleMin(false)
|
||||
dmScatterPlot.SetYAxisAutoScaleMax(false)
|
||||
dmScatterPlot.SetYRange(-1, 3)
|
||||
dmScatterPlot.SetData(scatterPlotData)
|
||||
dmScatterPlot.SetDrawYAxisLabel(false)
|
||||
|
||||
bmScatterPlot := tvxwidgets.NewPlot()
|
||||
bmScatterPlot.SetBorder(true)
|
||||
bmScatterPlot.SetTitle("scatter plot (braille mode)")
|
||||
bmScatterPlot.SetLineColor([]tcell.Color{
|
||||
tcell.ColorGold,
|
||||
tcell.ColorLightSkyBlue,
|
||||
})
|
||||
bmScatterPlot.SetPlotType(tvxwidgets.PlotTypeScatter)
|
||||
bmScatterPlot.SetMarker(tvxwidgets.PlotMarkerBraille)
|
||||
bmScatterPlot.SetYAxisAutoScaleMin(false)
|
||||
bmScatterPlot.SetYAxisAutoScaleMax(false)
|
||||
bmScatterPlot.SetYRange(-1, 5)
|
||||
bmScatterPlot.SetData(scatterPlotData)
|
||||
|
||||
firstRow := tview.NewFlex().SetDirection(tview.FlexColumn)
|
||||
firstRow.AddItem(dmLineChart, 0, 1, false)
|
||||
firstRow.AddItem(bmLineChart, 0, 1, false)
|
||||
firstRow.SetRect(0, 0, 100, 15)
|
||||
|
||||
secondRow := tview.NewFlex().SetDirection(tview.FlexColumn)
|
||||
secondRow.AddItem(dmScatterPlot, 0, 1, false)
|
||||
secondRow.AddItem(bmScatterPlot, 0, 1, false)
|
||||
secondRow.SetRect(0, 0, 100, 15)
|
||||
|
||||
layout := tview.NewFlex().SetDirection(tview.FlexRow)
|
||||
layout.AddItem(firstRow, 0, 1, false)
|
||||
layout.AddItem(secondRow, 0, 1, false)
|
||||
layout.SetRect(0, 0, 100, 30)
|
||||
|
||||
if err := app.SetRoot(layout, false).EnableMouse(true).Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
BIN
demos/plot_custom_range/screenshot.png
Normal file
BIN
demos/plot_custom_range/screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
1
demos/plot_xaxis_labels/README.md
Normal file
1
demos/plot_xaxis_labels/README.md
Normal file
@ -0,0 +1 @@
|
||||

|
133
demos/plot_xaxis_labels/main.go
Normal file
133
demos/plot_xaxis_labels/main.go
Normal file
@ -0,0 +1,133 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/navidys/tvxwidgets"
|
||||
"github.com/rivo/tview"
|
||||
"math"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
app := tview.NewApplication()
|
||||
|
||||
// >>> Data Function <<<
|
||||
// With these values, the curve will start with a value of 0 and reach a
|
||||
// high point of 2 at x = 3.14 (Pi) and then return to 0 at x = 6.28 (2*Pi).
|
||||
|
||||
// Play around with these values to get a feel for how they affect the curve
|
||||
// and how you might adapt this code to plot other functions.
|
||||
|
||||
period := 2 * math.Pi
|
||||
horizontalStretchFactor := 1.0
|
||||
verticalStretchFactor := 1.0
|
||||
xOffset := 0.0
|
||||
yOffset := 0.0
|
||||
|
||||
// >>> Graph View/Camera Controls <<<
|
||||
// These values influence which part of the curve is shown in
|
||||
// what "zoom level".
|
||||
|
||||
xAxisZoomFactor := 3.0
|
||||
yAxisZoomFactor := 1.0
|
||||
xAxisShift := 0.0
|
||||
yAxisShift := 0.0
|
||||
|
||||
// xFunc1 defines the x values that should be used for each vertical "slot" in the graph.
|
||||
xFunc1 := func(i int) float64 {
|
||||
return (float64(i) / xAxisZoomFactor) + xAxisShift
|
||||
}
|
||||
// yFunc1 defines the y values that result from a given input value x (this is the actual function).
|
||||
yFunc1 := func(x float64) float64 {
|
||||
return (math.Sin((x+xOffset)/horizontalStretchFactor) + yOffset) * verticalStretchFactor
|
||||
}
|
||||
|
||||
// xLabelFunc1 defines a label for each vertical "slot". Which labels are shown is determined automatically
|
||||
// based on the available space.
|
||||
xLabelFunc1 := func(i int) string {
|
||||
xVal := xFunc1(i)
|
||||
labelVal := xVal
|
||||
label := fmt.Sprintf("%.1f", labelVal)
|
||||
return label
|
||||
}
|
||||
|
||||
// computeDataArray computes the y values for n vertical slots based on the definitions above.
|
||||
computeDataArray := func() [][]float64 {
|
||||
n := 150
|
||||
data := make([][]float64, 1)
|
||||
data[0] = make([]float64, n)
|
||||
for i := 0; i < n; i++ {
|
||||
xVal := xFunc1(i)
|
||||
yVal := yFunc1(xVal)
|
||||
data[0][i] = yVal
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
data := computeDataArray()
|
||||
|
||||
bmLineChart := tvxwidgets.NewPlot()
|
||||
bmLineChart.SetBorder(true)
|
||||
bmLineChart.SetTitle("line chart (braille mode)")
|
||||
bmLineChart.SetLineColor([]tcell.Color{
|
||||
tcell.ColorSteelBlue,
|
||||
tcell.ColorGreen,
|
||||
})
|
||||
bmLineChart.SetMarker(tvxwidgets.PlotMarkerBraille)
|
||||
bmLineChart.SetXAxisLabelFunc(xLabelFunc1)
|
||||
bmLineChart.SetYAxisAutoScaleMin(false)
|
||||
bmLineChart.SetYAxisAutoScaleMax(false)
|
||||
bmLineChart.SetYRange(
|
||||
(-1+yOffset+yAxisShift)/yAxisZoomFactor,
|
||||
(1+yOffset+yAxisShift)/yAxisZoomFactor,
|
||||
)
|
||||
bmLineChart.SetData(data)
|
||||
|
||||
firstRow := tview.NewFlex().SetDirection(tview.FlexColumn)
|
||||
firstRow.AddItem(bmLineChart, 0, 1, false)
|
||||
firstRow.SetRect(0, 0, 100, 15)
|
||||
|
||||
layout := tview.NewFlex().SetDirection(tview.FlexRow)
|
||||
layout.AddItem(firstRow, 0, 1, false)
|
||||
layout.SetRect(0, 0, 100, 30)
|
||||
|
||||
animate := true
|
||||
|
||||
rotateDataContinuously := func() {
|
||||
tick := time.NewTicker(100 * time.Millisecond)
|
||||
go func() {
|
||||
initialxAxisShift := xAxisShift
|
||||
for {
|
||||
select {
|
||||
case <-tick.C:
|
||||
if !animate {
|
||||
continue
|
||||
}
|
||||
|
||||
xAxisShift = xAxisShift + 0.1
|
||||
if xAxisShift >= initialxAxisShift+period*4 {
|
||||
xAxisShift = initialxAxisShift
|
||||
}
|
||||
data = computeDataArray()
|
||||
bmLineChart.SetData(data)
|
||||
|
||||
app.Draw()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
go rotateDataContinuously()
|
||||
|
||||
if err := app.SetRoot(layout, false).EnableMouse(true).SetMouseCapture(func(event *tcell.EventMouse, action tview.MouseAction) (*tcell.EventMouse, tview.MouseAction) {
|
||||
if action == tview.MouseLeftClick {
|
||||
animate = !animate
|
||||
}
|
||||
return event, action
|
||||
}).Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
BIN
demos/plot_xaxis_labels/screenshot.png
Normal file
BIN
demos/plot_xaxis_labels/screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
@ -76,6 +76,11 @@ func (d *MessageDialog) SetType(dtype int) {
|
||||
}
|
||||
}
|
||||
|
||||
// SetTitle sets dialog title.
|
||||
func (d *MessageDialog) SetTitle(title string) {
|
||||
d.layout.SetTitle(title)
|
||||
}
|
||||
|
||||
// SetBackgroundColor sets dialog background color.
|
||||
func (d *MessageDialog) SetBackgroundColor(color tcell.Color) {
|
||||
d.bgColor = color
|
||||
@ -133,6 +138,7 @@ func (d *MessageDialog) InputHandler() func(event *tcell.EventKey, setFocus func
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if formHandler := d.form.InputHandler(); formHandler != nil {
|
||||
formHandler(event, setFocus)
|
||||
|
||||
@ -148,6 +154,7 @@ func (d *MessageDialog) MouseHandler() func(action tview.MouseAction, event *tce
|
||||
consumed, capture = d.form.MouseHandler()(action, event, setFocus)
|
||||
if !consumed && action == tview.MouseLeftClick && d.InRect(event.Position()) {
|
||||
setFocus(d)
|
||||
|
||||
consumed = true
|
||||
}
|
||||
|
||||
|
12
gauge_am.go
12
gauge_am.go
@ -34,13 +34,13 @@ func (g *ActivityModeGauge) Draw(screen tcell.Screen) {
|
||||
x, y, width, height := g.Box.GetInnerRect()
|
||||
tickStr := g.tickStr(width)
|
||||
|
||||
for i := 0; i < height; i++ {
|
||||
for i := range height {
|
||||
tview.Print(screen, tickStr, x, y+i, width, tview.AlignLeft, g.pgBgColor)
|
||||
}
|
||||
}
|
||||
|
||||
// Focus is called when this primitive receives focus.
|
||||
func (g *ActivityModeGauge) Focus(delegate func(p tview.Primitive)) {
|
||||
func (g *ActivityModeGauge) Focus(delegate func(p tview.Primitive)) { //nolint:revive
|
||||
}
|
||||
|
||||
// HasFocus returns whether or not this primitive has focus.
|
||||
@ -73,27 +73,27 @@ func (g *ActivityModeGauge) Reset() {
|
||||
g.counter = 0
|
||||
}
|
||||
|
||||
func (g *ActivityModeGauge) tickStr(max int) string {
|
||||
func (g *ActivityModeGauge) tickStr(maxCount int) string {
|
||||
var (
|
||||
prgHeadStr string
|
||||
prgEndStr string
|
||||
prgStr string
|
||||
)
|
||||
|
||||
if g.counter >= max-4 {
|
||||
if g.counter >= maxCount-4 {
|
||||
g.counter = 0
|
||||
}
|
||||
|
||||
hWidth := 0
|
||||
|
||||
for i := 0; i < g.counter; i++ {
|
||||
for range g.counter {
|
||||
prgHeadStr += fmt.Sprintf("[%s::]%s", getColorName(tview.Styles.PrimitiveBackgroundColor), prgCell)
|
||||
hWidth++
|
||||
}
|
||||
|
||||
prgStr = prgCell + prgCell + prgCell + prgCell
|
||||
|
||||
for i := 0; i < max+hWidth+4; i++ {
|
||||
for range maxCount + hWidth + 4 {
|
||||
prgEndStr += fmt.Sprintf("[%s::]%s", getColorName(tview.Styles.PrimitiveBackgroundColor), prgCell)
|
||||
}
|
||||
|
||||
|
15
gauge_pm.go
15
gauge_pm.go
@ -47,8 +47,8 @@ func (g *PercentageModeGauge) Draw(screen tcell.Screen) {
|
||||
prgBlock := g.progressBlock(width)
|
||||
style := tcell.StyleDefault.Background(g.pgBgColor).Foreground(tview.Styles.PrimaryTextColor)
|
||||
|
||||
for i := 0; i < height; i++ {
|
||||
for j := 0; j < prgBlock; j++ {
|
||||
for i := range height {
|
||||
for j := range prgBlock {
|
||||
screen.SetContent(x+j, y+i, ' ', nil, style)
|
||||
}
|
||||
}
|
||||
@ -56,21 +56,22 @@ func (g *PercentageModeGauge) Draw(screen tcell.Screen) {
|
||||
// print percentage in middle of box
|
||||
|
||||
pcRune := []rune(pcString)
|
||||
for j := 0; j < len(pcRune); j++ {
|
||||
for j := range pcRune {
|
||||
style = tcell.StyleDefault.Background(tview.Styles.PrimitiveBackgroundColor).Foreground(tview.Styles.PrimaryTextColor)
|
||||
if x+prgBlock >= tX+j {
|
||||
style = tcell.StyleDefault.Background(g.pgBgColor).Foreground(tview.Styles.PrimaryTextColor)
|
||||
}
|
||||
|
||||
for i := 0; i < height; i++ {
|
||||
for i := range height {
|
||||
screen.SetContent(tX+j, y+i, ' ', nil, style)
|
||||
}
|
||||
|
||||
screen.SetContent(tX+j, tY, pcRune[j], nil, style)
|
||||
}
|
||||
}
|
||||
|
||||
// Focus is called when this primitive receives focus.
|
||||
func (g *PercentageModeGauge) Focus(delegate func(p tview.Primitive)) {
|
||||
func (g *PercentageModeGauge) Focus(delegate func(p tview.Primitive)) { //nolint:revive
|
||||
}
|
||||
|
||||
// HasFocus returns whether or not this primitive has focus.
|
||||
@ -122,13 +123,13 @@ func (g *PercentageModeGauge) Reset() {
|
||||
g.value = 0
|
||||
}
|
||||
|
||||
func (g *PercentageModeGauge) progressBlock(max int) int {
|
||||
func (g *PercentageModeGauge) progressBlock(maxValue int) int {
|
||||
if g.maxValue == 0 {
|
||||
return g.maxValue
|
||||
}
|
||||
|
||||
pc := g.value * gaugeMaxPc / g.maxValue
|
||||
value := pc * max / gaugeMaxPc
|
||||
value := pc * maxValue / gaugeMaxPc
|
||||
|
||||
return value
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ func (g *UtilModeGauge) SetLabelColor(color tcell.Color) {
|
||||
}
|
||||
|
||||
// Focus is called when this primitive receives focus.
|
||||
func (g *UtilModeGauge) Focus(delegate func(p tview.Primitive)) {
|
||||
func (g *UtilModeGauge) Focus(delegate func(p tview.Primitive)) { //nolint:revive
|
||||
}
|
||||
|
||||
// HasFocus returns whether or not this primitive has focus.
|
||||
@ -97,8 +97,8 @@ func (g *UtilModeGauge) Draw(screen tcell.Screen) {
|
||||
labelWidth := len(g.label)
|
||||
barWidth := width - labelPCWidth - labelWidth
|
||||
|
||||
for i := 0; i < barWidth; i++ {
|
||||
for j := 0; j < height; j++ {
|
||||
for i := range barWidth {
|
||||
for j := range height {
|
||||
value := float64(i * 100 / barWidth)
|
||||
color := g.getBarColor(value)
|
||||
|
||||
|
32
go.mod
32
go.mod
@ -1,27 +1,27 @@
|
||||
module github.com/navidys/tvxwidgets
|
||||
|
||||
go 1.18
|
||||
go 1.22.6
|
||||
|
||||
require (
|
||||
github.com/gdamore/tcell/v2 v2.7.0
|
||||
github.com/onsi/ginkgo/v2 v2.13.2
|
||||
github.com/onsi/gomega v1.30.0
|
||||
github.com/rivo/tview v0.0.0-20231206124440-5f078138442e
|
||||
github.com/gdamore/tcell/v2 v2.8.1
|
||||
github.com/onsi/ginkgo/v2 v2.22.2
|
||||
github.com/onsi/gomega v1.36.2
|
||||
github.com/rivo/tview v0.0.0-20240616192244-23476fa0bab2
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/gdamore/encoding v1.0.0 // indirect
|
||||
github.com/go-logr/logr v1.3.0 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/gdamore/encoding v1.0.1 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
||||
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/rivo/uniseg v0.4.3 // indirect
|
||||
golang.org/x/net v0.17.0 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
golang.org/x/term v0.15.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/tools v0.14.0 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
golang.org/x/net v0.33.0 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
golang.org/x/term v0.28.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
golang.org/x/tools v0.28.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
103
go.sum
103
go.sum
@ -1,86 +1,107 @@
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||
github.com/gdamore/tcell/v2 v2.7.0 h1:I5LiGTQuwrysAt1KS9wg1yFfOI3arI3ucFrxtd/xqaA=
|
||||
github.com/gdamore/tcell/v2 v2.7.0/go.mod h1:hl/KtAANGBecfIPxk+FzKvThTqI84oplgbPEmVX60b8=
|
||||
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
|
||||
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw=
|
||||
github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=
|
||||
github.com/gdamore/tcell/v2 v2.8.1 h1:KPNxyqclpWpWQlPLx6Xui1pMk8S+7+R37h3g07997NU=
|
||||
github.com/gdamore/tcell/v2 v2.8.1/go.mod h1:bj8ori1BG3OYMjmb3IklZVWfZUJ1UBQt9JXrOCOhGWw=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg=
|
||||
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs=
|
||||
github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM=
|
||||
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
|
||||
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU=
|
||||
github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk=
|
||||
github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
|
||||
github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rivo/tview v0.0.0-20231206124440-5f078138442e h1:mPy47VW9tkqImnSPgcjnEHJuG3XHDBtXj2hDb1qBrRs=
|
||||
github.com/rivo/tview v0.0.0-20231206124440-5f078138442e/go.mod h1:c0SPlNPXkM+/Zgjn/0vD3W0Ds1yxstN7lpquqLDpWCg=
|
||||
github.com/rivo/tview v0.0.0-20240616192244-23476fa0bab2 h1:LXMiBMxtuXw8e2paN61dI2LMp8JZYyH4UXDwssRI3ys=
|
||||
github.com/rivo/tview v0.0.0-20240616192244-23476fa0bab2/go.mod h1:02iFIz7K/A9jGCvrizLPvoqr4cEIx7q54RH5Qudkrss=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw=
|
||||
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
|
||||
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
|
||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
|
||||
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
|
||||
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
|
||||
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
|
||||
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
355
plot.go
355
plot.go
@ -3,6 +3,9 @@ package tvxwidgets
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
@ -18,6 +21,14 @@ const (
|
||||
PlotMarkerDot
|
||||
)
|
||||
|
||||
// PlotYAxisLabelDataType represents plot y axis type (integer or float).
|
||||
type PlotYAxisLabelDataType uint
|
||||
|
||||
const (
|
||||
PlotYAxisLabelDataInt PlotYAxisLabelDataType = iota
|
||||
PlotYAxisLabelDataFloat
|
||||
)
|
||||
|
||||
// PlotType represents plot type (line chart or scatter).
|
||||
type PlotType uint
|
||||
|
||||
@ -31,6 +42,8 @@ const (
|
||||
plotXAxisLabelsHeight = 1
|
||||
plotXAxisLabelsGap = 2
|
||||
plotYAxisLabelsGap = 1
|
||||
|
||||
gapRune = " "
|
||||
)
|
||||
|
||||
type brailleCell struct {
|
||||
@ -41,29 +54,44 @@ type brailleCell struct {
|
||||
// Plot represents a plot primitive used for different charts.
|
||||
type Plot struct {
|
||||
*tview.Box
|
||||
data [][]float64
|
||||
maxVal float64
|
||||
marker Marker
|
||||
ptype PlotType
|
||||
dotMarkerRune rune
|
||||
lineColors []tcell.Color
|
||||
axesColor tcell.Color
|
||||
axesLabelColor tcell.Color
|
||||
drawAxes bool
|
||||
brailleCellMap map[image.Point]brailleCell
|
||||
mu sync.Mutex
|
||||
data [][]float64
|
||||
// maxVal is the maximum y-axis (vertical) value found in any of the lines in the data set.
|
||||
maxVal float64
|
||||
// minVal is the minimum y-axis (vertical) value found in any of the lines in the data set.
|
||||
minVal float64
|
||||
marker Marker
|
||||
ptype PlotType
|
||||
dotMarkerRune rune
|
||||
lineColors []tcell.Color
|
||||
axesColor tcell.Color
|
||||
axesLabelColor tcell.Color
|
||||
drawAxes bool
|
||||
drawXAxisLabel bool
|
||||
xAxisLabelFunc func(int) string
|
||||
drawYAxisLabel bool
|
||||
yAxisLabelDataType PlotYAxisLabelDataType
|
||||
yAxisAutoScaleMin bool
|
||||
yAxisAutoScaleMax bool
|
||||
brailleCellMap map[image.Point]brailleCell
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// NewPlot returns a plot widget.
|
||||
func NewPlot() *Plot {
|
||||
return &Plot{
|
||||
Box: tview.NewBox(),
|
||||
marker: PlotMarkerDot,
|
||||
ptype: PlotTypeLineChart,
|
||||
dotMarkerRune: dotRune,
|
||||
axesColor: tcell.ColorDimGray,
|
||||
axesLabelColor: tcell.ColorDimGray,
|
||||
drawAxes: true,
|
||||
Box: tview.NewBox(),
|
||||
marker: PlotMarkerDot,
|
||||
ptype: PlotTypeLineChart,
|
||||
dotMarkerRune: dotRune,
|
||||
axesColor: tcell.ColorDimGray,
|
||||
axesLabelColor: tcell.ColorDimGray,
|
||||
drawAxes: true,
|
||||
drawXAxisLabel: true,
|
||||
xAxisLabelFunc: strconv.Itoa,
|
||||
drawYAxisLabel: true,
|
||||
yAxisLabelDataType: PlotYAxisLabelDataFloat,
|
||||
yAxisAutoScaleMin: false,
|
||||
yAxisAutoScaleMax: true,
|
||||
lineColors: []tcell.Color{
|
||||
tcell.ColorSteelBlue,
|
||||
},
|
||||
@ -94,6 +122,21 @@ func (plot *Plot) SetLineColor(color []tcell.Color) {
|
||||
plot.lineColors = color
|
||||
}
|
||||
|
||||
// SetYAxisLabelDataType sets Y axis label data type (integer or float).
|
||||
func (plot *Plot) SetYAxisLabelDataType(dataType PlotYAxisLabelDataType) {
|
||||
plot.yAxisLabelDataType = dataType
|
||||
}
|
||||
|
||||
// SetYAxisAutoScaleMin enables YAxis min value autoscale.
|
||||
func (plot *Plot) SetYAxisAutoScaleMin(autoScale bool) {
|
||||
plot.yAxisAutoScaleMin = autoScale
|
||||
}
|
||||
|
||||
// SetYAxisAutoScaleMax enables YAxix max value autoscale.
|
||||
func (plot *Plot) SetYAxisAutoScaleMax(autoScale bool) {
|
||||
plot.yAxisAutoScaleMax = autoScale
|
||||
}
|
||||
|
||||
// SetAxesColor sets axes x and y lines color.
|
||||
func (plot *Plot) SetAxesColor(color tcell.Color) {
|
||||
plot.axesColor = color
|
||||
@ -109,6 +152,21 @@ func (plot *Plot) SetDrawAxes(draw bool) {
|
||||
plot.drawAxes = draw
|
||||
}
|
||||
|
||||
// SetDrawXAxisLabel set true in order to draw x axis label to screen.
|
||||
func (plot *Plot) SetDrawXAxisLabel(draw bool) {
|
||||
plot.drawXAxisLabel = draw
|
||||
}
|
||||
|
||||
// SetXAxisLabelFunc sets x axis label function.
|
||||
func (plot *Plot) SetXAxisLabelFunc(f func(int) string) {
|
||||
plot.xAxisLabelFunc = f
|
||||
}
|
||||
|
||||
// SetDrawYAxisLabel set true in order to draw y axis label to screen.
|
||||
func (plot *Plot) SetDrawYAxisLabel(draw bool) {
|
||||
plot.drawYAxisLabel = draw
|
||||
}
|
||||
|
||||
// SetMarker sets marker type braille or dot mode.
|
||||
func (plot *Plot) SetMarker(marker Marker) {
|
||||
plot.marker = marker
|
||||
@ -126,7 +184,27 @@ func (plot *Plot) SetData(data [][]float64) {
|
||||
|
||||
plot.brailleCellMap = make(map[image.Point]brailleCell)
|
||||
plot.data = data
|
||||
plot.maxVal = getMaxFloat64From2dSlice(data)
|
||||
|
||||
if plot.yAxisAutoScaleMax {
|
||||
plot.maxVal = getMaxFloat64From2dSlice(data)
|
||||
}
|
||||
|
||||
if plot.yAxisAutoScaleMin {
|
||||
plot.minVal = getMinFloat64From2dSlice(data)
|
||||
}
|
||||
}
|
||||
|
||||
func (plot *Plot) SetMaxVal(maxVal float64) {
|
||||
plot.maxVal = maxVal
|
||||
}
|
||||
|
||||
func (plot *Plot) SetMinVal(minVal float64) {
|
||||
plot.minVal = minVal
|
||||
}
|
||||
|
||||
func (plot *Plot) SetYRange(minVal float64, maxVal float64) {
|
||||
plot.minVal = minVal
|
||||
plot.maxVal = maxVal
|
||||
}
|
||||
|
||||
// SetDotMarkerRune sets dot marker rune.
|
||||
@ -139,7 +217,8 @@ func (plot *Plot) getYAxisLabelsWidth() int {
|
||||
return len(fmt.Sprintf("%.2f", plot.maxVal))
|
||||
}
|
||||
|
||||
func (plot *Plot) getChartAreaRect() (int, int, int, int) {
|
||||
// GetPlotRect returns the rect for the inner part of the plot, ie not including axes.
|
||||
func (plot *Plot) GetPlotRect() (int, int, int, int) {
|
||||
x, y, width, height := plot.Box.GetInnerRect()
|
||||
plotYAxisLabelsWidth := plot.getYAxisLabelsWidth()
|
||||
|
||||
@ -192,43 +271,137 @@ func (plot *Plot) drawAxesToScreen(screen tcell.Screen) {
|
||||
y+height-plotXAxisLabelsHeight-1,
|
||||
tview.BoxDrawingsLightUpAndRight, axesStyle)
|
||||
|
||||
// draw x axis labels
|
||||
tview.Print(screen, "0",
|
||||
x+plotYAxisLabelsWidth,
|
||||
y+height-plotXAxisLabelsHeight,
|
||||
1,
|
||||
tview.AlignLeft, plot.axesLabelColor)
|
||||
|
||||
for labelX := x + plotYAxisLabelsWidth +
|
||||
(plotXAxisLabelsGap)*plotHorizontalScale + 1; labelX < x+width-1; {
|
||||
label := fmt.Sprintf(
|
||||
"%d",
|
||||
(labelX-(x+plotYAxisLabelsWidth)-1)/(plotHorizontalScale)+1,
|
||||
)
|
||||
|
||||
tview.Print(screen, label, labelX, y+height-plotXAxisLabelsHeight, width, tview.AlignLeft, plot.axesLabelColor)
|
||||
|
||||
labelX += (len(label) + plotXAxisLabelsGap) * plotHorizontalScale
|
||||
if plot.drawXAxisLabel {
|
||||
plot.drawXAxisLabelsToScreen(screen, plotYAxisLabelsWidth, x, y, width, height)
|
||||
}
|
||||
|
||||
// draw Y axis labels
|
||||
verticalScale := plot.maxVal / float64(height-plotXAxisLabelsHeight-1)
|
||||
if plot.drawYAxisLabel {
|
||||
plot.drawYAxisLabelsToScreen(screen, plotYAxisLabelsWidth, x, y, height)
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:funlen,cyclop
|
||||
func (plot *Plot) drawXAxisLabelsToScreen(
|
||||
screen tcell.Screen, plotYAxisLabelsWidth int, x int, y int, width int, height int,
|
||||
) {
|
||||
xAxisAreaStartX := x + plotYAxisLabelsWidth + 1
|
||||
xAxisAreaEndX := x + width
|
||||
xAxisAvailableWidth := xAxisAreaEndX - xAxisAreaStartX
|
||||
|
||||
labelMap := map[int]string{}
|
||||
labelStartMap := map[int]int{}
|
||||
|
||||
maxDataPoints := 0
|
||||
for _, d := range plot.data {
|
||||
maxDataPoints = max(maxDataPoints, len(d))
|
||||
}
|
||||
|
||||
// determine the width needed for the largest label
|
||||
maxXAxisLabelWidth := 0
|
||||
|
||||
for _, d := range plot.data {
|
||||
for i := range d {
|
||||
label := plot.xAxisLabelFunc(i)
|
||||
labelMap[i] = label
|
||||
maxXAxisLabelWidth = max(maxXAxisLabelWidth, len(label))
|
||||
}
|
||||
}
|
||||
|
||||
// determine the start position for each label, if they were
|
||||
// to be centered below the data point.
|
||||
// Note: not all of these labels will be printed, as they would
|
||||
// overlap with each other
|
||||
for i, label := range labelMap {
|
||||
expectedLabelWidth := len(label)
|
||||
if i == 0 {
|
||||
expectedLabelWidth += plotXAxisLabelsGap / 2 //nolint:mnd
|
||||
} else {
|
||||
expectedLabelWidth += plotXAxisLabelsGap
|
||||
}
|
||||
|
||||
currentLabelStart := i - int(math.Round(float64(expectedLabelWidth)/2)) //nolint:mnd
|
||||
labelStartMap[i] = currentLabelStart
|
||||
}
|
||||
|
||||
// print the labels, skipping those that would overlap,
|
||||
// stopping when there is no more space
|
||||
lastUsedLabelEnd := math.MinInt
|
||||
initialOffset := xAxisAreaStartX
|
||||
|
||||
for i := range maxDataPoints {
|
||||
labelStart := labelStartMap[i]
|
||||
if labelStart < lastUsedLabelEnd {
|
||||
// the label would overlap with the previous label
|
||||
continue
|
||||
}
|
||||
|
||||
rawLabel := labelMap[i]
|
||||
labelWithGap := rawLabel
|
||||
|
||||
if i == 0 {
|
||||
labelWithGap += strings.Repeat(gapRune, plotXAxisLabelsGap/2) //nolint:mnd
|
||||
} else {
|
||||
labelWithGap = strings.Repeat(gapRune, plotXAxisLabelsGap/2) + labelWithGap + strings.Repeat(gapRune, plotXAxisLabelsGap/2) //nolint:lll,mnd
|
||||
}
|
||||
|
||||
expectedLabelWidth := len(labelWithGap)
|
||||
remainingWidth := xAxisAvailableWidth - labelStart
|
||||
|
||||
if expectedLabelWidth > remainingWidth {
|
||||
// the label would be too long to fit in the remaining space
|
||||
if expectedLabelWidth-1 <= remainingWidth {
|
||||
// if we omit the last gap, it fits, so we draw that before stopping
|
||||
labelWithoutGap := labelWithGap[:len(labelWithGap)-1]
|
||||
plot.printXAxisLabel(screen, labelWithoutGap, initialOffset+labelStart, y+height-plotXAxisLabelsHeight)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
lastUsedLabelEnd = labelStart + expectedLabelWidth
|
||||
plot.printXAxisLabel(screen, labelWithGap, initialOffset+labelStart, y+height-plotXAxisLabelsHeight)
|
||||
}
|
||||
}
|
||||
|
||||
func (plot *Plot) printXAxisLabel(screen tcell.Screen, label string, x, y int) {
|
||||
tview.Print(screen, label, x, y, len(label), tview.AlignLeft, plot.axesLabelColor)
|
||||
}
|
||||
|
||||
func (plot *Plot) drawYAxisLabelsToScreen(screen tcell.Screen, plotYAxisLabelsWidth int, x int, y int, height int) {
|
||||
verticalOffset := plot.minVal
|
||||
verticalScale := (plot.maxVal - plot.minVal) / float64(height-plotXAxisLabelsHeight-1)
|
||||
previousLabel := ""
|
||||
|
||||
for i := 0; i*(plotYAxisLabelsGap+1) < height-1; i++ {
|
||||
label := fmt.Sprintf("%.2f", float64(i)*verticalScale*(plotYAxisLabelsGap+1))
|
||||
var label string
|
||||
if plot.yAxisLabelDataType == PlotYAxisLabelDataFloat {
|
||||
label = fmt.Sprintf("%.2f", float64(i)*verticalScale*(plotYAxisLabelsGap+1)+verticalOffset)
|
||||
} else {
|
||||
label = strconv.Itoa(int(float64(i)*verticalScale*(plotYAxisLabelsGap+1) + verticalOffset))
|
||||
}
|
||||
|
||||
// Prevent same label being shown twice.
|
||||
// Mainly relevant for integer labels with small data sets (in value)
|
||||
if label == previousLabel {
|
||||
continue
|
||||
}
|
||||
|
||||
previousLabel = label
|
||||
|
||||
tview.Print(screen,
|
||||
label,
|
||||
x,
|
||||
y+height-(i*(plotYAxisLabelsGap+1))-2, //nolint:gomnd
|
||||
y+height-(i*(plotYAxisLabelsGap+1))-2, //nolint:mnd
|
||||
plotYAxisLabelsWidth,
|
||||
tview.AlignLeft, plot.axesLabelColor)
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:gocognit,cyclop
|
||||
//nolint:cyclop,gocognit
|
||||
func (plot *Plot) drawDotMarkerToScreen(screen tcell.Screen) {
|
||||
x, y, width, height := plot.getChartAreaRect()
|
||||
x, y, width, height := plot.GetPlotRect()
|
||||
chartData := plot.getData()
|
||||
verticalOffset := -plot.minVal
|
||||
|
||||
switch plot.ptype {
|
||||
case PlotTypeLineChart:
|
||||
@ -237,7 +410,14 @@ func (plot *Plot) drawDotMarkerToScreen(screen tcell.Screen) {
|
||||
|
||||
for j := 0; j < len(line) && j*plotHorizontalScale < width; j++ {
|
||||
val := line[j]
|
||||
lheight := int((val / plot.maxVal) * float64(height-1))
|
||||
if math.IsNaN(val) {
|
||||
continue
|
||||
}
|
||||
|
||||
lheight := int(((val + verticalOffset) / plot.maxVal) * float64(height-1))
|
||||
if lheight > height {
|
||||
continue
|
||||
}
|
||||
|
||||
if (x+(j*plotHorizontalScale) < x+width) && (y+height-1-lheight < y+height) {
|
||||
tview.PrintJoinedSemigraphics(screen, x+(j*plotHorizontalScale), y+height-1-lheight, plot.dotMarkerRune, style)
|
||||
@ -250,7 +430,14 @@ func (plot *Plot) drawDotMarkerToScreen(screen tcell.Screen) {
|
||||
style := tcell.StyleDefault.Background(plot.GetBackgroundColor()).Foreground(plot.lineColors[i])
|
||||
|
||||
for j, val := range line {
|
||||
lheight := int((val / plot.maxVal) * float64(height-1))
|
||||
if math.IsNaN(val) {
|
||||
continue
|
||||
}
|
||||
|
||||
lheight := int(((val + verticalOffset) / plot.maxVal) * float64(height-1))
|
||||
if lheight > height {
|
||||
continue
|
||||
}
|
||||
|
||||
if (x+(j*plotHorizontalScale) < x+width) && (y+height-1-lheight < y+height) {
|
||||
tview.PrintJoinedSemigraphics(screen, x+(j*plotHorizontalScale), y+height-1-lheight, plot.dotMarkerRune, style)
|
||||
@ -261,7 +448,7 @@ func (plot *Plot) drawDotMarkerToScreen(screen tcell.Screen) {
|
||||
}
|
||||
|
||||
func (plot *Plot) drawBrailleMarkerToScreen(screen tcell.Screen) {
|
||||
x, y, width, height := plot.getChartAreaRect()
|
||||
x, y, width, height := plot.GetPlotRect()
|
||||
|
||||
plot.calcBrailleLines()
|
||||
|
||||
@ -274,8 +461,25 @@ func (plot *Plot) drawBrailleMarkerToScreen(screen tcell.Screen) {
|
||||
}
|
||||
}
|
||||
|
||||
func calcDataPointHeight(val, maxVal, minVal float64, height int) int {
|
||||
return int(((val - minVal) / (maxVal - minVal)) * float64(height-1))
|
||||
}
|
||||
|
||||
func calcDataPointHeightIfInBounds(val float64, maxVal float64, minVal float64, height int) (int, bool) {
|
||||
if math.IsNaN(val) {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
result := calcDataPointHeight(val, maxVal, minVal, height)
|
||||
if (val > maxVal) || (val < minVal) || (result > height) {
|
||||
return result, false
|
||||
}
|
||||
|
||||
return result, true
|
||||
}
|
||||
|
||||
func (plot *Plot) calcBrailleLines() {
|
||||
x, y, _, height := plot.getChartAreaRect()
|
||||
x, y, _, height := plot.GetPlotRect()
|
||||
chartData := plot.getData()
|
||||
|
||||
for i, line := range chartData {
|
||||
@ -283,30 +487,57 @@ func (plot *Plot) calcBrailleLines() {
|
||||
continue
|
||||
}
|
||||
|
||||
previousHeight := int((line[0] / plot.maxVal) * float64(height-1))
|
||||
previousHeight := 0
|
||||
lastValWasOk := false
|
||||
|
||||
for j, val := range line[1:] {
|
||||
lheight := int((val / plot.maxVal) * float64(height-1))
|
||||
for j, val := range line {
|
||||
lheight, currentValIsOk := calcDataPointHeightIfInBounds(val, plot.maxVal, plot.minVal, height)
|
||||
|
||||
plot.setBrailleLine(
|
||||
image.Pt(
|
||||
(x+(j*plotHorizontalScale))*2, //nolint:gomnd
|
||||
(y+height-previousHeight-1)*4, //nolint:gomnd
|
||||
),
|
||||
image.Pt(
|
||||
(x+((j+1)*plotHorizontalScale))*2, //nolint:gomnd
|
||||
(y+height-lheight-1)*4, //nolint:gomnd
|
||||
),
|
||||
plot.lineColors[i],
|
||||
)
|
||||
if !lastValWasOk && !currentValIsOk {
|
||||
// nothing valid to draw, skip to next data point
|
||||
continue
|
||||
}
|
||||
|
||||
if !lastValWasOk { //nolint:gocritic
|
||||
// current data point is single valid data point, draw it individually
|
||||
plot.setBraillePoint(
|
||||
calcBraillePoint(x, j+1, y, height, lheight),
|
||||
plot.lineColors[i],
|
||||
)
|
||||
} else if !currentValIsOk {
|
||||
// last data point was single valid data point, draw it individually
|
||||
plot.setBraillePoint(
|
||||
calcBraillePoint(x, j, y, height, previousHeight),
|
||||
plot.lineColors[i],
|
||||
)
|
||||
} else {
|
||||
// we have two valid data points, draw a line between them
|
||||
plot.setBrailleLine(
|
||||
calcBraillePoint(x, j, y, height, previousHeight),
|
||||
calcBraillePoint(x, j+1, y, height, lheight),
|
||||
plot.lineColors[i],
|
||||
)
|
||||
}
|
||||
|
||||
lastValWasOk = currentValIsOk
|
||||
previousHeight = lheight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func calcBraillePoint(x, j, y, maxY, height int) image.Point {
|
||||
return image.Pt(
|
||||
(x+(j*plotHorizontalScale))*2, //nolint:mnd
|
||||
(y+maxY-height-1)*4, //nolint:mnd
|
||||
)
|
||||
}
|
||||
|
||||
func (plot *Plot) setBraillePoint(p image.Point, color tcell.Color) {
|
||||
point := image.Pt(p.X/2, p.Y/4) //nolint:gomnd
|
||||
if p.X < 0 || p.Y < 0 {
|
||||
return
|
||||
}
|
||||
|
||||
point := image.Pt(p.X/2, p.Y/4) //nolint:mnd
|
||||
plot.brailleCellMap[point] = brailleCell{
|
||||
plot.brailleCellMap[point].cRune | brailleRune[p.Y%4][p.X%2],
|
||||
color,
|
||||
|
11
sparkline.go
11
sparkline.go
@ -1,6 +1,7 @@
|
||||
package tvxwidgets
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sync"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
@ -35,24 +36,30 @@ func (sl *Sparkline) Draw(screen tcell.Screen) {
|
||||
// print label
|
||||
if sl.dataTitle != "" {
|
||||
tview.Print(screen, sl.dataTitle, x, y, width, tview.AlignLeft, sl.dataTitlecolor)
|
||||
|
||||
barHeight--
|
||||
}
|
||||
|
||||
maxVal := getMaxFloat64FromSlice(sl.data)
|
||||
if maxVal == 0 {
|
||||
if maxVal < 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// print lines
|
||||
for i := 0; i < len(sl.data) && i+x < x+width; i++ {
|
||||
data := sl.data[i]
|
||||
|
||||
if math.IsNaN(data) {
|
||||
continue
|
||||
}
|
||||
|
||||
dHeight := int((data / maxVal) * float64(barHeight))
|
||||
|
||||
sparkChar := barsRune[len(barsRune)-1]
|
||||
|
||||
style := tcell.StyleDefault.Background(sl.GetBackgroundColor()).Foreground(sl.lineColor)
|
||||
|
||||
for j := 0; j < dHeight; j++ {
|
||||
for j := range dHeight {
|
||||
tview.PrintJoinedSemigraphics(screen, i+x, y-1+height-j, sparkChar, style)
|
||||
}
|
||||
|
||||
|
66
utils.go
66
utils.go
@ -1,6 +1,7 @@
|
||||
package tvxwidgets
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
@ -77,26 +78,62 @@ func getMaxFloat64From2dSlice(slices [][]float64) float64 {
|
||||
}
|
||||
|
||||
var (
|
||||
max float64
|
||||
maxValue float64
|
||||
maxIsInit bool
|
||||
)
|
||||
|
||||
for _, slice := range slices {
|
||||
for _, val := range slice {
|
||||
if math.IsNaN(val) {
|
||||
continue
|
||||
}
|
||||
|
||||
if !maxIsInit {
|
||||
maxIsInit = true
|
||||
max = val
|
||||
maxValue = val
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if val > max {
|
||||
max = val
|
||||
if val > maxValue {
|
||||
maxValue = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return max
|
||||
return maxValue
|
||||
}
|
||||
|
||||
func getMinFloat64From2dSlice(slices [][]float64) float64 {
|
||||
if len(slices) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
var (
|
||||
minValue float64
|
||||
minIsInit bool
|
||||
)
|
||||
|
||||
for _, slice := range slices {
|
||||
for _, val := range slice {
|
||||
if math.IsNaN(val) {
|
||||
continue
|
||||
}
|
||||
|
||||
if !minIsInit {
|
||||
minIsInit = true
|
||||
minValue = val
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if val < minValue {
|
||||
minValue = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return minValue
|
||||
}
|
||||
|
||||
// returns max values in float64 slices.
|
||||
@ -105,14 +142,19 @@ func getMaxFloat64FromSlice(slice []float64) float64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
max := slice[0]
|
||||
for i := 1; i < len(slice); i++ {
|
||||
if slice[i] > max {
|
||||
max = slice[i]
|
||||
maxValue := -1.0
|
||||
|
||||
for i := range slice {
|
||||
if math.IsNaN(slice[i]) {
|
||||
continue
|
||||
}
|
||||
|
||||
if slice[i] > maxValue {
|
||||
maxValue = slice[i]
|
||||
}
|
||||
}
|
||||
|
||||
return max
|
||||
return maxValue
|
||||
}
|
||||
|
||||
func absInt(x int) int {
|
||||
@ -125,11 +167,11 @@ func absInt(x int) int {
|
||||
|
||||
func drawLine(screen tcell.Screen, startX int, startY int, length int, mode drawLineMode, style tcell.Style) {
|
||||
if mode == horizontalLine {
|
||||
for i := 0; i < length; i++ {
|
||||
for i := range length {
|
||||
tview.PrintJoinedSemigraphics(screen, startX+i, startY, tview.BoxDrawingsLightTripleDashHorizontal, style)
|
||||
}
|
||||
} else if mode == verticalLine {
|
||||
for i := 0; i < length; i++ {
|
||||
for i := range length {
|
||||
tview.PrintJoinedSemigraphics(screen, startX, startY+i, tview.BoxDrawingsLightTripleDashVertical, style)
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user