1
0
mirror of https://github.com/mum4k/termdash.git synced 2025-04-27 13:48:49 +08:00

Function to calculate required height.

And options to set label orientation.
This commit is contained in:
Jakub Sobon 2019-02-13 22:53:19 -05:00
parent 88a948bf56
commit 9f893eb482
No known key found for this signature in database
GPG Key ID: F2451A77FB05D3B7
3 changed files with 128 additions and 14 deletions

View File

@ -26,8 +26,8 @@ const (
// rounded up to.
nonZeroDecimals = 2
// yAxisWidth is width of the Y axis.
yAxisWidth = 1
// axisWidth is width of an axis.
axisWidth = 1
)
// YDetails contain information about the Y axis that will be drawn onto the
@ -75,15 +75,16 @@ func (y *Y) Update(minVal, maxVal float64) {
y.min, y.max = NewValue(minVal, nonZeroDecimals), NewValue(maxVal, nonZeroDecimals)
}
// RequiredWidth calculates the minimum width required in order to draw the Y axis.
// RequiredWidth calculates the minimum width required in order to draw the Y
// axis and its labels.
func (y *Y) RequiredWidth() int {
// This is an estimation only, it is possible that more labels in the
// middle will be generated and might be wider than this. Such cases are
// handled on the call to Details when the size of canvas is known.
return widestLabel([]*Label{
return longestLabel([]*Label{
{Value: y.min},
{Value: y.max},
}) + yAxisWidth
}) + axisWidth
}
// Details retrieves details about the Y axis required to draw it on a canvas
@ -103,7 +104,7 @@ func (y *Y) Details(cvsAr image.Rectangle, mode YScaleMode) (*YDetails, error) {
}
// See how the labels would look like on the entire maxWidth.
maxLabelWidth := maxWidth - yAxisWidth
maxLabelWidth := maxWidth - axisWidth
labels, err := yLabels(scale, maxLabelWidth)
if err != nil {
return nil, err
@ -112,7 +113,7 @@ func (y *Y) Details(cvsAr image.Rectangle, mode YScaleMode) (*YDetails, error) {
var width int
// Determine the largest label, which might be less than maxWidth.
// Such case would allow us to save more space for the line chart itself.
widest := widestLabel(labels)
widest := longestLabel(labels)
if widest < maxLabelWidth {
// Save the space and recalculate the labels, since they need to be realigned.
l, err := yLabels(scale, widest)
@ -120,7 +121,7 @@ func (y *Y) Details(cvsAr image.Rectangle, mode YScaleMode) (*YDetails, error) {
return nil, err
}
labels = l
width = widest + yAxisWidth // One for the axis itself.
width = widest + axisWidth // One for the axis itself.
} else {
width = maxWidth
}
@ -134,8 +135,8 @@ func (y *Y) Details(cvsAr image.Rectangle, mode YScaleMode) (*YDetails, error) {
}, nil
}
// widestLabel returns the width of the widest label.
func widestLabel(labels []*Label) int {
// longestLabel returns the width of the widest label.
func longestLabel(labels []*Label) int {
var widest int
for _, label := range labels {
if l := len(label.Value.Text()); l > widest {
@ -193,3 +194,49 @@ func NewXDetails(numPoints int, yStart image.Point, cvsAr image.Rectangle, custo
Labels: labels,
}, nil
}
// LabelOrientation represents the orientation of text labels.
type LabelOrientation int
// String implements fmt.Stringer()
func (lo LabelOrientation) String() string {
if n, ok := labelOrientationNames[lo]; ok {
return n
}
return "LabelOrientationUnknown"
}
// labelOrientationNames maps LabelOrientation values to human readable names.
var labelOrientationNames = map[LabelOrientation]string{
LabelOrientationHorizontal: "LabelOrientationHorizontal",
LabelOrientationVertical: "LabelOrientationVertical",
}
const (
// LabelOrientationHorizontal is the default label orientation where text
// flows horizontally.
LabelOrientationHorizontal LabelOrientation = iota
// LabelOrientationvertical is an orientation where text flows vertically.
LabelOrientationVertical
)
// RequiredHeight calculates the minimum height required in order to draw the X
// axis and its labels.
func RequiredHeight(numPoints int, customLabels map[int]string, lo LabelOrientation) int {
if lo == LabelOrientationHorizontal {
// One row for the X axis and one row for its labels flowing
// horizontally.
return axisWidth + 1
}
labels := []*Label{
{Value: NewValue(float64(numPoints), nonZeroDecimals)},
}
for _, cl := range customLabels {
labels = append(labels, &Label{
Value: NewTextValue(cl),
})
}
return longestLabel(labels) + axisWidth
}

View File

@ -261,3 +261,53 @@ func TestNewXDetails(t *testing.T) {
})
}
}
func TestRequiredHeight(t *testing.T) {
tests := []struct {
desc string
numPoints int
customLabels map[int]string
labelOrientation LabelOrientation
want int
}{
{
desc: "horizontal orientation",
want: 2,
},
{
desc: "vertical orientation, no custom labels, need single row for max label",
numPoints: 9,
labelOrientation: LabelOrientationVertical,
want: 2,
},
{
desc: "vertical orientation, no custom labels, need multiple row for max label",
numPoints: 100,
labelOrientation: LabelOrientationVertical,
want: 4,
},
{
desc: "vertical orientation, custom labels but all shorter than max label",
numPoints: 100,
customLabels: map[int]string{1: "a", 2: "b"},
labelOrientation: LabelOrientationVertical,
want: 4,
},
{
desc: "vertical orientation, custom labels and some longer than max label",
numPoints: 100,
customLabels: map[int]string{1: "a", 2: "bbbbb"},
labelOrientation: LabelOrientationVertical,
want: 6,
},
}
for _, tc := range tests {
t.Run(tc.desc, func(t *testing.T) {
got := RequiredHeight(tc.numPoints, tc.customLabels, tc.labelOrientation)
if got != tc.want {
t.Errorf("RequiredHeight => %d, want %d", got, tc.want)
}
})
}
}

View File

@ -29,10 +29,11 @@ type Option interface {
// options stores the provided options.
type options struct {
axesCellOpts []cell.Option
xLabelCellOpts []cell.Option
yLabelCellOpts []cell.Option
yAxisMode axes.YScaleMode
axesCellOpts []cell.Option
xLabelCellOpts []cell.Option
xLabelOrientation axes.LabelOrientation
yLabelCellOpts []cell.Option
yAxisMode axes.YScaleMode
}
// newOptions returns a new options instance.
@ -66,6 +67,22 @@ func XLabelCellOpts(co ...cell.Option) Option {
})
}
// XLabelsVertical makes the labels under the X axis flow vertically.
// Defaults to labels that flow horizontally.
func XLabelsVertical() Option {
return option(func(opts *options) {
opts.xLabelOrientation = axes.LabelOrientationVertical
})
}
// XLabelsHorizontal makes the labels under the X axis flow horizontally.
// This is the default option.
func XLabelsHorizontal() Option {
return option(func(opts *options) {
opts.xLabelOrientation = axes.LabelOrientationHorizontal
})
}
// YLabelCellOpts set the cell options for the labels on the Y axis.
func YLabelCellOpts(co ...cell.Option) Option {
return option(func(opts *options) {