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:
parent
88a948bf56
commit
9f893eb482
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user