Fix some off-by-one errors in Y axis calculations
Y axis label printing stopped before the top line Sometimes large data values would land on the border or beyond the box fix typo
This commit is contained in:
parent
e74935dded
commit
562ca47996
71
linechart.go
71
linechart.go
@ -7,9 +7,7 @@ package termui
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"os"
|
|
||||||
"sort"
|
"sort"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// only 16 possible combinations, why bother
|
// only 16 possible combinations, why bother
|
||||||
@ -38,39 +36,9 @@ var braillePatterns = map[[2]int]rune{
|
|||||||
var lSingleBraille = [4]rune{'\u2840', '⠄', '⠂', '⠁'}
|
var lSingleBraille = [4]rune{'\u2840', '⠄', '⠂', '⠁'}
|
||||||
var rSingleBraille = [4]rune{'\u2880', '⠠', '⠐', '⠈'}
|
var rSingleBraille = [4]rune{'\u2880', '⠠', '⠐', '⠈'}
|
||||||
|
|
||||||
// set this filename to have debug logging written here
|
// LineChart has two modes: braille(default) and dot.
|
||||||
var DebugFilename string
|
// A single braille character is a 2x4 grid of dots, so Using braille
|
||||||
var debugFile *os.File
|
// gives 2x X resolution and 4x Y resolution over dot mode.
|
||||||
|
|
||||||
func debugLog(str string) {
|
|
||||||
if DebugFilename == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
if debugFile == nil {
|
|
||||||
debugFile, err = os.OpenFile(DebugFilename, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stamp := time.Now().Format(time.StampMilli)
|
|
||||||
_, err = fmt.Fprintln(debugFile, stamp, str)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Debug(a ...interface{}) {
|
|
||||||
debugLog(fmt.Sprint(a))
|
|
||||||
}
|
|
||||||
|
|
||||||
func Debugf(format string, a ...interface{}) {
|
|
||||||
debugLog(fmt.Sprintf(format, a...))
|
|
||||||
}
|
|
||||||
|
|
||||||
// LineChart has two modes: braille(default) and dot. Using braille gives 2x capicity as dot mode,
|
|
||||||
// because one braille char can represent two data points.
|
|
||||||
/*
|
/*
|
||||||
lc := termui.NewLineChart()
|
lc := termui.NewLineChart()
|
||||||
lc.Border.Label = "braille-mode Line Chart"
|
lc.Border.Label = "braille-mode Line Chart"
|
||||||
@ -95,8 +63,8 @@ type LineChart struct {
|
|||||||
drawingY int
|
drawingY int
|
||||||
axisYHeight int
|
axisYHeight int
|
||||||
axisXWidth int
|
axisXWidth int
|
||||||
axisYLebelGap int
|
axisYLabelGap int
|
||||||
axisXLebelGap int
|
axisXLabelGap int
|
||||||
topValue float64
|
topValue float64
|
||||||
bottomValue float64
|
bottomValue float64
|
||||||
labelX [][]rune
|
labelX [][]rune
|
||||||
@ -118,8 +86,8 @@ func NewLineChart() *LineChart {
|
|||||||
lc.DotStyle = '•'
|
lc.DotStyle = '•'
|
||||||
lc.Data = make(map[string][]float64)
|
lc.Data = make(map[string][]float64)
|
||||||
lc.LineColor = make(map[string]Attribute)
|
lc.LineColor = make(map[string]Attribute)
|
||||||
lc.axisXLebelGap = 2
|
lc.axisXLabelGap = 2
|
||||||
lc.axisYLebelGap = 1
|
lc.axisYLabelGap = 1
|
||||||
lc.bottomValue = math.Inf(1)
|
lc.bottomValue = math.Inf(1)
|
||||||
lc.topValue = math.Inf(-1)
|
lc.topValue = math.Inf(-1)
|
||||||
lc.YPadding = 0.2
|
lc.YPadding = 0.2
|
||||||
@ -222,7 +190,6 @@ func (lc *LineChart) renderDot() Buffer {
|
|||||||
minCell := lc.innerArea.Min.X + lc.labelYSpace
|
minCell := lc.innerArea.Min.X + lc.labelYSpace
|
||||||
cellPos := lc.innerArea.Max.X - 1
|
cellPos := lc.innerArea.Max.X - 1
|
||||||
for dataPos := len(seriesData) - 1; dataPos >= 0 && cellPos > minCell; {
|
for dataPos := len(seriesData) - 1; dataPos >= 0 && cellPos > minCell; {
|
||||||
Debug(seriesName, " ", dataPos, cellPos, seriesData[dataPos])
|
|
||||||
c := Cell{
|
c := Cell{
|
||||||
Ch: lc.DotStyle,
|
Ch: lc.DotStyle,
|
||||||
Fg: thisLineColor,
|
Fg: thisLineColor,
|
||||||
@ -254,7 +221,7 @@ func (lc *LineChart) calcLabelX() {
|
|||||||
if l+w <= lc.axisXWidth {
|
if l+w <= lc.axisXWidth {
|
||||||
lc.labelX = append(lc.labelX, s)
|
lc.labelX = append(lc.labelX, s)
|
||||||
}
|
}
|
||||||
l += w + lc.axisXLebelGap
|
l += w + lc.axisXLabelGap
|
||||||
} else { // braille
|
} else { // braille
|
||||||
if 2*l >= len(lc.DataLabels) {
|
if 2*l >= len(lc.DataLabels) {
|
||||||
break
|
break
|
||||||
@ -265,7 +232,7 @@ func (lc *LineChart) calcLabelX() {
|
|||||||
if l+w <= lc.axisXWidth {
|
if l+w <= lc.axisXWidth {
|
||||||
lc.labelX = append(lc.labelX, s)
|
lc.labelX = append(lc.labelX, s)
|
||||||
}
|
}
|
||||||
l += w + lc.axisXLebelGap
|
l += w + lc.axisXLabelGap
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -285,9 +252,10 @@ func shortenFloatVal(x float64) string {
|
|||||||
|
|
||||||
func (lc *LineChart) calcLabelY() {
|
func (lc *LineChart) calcLabelY() {
|
||||||
span := lc.topValue - lc.bottomValue
|
span := lc.topValue - lc.bottomValue
|
||||||
lc.scale = span / float64(lc.axisYHeight)
|
// where does -2 come from? Without it, we might draw on the top border or past the block
|
||||||
|
lc.scale = span / float64(lc.axisYHeight-2)
|
||||||
|
|
||||||
n := (1 + lc.axisYHeight) / (lc.axisYLebelGap + 1)
|
n := (1 + lc.axisYHeight) / (lc.axisYLabelGap + 1)
|
||||||
lc.labelY = make([][]rune, n)
|
lc.labelY = make([][]rune, n)
|
||||||
maxLen := 0
|
maxLen := 0
|
||||||
for i := 0; i < n; i++ {
|
for i := 0; i < n; i++ {
|
||||||
@ -354,7 +322,7 @@ func (lc *LineChart) calcLayout() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lc.axisYHeight = lc.innerArea.Dy() - 2
|
lc.axisYHeight = lc.innerArea.Dy() - 1
|
||||||
lc.calcLabelY()
|
lc.calcLabelY()
|
||||||
|
|
||||||
lc.axisXWidth = lc.innerArea.Dx() - 1 - lc.labelYSpace
|
lc.axisXWidth = lc.innerArea.Dx() - 1 - lc.labelYSpace
|
||||||
@ -362,8 +330,6 @@ func (lc *LineChart) calcLayout() {
|
|||||||
|
|
||||||
lc.drawingX = lc.innerArea.Min.X + 1 + lc.labelYSpace
|
lc.drawingX = lc.innerArea.Min.X + 1 + lc.labelYSpace
|
||||||
lc.drawingY = lc.innerArea.Min.Y
|
lc.drawingY = lc.innerArea.Min.Y
|
||||||
|
|
||||||
Debugf("calcLayout bottom=%f top=%f min=%f max=%f axisYHeight=%d", lc.bottomValue, lc.topValue, lc.minY, lc.maxY, lc.axisYHeight)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lc *LineChart) plotAxes() Buffer {
|
func (lc *LineChart) plotAxes() Buffer {
|
||||||
@ -378,8 +344,8 @@ func (lc *LineChart) plotAxes() Buffer {
|
|||||||
buf.Set(x, origY, Cell{Ch: HDASH, Fg: lc.AxesColor, Bg: lc.Bg})
|
buf.Set(x, origY, Cell{Ch: HDASH, Fg: lc.AxesColor, Bg: lc.Bg})
|
||||||
}
|
}
|
||||||
|
|
||||||
for dy := 1; dy <= lc.axisYHeight; dy++ {
|
for y := origY - 1; y > origY-lc.axisYHeight; y-- {
|
||||||
buf.Set(origX, origY-dy, Cell{Ch: VDASH, Fg: lc.AxesColor, Bg: lc.Bg})
|
buf.Set(origX, y, Cell{Ch: VDASH, Fg: lc.AxesColor, Bg: lc.Bg})
|
||||||
}
|
}
|
||||||
|
|
||||||
// x label
|
// x label
|
||||||
@ -398,7 +364,7 @@ func (lc *LineChart) plotAxes() Buffer {
|
|||||||
y := lc.innerArea.Min.Y + lc.innerArea.Dy() - 1
|
y := lc.innerArea.Min.Y + lc.innerArea.Dy() - 1
|
||||||
buf.Set(x, y, c)
|
buf.Set(x, y, c)
|
||||||
}
|
}
|
||||||
oft += len(rs) + lc.axisXLebelGap
|
oft += len(rs) + lc.axisXLabelGap
|
||||||
}
|
}
|
||||||
|
|
||||||
// y labels
|
// y labels
|
||||||
@ -406,7 +372,7 @@ func (lc *LineChart) plotAxes() Buffer {
|
|||||||
for j, r := range rs {
|
for j, r := range rs {
|
||||||
buf.Set(
|
buf.Set(
|
||||||
lc.innerArea.Min.X+j,
|
lc.innerArea.Min.X+j,
|
||||||
origY-i*(lc.axisYLebelGap+1),
|
origY-i*(lc.axisYLabelGap+1),
|
||||||
Cell{Ch: r, Fg: lc.AxesColor, Bg: lc.Bg})
|
Cell{Ch: r, Fg: lc.AxesColor, Bg: lc.Bg})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -425,17 +391,14 @@ func (lc *LineChart) Buffer() Buffer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if seriesCount == 0 {
|
if seriesCount == 0 {
|
||||||
Debug("lc render no data")
|
|
||||||
return buf
|
return buf
|
||||||
}
|
}
|
||||||
lc.calcLayout()
|
lc.calcLayout()
|
||||||
buf.Merge(lc.plotAxes())
|
buf.Merge(lc.plotAxes())
|
||||||
|
|
||||||
if lc.Mode == "dot" {
|
if lc.Mode == "dot" {
|
||||||
Debug("lc render start dot")
|
|
||||||
buf.Merge(lc.renderDot())
|
buf.Merge(lc.renderDot())
|
||||||
} else {
|
} else {
|
||||||
Debug("lc render start braille")
|
|
||||||
buf.Merge(lc.renderBraille())
|
buf.Merge(lc.renderBraille())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,9 +63,6 @@ func Init() error {
|
|||||||
// should be called after successful initialization when termui's functionality isn't required anymore.
|
// should be called after successful initialization when termui's functionality isn't required anymore.
|
||||||
func Close() {
|
func Close() {
|
||||||
tm.Close()
|
tm.Close()
|
||||||
if debugFile != nil {
|
|
||||||
debugFile.Close()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var renderLock sync.Mutex
|
var renderLock sync.Mutex
|
||||||
|
Loading…
Reference in New Issue
Block a user