mirror of
https://github.com/mum4k/termdash.git
synced 2025-05-04 22:18:07 +08:00
Finalizing the axes package.
It can determine details for both of the axis.
This commit is contained in:
parent
426cc6230b
commit
01957f0d15
@ -15,7 +15,10 @@
|
|||||||
// Package axes calculates the required layout and draws the X and Y axes of a line chart.
|
// Package axes calculates the required layout and draws the X and Y axes of a line chart.
|
||||||
package axes
|
package axes
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// nonZeroDecimals determines the overall precision of values displayed on the
|
// nonZeroDecimals determines the overall precision of values displayed on the
|
||||||
@ -77,7 +80,7 @@ func (y *Y) RequiredWidth() int {
|
|||||||
}) + yAxisWidth
|
}) + yAxisWidth
|
||||||
}
|
}
|
||||||
|
|
||||||
// Details retrieves details about the Y axis required to draw it on the provided canvas.
|
// Details retrieves details about the Y axis required to draw it on a canvas
|
||||||
// of the provided height. The maxWidth indicates the maximum width available
|
// of the provided height. The maxWidth indicates the maximum width available
|
||||||
// for the Y axis and its labels. This is guaranteed to be at least what
|
// for the Y axis and its labels. This is guaranteed to be at least what
|
||||||
// RequiredWidth returned.
|
// RequiredWidth returned.
|
||||||
@ -130,3 +133,33 @@ func widestLabel(labels []*Label) int {
|
|||||||
}
|
}
|
||||||
return widest
|
return widest
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// XDetails contain information about the X axis that will be drawn onto the
|
||||||
|
// canvas.
|
||||||
|
type XDetails struct {
|
||||||
|
// Scale is the scale of the X axis.
|
||||||
|
Scale *XScale
|
||||||
|
|
||||||
|
// Labels are the labels for values on the X axis in an increasing order.
|
||||||
|
Labels []*Label
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewXDetails retrieves details about the X axis required to draw it on a canvas
|
||||||
|
// of the provided height. The axisStart is the zero point of the X axis on the
|
||||||
|
// canvas and the axisWidth is its width in cells. The numPoints is the number
|
||||||
|
// of points in the largest series that will be plotted.
|
||||||
|
func NewXDetails(numPoints int, axisStart image.Point, axisWidth int) (*XDetails, error) {
|
||||||
|
scale, err := NewXScale(numPoints, axisWidth, nonZeroDecimals)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
labels, err := xLabels(scale, axisStart)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &XDetails{
|
||||||
|
Scale: scale,
|
||||||
|
Labels: labels,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
@ -56,6 +56,15 @@ func TestY(t *testing.T) {
|
|||||||
wantWidth: 2,
|
wantWidth: 2,
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "fails when max is less than min",
|
||||||
|
minVal: 0,
|
||||||
|
maxVal: -1,
|
||||||
|
cvsHeight: 2,
|
||||||
|
maxWidth: 3,
|
||||||
|
wantWidth: 3,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "maxWidth equals required width",
|
desc: "maxWidth equals required width",
|
||||||
minVal: 0,
|
minVal: 0,
|
||||||
@ -131,3 +140,75 @@ func TestY(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNewXDetails(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
numPoints int
|
||||||
|
axisStart image.Point
|
||||||
|
axisWidth int
|
||||||
|
want *XDetails
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "fails when numPoints is negative",
|
||||||
|
numPoints: -1,
|
||||||
|
axisStart: image.Point{0, 1},
|
||||||
|
axisWidth: 1,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "fails when axisWidth is too small",
|
||||||
|
numPoints: 1,
|
||||||
|
axisStart: image.Point{0, 1},
|
||||||
|
axisWidth: 0,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "works with no data points",
|
||||||
|
numPoints: 0,
|
||||||
|
axisStart: image.Point{0, 1},
|
||||||
|
axisWidth: 1,
|
||||||
|
want: &XDetails{
|
||||||
|
Scale: mustNewXScale(0, 1, nonZeroDecimals),
|
||||||
|
Labels: []*Label{
|
||||||
|
{
|
||||||
|
Value: NewValue(0, nonZeroDecimals),
|
||||||
|
Pos: image.Point{0, 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "axis doesn't start at point zero",
|
||||||
|
numPoints: 0,
|
||||||
|
axisStart: image.Point{11, 2},
|
||||||
|
axisWidth: 1,
|
||||||
|
want: &XDetails{
|
||||||
|
Scale: mustNewXScale(0, 1, nonZeroDecimals),
|
||||||
|
Labels: []*Label{
|
||||||
|
{
|
||||||
|
Value: NewValue(0, nonZeroDecimals),
|
||||||
|
Pos: image.Point{11, 2},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
|
got, err := NewXDetails(tc.numPoints, tc.axisStart, tc.axisWidth)
|
||||||
|
if (err != nil) != tc.wantErr {
|
||||||
|
t.Errorf("NewXDetails => unexpected error: %v, wantErr: %v", err, tc.wantErr)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if diff := pretty.Compare(tc.want, got); diff != "" {
|
||||||
|
t.Errorf("NewXDetails => unexpected diff (-want, +got):\n%s", diff)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -94,32 +94,30 @@ func rowLabel(scale *YScale, y int, labelWidth int) (*Label, error) {
|
|||||||
|
|
||||||
// xSpace represents an available space among the X axis.
|
// xSpace represents an available space among the X axis.
|
||||||
type xSpace struct {
|
type xSpace struct {
|
||||||
// min is the current coordinate.
|
// min is the current relative coordinate.
|
||||||
|
// These are zero based, i.e. not adjusted to axisStart.
|
||||||
cur int
|
cur int
|
||||||
// max is the maximum coordinate.
|
// max is the maximum relative coordinate.
|
||||||
|
// These are zero based, i.e. not adjusted to axisStart.
|
||||||
// The xSpace instance contains points 0 <= x < max
|
// The xSpace instance contains points 0 <= x < max
|
||||||
max int
|
max int
|
||||||
|
|
||||||
// the y coordinate of this space.
|
// axisStart is the actual position of the X axis zero point on the canvas.
|
||||||
y int
|
axisStart image.Point
|
||||||
}
|
}
|
||||||
|
|
||||||
// newXSpace returns a new xSpace instance initialized for the provided width.
|
// newXSpace returns a new xSpace instance initialized for the provided width.
|
||||||
func newXSpace(axisWidth, cvsHeight int) (*xSpace, error) {
|
func newXSpace(axisStart image.Point, axisWidth int) *xSpace {
|
||||||
y, err := positionToY(0, cvsHeight)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &xSpace{
|
return &xSpace{
|
||||||
cur: 0,
|
cur: 0,
|
||||||
max: axisWidth,
|
max: axisWidth,
|
||||||
y: y,
|
axisStart: axisStart,
|
||||||
}, nil
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implements fmt.Stringer.
|
// Implements fmt.Stringer.
|
||||||
func (xs *xSpace) String() string {
|
func (xs *xSpace) String() string {
|
||||||
return fmt.Sprintf("xSpace(size:%d)-cur:%v-max:%v", xs.Remaining(), image.Point{xs.cur, xs.y}, image.Point{xs.max, xs.y})
|
return fmt.Sprintf("xSpace(size:%d)-cur:%v-max:%v", xs.Remaining(), image.Point{xs.cur, xs.axisStart.Y}, image.Point{xs.max, xs.axisStart.Y})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remaining returns the remaining size on the X axis.
|
// Remaining returns the remaining size on the X axis.
|
||||||
@ -127,9 +125,17 @@ func (xs *xSpace) Remaining() int {
|
|||||||
return xs.max - xs.cur
|
return xs.max - xs.cur
|
||||||
}
|
}
|
||||||
|
|
||||||
// Current returns the current point.
|
// Relative returns the relative coordinate within the space, these are zero
|
||||||
func (xs *xSpace) Current() image.Point {
|
// based.
|
||||||
return image.Point{xs.cur, xs.y}
|
func (xs *xSpace) Relative() image.Point {
|
||||||
|
return image.Point{xs.cur, xs.axisStart.Y}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Absolute returns the absolute coordinate on the canvas where a label should
|
||||||
|
// be placed. The is the coordinate that represents the current relative
|
||||||
|
// coordinate of the space.
|
||||||
|
func (xs *xSpace) Absolute() image.Point {
|
||||||
|
return image.Point{xs.cur + xs.axisStart.X, xs.axisStart.Y}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sub subtracts the specified size from the beginning of the available
|
// Sub subtracts the specified size from the beginning of the available
|
||||||
@ -143,19 +149,13 @@ func (xs *xSpace) Sub(size int) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// xLabels returns labels that should be placed under the X axis.
|
// xLabels returns labels that should be placed under the X axis.
|
||||||
|
// The axisStart is the point where the X axis starts (its zero value) and is
|
||||||
|
// used to determine label placement.
|
||||||
// Labels are returned in an increasing value order.
|
// Labels are returned in an increasing value order.
|
||||||
// Returned labels shouldn't be trimmed, their count is adjusted so that they
|
// Returned labels shouldn't be trimmed, their count is adjusted so that they
|
||||||
// fit under the width of the axis.
|
// fit under the width of the axis.
|
||||||
func xLabels(scale *XScale, cvsHeight int) ([]*Label, error) {
|
func xLabels(scale *XScale, axisStart image.Point) ([]*Label, error) {
|
||||||
if min := 2; cvsHeight < min {
|
space := newXSpace(axisStart, scale.AxisWidth)
|
||||||
return nil, fmt.Errorf("cannot place labels on a canvas with height %d, minimum is %d", cvsHeight, min)
|
|
||||||
}
|
|
||||||
|
|
||||||
space, err := newXSpace(scale.AxisWidth, cvsHeight)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("newXSpace => %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
const minSpacing = 3
|
const minSpacing = 3
|
||||||
var res []*Label
|
var res []*Label
|
||||||
|
|
||||||
@ -179,7 +179,7 @@ func xLabels(scale *XScale, cvsHeight int) ([]*Label, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
skip := nextCell - space.Current().X
|
skip := nextCell - space.Relative().X
|
||||||
if skip < minSpacing {
|
if skip < minSpacing {
|
||||||
skip = minSpacing
|
skip = minSpacing
|
||||||
}
|
}
|
||||||
@ -198,7 +198,7 @@ func xLabels(scale *XScale, cvsHeight int) ([]*Label, error) {
|
|||||||
// The space is adjusted according to how much space was taken by the label.
|
// The space is adjusted according to how much space was taken by the label.
|
||||||
// Returns nil, nil if the label doesn't fit in the space.
|
// Returns nil, nil if the label doesn't fit in the space.
|
||||||
func colLabel(scale *XScale, space *xSpace) (*Label, error) {
|
func colLabel(scale *XScale, space *xSpace) (*Label, error) {
|
||||||
pos := space.Current()
|
pos := space.Relative()
|
||||||
v, err := scale.CellLabel(pos.X)
|
v, err := scale.CellLabel(pos.X)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to determine label value for column %d: %v", pos.X, err)
|
return nil, fmt.Errorf("unable to determine label value for column %d: %v", pos.X, err)
|
||||||
@ -209,12 +209,13 @@ func colLabel(scale *XScale, space *xSpace) (*Label, error) {
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abs := space.Absolute()
|
||||||
if err := space.Sub(labelLen); err != nil {
|
if err := space.Sub(labelLen); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Label{
|
return &Label{
|
||||||
Value: v,
|
Value: v,
|
||||||
Pos: pos,
|
Pos: abs,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -144,23 +144,15 @@ func TestXLabels(t *testing.T) {
|
|||||||
desc string
|
desc string
|
||||||
numPoints int
|
numPoints int
|
||||||
axisWidth int
|
axisWidth int
|
||||||
cvsHeight int
|
axisStart image.Point
|
||||||
want []*Label
|
want []*Label
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
|
|
||||||
{
|
|
||||||
desc: "fails when canvas height is too small",
|
|
||||||
numPoints: 1,
|
|
||||||
axisWidth: 1,
|
|
||||||
cvsHeight: 1,
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
desc: "only one point",
|
desc: "only one point",
|
||||||
numPoints: 1,
|
numPoints: 1,
|
||||||
axisWidth: 1,
|
axisWidth: 1,
|
||||||
cvsHeight: 2,
|
axisStart: image.Point{0, 1},
|
||||||
want: []*Label{
|
want: []*Label{
|
||||||
{NewValue(0, nonZeroDecimals), image.Point{0, 1}},
|
{NewValue(0, nonZeroDecimals), image.Point{0, 1}},
|
||||||
},
|
},
|
||||||
@ -169,7 +161,7 @@ func TestXLabels(t *testing.T) {
|
|||||||
desc: "two points, only one label fits",
|
desc: "two points, only one label fits",
|
||||||
numPoints: 2,
|
numPoints: 2,
|
||||||
axisWidth: 1,
|
axisWidth: 1,
|
||||||
cvsHeight: 2,
|
axisStart: image.Point{0, 1},
|
||||||
want: []*Label{
|
want: []*Label{
|
||||||
{NewValue(0, nonZeroDecimals), image.Point{0, 1}},
|
{NewValue(0, nonZeroDecimals), image.Point{0, 1}},
|
||||||
},
|
},
|
||||||
@ -178,17 +170,27 @@ func TestXLabels(t *testing.T) {
|
|||||||
desc: "two points, two labels fit exactly",
|
desc: "two points, two labels fit exactly",
|
||||||
numPoints: 2,
|
numPoints: 2,
|
||||||
axisWidth: 5,
|
axisWidth: 5,
|
||||||
cvsHeight: 2,
|
axisStart: image.Point{0, 1},
|
||||||
want: []*Label{
|
want: []*Label{
|
||||||
{NewValue(0, nonZeroDecimals), image.Point{0, 1}},
|
{NewValue(0, nonZeroDecimals), image.Point{0, 1}},
|
||||||
{NewValue(1, nonZeroDecimals), image.Point{4, 1}},
|
{NewValue(1, nonZeroDecimals), image.Point{4, 1}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "labels are placed according to axisStart",
|
||||||
|
numPoints: 2,
|
||||||
|
axisWidth: 5,
|
||||||
|
axisStart: image.Point{3, 5},
|
||||||
|
want: []*Label{
|
||||||
|
{NewValue(0, nonZeroDecimals), image.Point{3, 5}},
|
||||||
|
{NewValue(1, nonZeroDecimals), image.Point{7, 5}},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "skip to next value exhausts the space completely",
|
desc: "skip to next value exhausts the space completely",
|
||||||
numPoints: 11,
|
numPoints: 11,
|
||||||
axisWidth: 4,
|
axisWidth: 4,
|
||||||
cvsHeight: 2,
|
axisStart: image.Point{0, 1},
|
||||||
want: []*Label{
|
want: []*Label{
|
||||||
{NewValue(0, nonZeroDecimals), image.Point{0, 1}},
|
{NewValue(0, nonZeroDecimals), image.Point{0, 1}},
|
||||||
},
|
},
|
||||||
@ -197,7 +199,7 @@ func TestXLabels(t *testing.T) {
|
|||||||
desc: "second label doesn't fit due to its length",
|
desc: "second label doesn't fit due to its length",
|
||||||
numPoints: 100,
|
numPoints: 100,
|
||||||
axisWidth: 5,
|
axisWidth: 5,
|
||||||
cvsHeight: 2,
|
axisStart: image.Point{0, 1},
|
||||||
want: []*Label{
|
want: []*Label{
|
||||||
{NewValue(0, nonZeroDecimals), image.Point{0, 1}},
|
{NewValue(0, nonZeroDecimals), image.Point{0, 1}},
|
||||||
},
|
},
|
||||||
@ -206,7 +208,7 @@ func TestXLabels(t *testing.T) {
|
|||||||
desc: "two points, two labels, more space than minSpacing so end label adjusted",
|
desc: "two points, two labels, more space than minSpacing so end label adjusted",
|
||||||
numPoints: 2,
|
numPoints: 2,
|
||||||
axisWidth: 6,
|
axisWidth: 6,
|
||||||
cvsHeight: 2,
|
axisStart: image.Point{0, 1},
|
||||||
want: []*Label{
|
want: []*Label{
|
||||||
{NewValue(0, nonZeroDecimals), image.Point{0, 1}},
|
{NewValue(0, nonZeroDecimals), image.Point{0, 1}},
|
||||||
{NewValue(1, nonZeroDecimals), image.Point{5, 1}},
|
{NewValue(1, nonZeroDecimals), image.Point{5, 1}},
|
||||||
@ -216,7 +218,7 @@ func TestXLabels(t *testing.T) {
|
|||||||
desc: "at most as many labels as there are points",
|
desc: "at most as many labels as there are points",
|
||||||
numPoints: 2,
|
numPoints: 2,
|
||||||
axisWidth: 100,
|
axisWidth: 100,
|
||||||
cvsHeight: 2,
|
axisStart: image.Point{0, 1},
|
||||||
want: []*Label{
|
want: []*Label{
|
||||||
{NewValue(0, nonZeroDecimals), image.Point{0, 1}},
|
{NewValue(0, nonZeroDecimals), image.Point{0, 1}},
|
||||||
{NewValue(1, nonZeroDecimals), image.Point{98, 1}},
|
{NewValue(1, nonZeroDecimals), image.Point{98, 1}},
|
||||||
@ -226,7 +228,7 @@ func TestXLabels(t *testing.T) {
|
|||||||
desc: "some labels in the middle",
|
desc: "some labels in the middle",
|
||||||
numPoints: 4,
|
numPoints: 4,
|
||||||
axisWidth: 100,
|
axisWidth: 100,
|
||||||
cvsHeight: 2,
|
axisStart: image.Point{0, 1},
|
||||||
want: []*Label{
|
want: []*Label{
|
||||||
{NewValue(0, nonZeroDecimals), image.Point{0, 1}},
|
{NewValue(0, nonZeroDecimals), image.Point{0, 1}},
|
||||||
{NewValue(1, nonZeroDecimals), image.Point{31, 1}},
|
{NewValue(1, nonZeroDecimals), image.Point{31, 1}},
|
||||||
@ -238,7 +240,7 @@ func TestXLabels(t *testing.T) {
|
|||||||
desc: "more points than pixels",
|
desc: "more points than pixels",
|
||||||
numPoints: 100,
|
numPoints: 100,
|
||||||
axisWidth: 6,
|
axisWidth: 6,
|
||||||
cvsHeight: 2,
|
axisStart: image.Point{0, 1},
|
||||||
want: []*Label{
|
want: []*Label{
|
||||||
{NewValue(0, nonZeroDecimals), image.Point{0, 1}},
|
{NewValue(0, nonZeroDecimals), image.Point{0, 1}},
|
||||||
{NewValue(72, nonZeroDecimals), image.Point{4, 1}},
|
{NewValue(72, nonZeroDecimals), image.Point{4, 1}},
|
||||||
@ -253,7 +255,7 @@ func TestXLabels(t *testing.T) {
|
|||||||
t.Fatalf("NewXScale => unexpected error: %v", err)
|
t.Fatalf("NewXScale => unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
t.Logf("scale step: %v", scale.Step.Rounded)
|
t.Logf("scale step: %v", scale.Step.Rounded)
|
||||||
got, err := xLabels(scale, tc.cvsHeight)
|
got, err := xLabels(scale, tc.axisStart)
|
||||||
if (err != nil) != tc.wantErr {
|
if (err != nil) != tc.wantErr {
|
||||||
t.Errorf("xLabels => unexpected error: %v, wantErr: %v", err, tc.wantErr)
|
t.Errorf("xLabels => unexpected error: %v, wantErr: %v", err, tc.wantErr)
|
||||||
}
|
}
|
||||||
|
@ -100,9 +100,6 @@ func (ys *YScale) PixelToValue(y int) (float64, error) {
|
|||||||
// The value must be within the bounds provided to NewYScale. Y coordinates
|
// The value must be within the bounds provided to NewYScale. Y coordinates
|
||||||
// grow down.
|
// grow down.
|
||||||
func (ys *YScale) ValueToPixel(v float64) (int, error) {
|
func (ys *YScale) ValueToPixel(v float64) (int, error) {
|
||||||
if min, max := ys.Min.Value, ys.Max.Rounded; v < min || v > max {
|
|
||||||
return 0, fmt.Errorf("invalid value %v, must be in range %v <= v <= %v", v, min, max)
|
|
||||||
}
|
|
||||||
if ys.Step.Rounded == 0 {
|
if ys.Step.Rounded == 0 {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
@ -234,9 +231,6 @@ func (xs *XScale) ValueToCell(v int) (int, error) {
|
|||||||
// axisWidth provided to NewXScale. X coordinates grow right.
|
// axisWidth provided to NewXScale. X coordinates grow right.
|
||||||
// The returned value is rounded to the nearest int, rounding half away from zero.
|
// The returned value is rounded to the nearest int, rounding half away from zero.
|
||||||
func (xs *XScale) CellLabel(x int) (*Value, error) {
|
func (xs *XScale) CellLabel(x int) (*Value, error) {
|
||||||
if min, max := 0, xs.AxisWidth; x < min || x >= max {
|
|
||||||
return nil, fmt.Errorf("invalid cell coordinate %d, must be in range %v <= x < %v", x, min, max)
|
|
||||||
}
|
|
||||||
v, err := xs.PixelToValue(x * braille.ColMult)
|
v, err := xs.PixelToValue(x * braille.ColMult)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
Loading…
x
Reference in New Issue
Block a user