diff --git a/charts.go b/charts.go index 6a41bd3..c73f7a2 100644 --- a/charts.go +++ b/charts.go @@ -12,7 +12,8 @@ const labelFontSize = 10 const smallLabelFontSize = 8 const defaultDotWidth = 2.0 const defaultStrokeWidth = 2.0 -const defaultYAxisLabelCount = 10 +const defaultYAxisLabelCountHigh = 10 +const defaultYAxisLabelCountLow = 3 var defaultChartWidth = 600 var defaultChartHeight = 400 @@ -43,6 +44,15 @@ func GetNullValue() float64 { return nullValue } +func defaultYAxisLabelCount(span float64, decimalData bool) int { + result := math.Min(math.Max(span+1, defaultYAxisLabelCountLow), defaultYAxisLabelCountHigh) + if decimalData { + // if there is a decimal, we double our labels to provide more detailed labels + result = math.Min(result*2, defaultYAxisLabelCountHigh) + } + return int(result) +} + type Renderer interface { Render() (Box, error) } @@ -169,6 +179,7 @@ func defaultRender(p *Painter, opt defaultRenderOption) (*defaultRenderResult, e maxPadRange = *yAxisOption.RangeValuePaddingScale } min, max := opt.SeriesList.GetMinMax(index) + decimalData := (max - min) != math.Floor(max-min) if yAxisOption.Min != nil && *yAxisOption.Min < min { min = *yAxisOption.Min minPadRange = 0.0 @@ -195,7 +206,7 @@ func defaultRender(p *Painter, opt defaultRenderOption) (*defaultRenderResult, e if yAxisOption.Unit > 0 { padLabelCount = int((max-min)/yAxisOption.Unit) + 1 } else { - padLabelCount = defaultYAxisLabelCount + padLabelCount = defaultYAxisLabelCount(max-min, decimalData) } } // we call padRange directly because we need to do this padding before we can calculate the final labelCount for the axisRange @@ -207,7 +218,7 @@ func defaultRender(p *Painter, opt defaultRenderOption) (*defaultRenderResult, e } labelCount = int((max-min)/yAxisOption.Unit) + 1 } else { - labelCount = defaultYAxisLabelCount + labelCount = defaultYAxisLabelCount(max-min, decimalData) } yAxisOption.LabelCount = labelCount } diff --git a/line_chart_test.go b/line_chart_test.go index 2bff822..5550bc6 100644 --- a/line_chart_test.go +++ b/line_chart_test.go @@ -355,6 +355,34 @@ func TestLineChart(t *testing.T) { }, result: "\\n1.5k1k5000", }, + { + name: "ZeroData", + defaultTheme: true, + makeOptions: func() LineChartOption { + opt := makeMinimalLineChartOption() + values := [][]float64{ + {0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0}, + } + opt.SeriesList = NewSeriesListDataFromValues(values) + return opt + }, + result: "\\n210", + }, + { + name: "TinyRange", + defaultTheme: true, + makeOptions: func() LineChartOption { + opt := makeMinimalLineChartOption() + values := [][]float64{ + {0.1, 0.2, 0.1, 0.2, 0.4, 0.2, 0.1}, + {0.2, 0.4, 0.8, 0.4, 0.2, 0.1, 0.2}, + } + opt.SeriesList = NewSeriesListDataFromValues(values) + return opt + }, + result: "\\n21.61.200.80.40", + }, } for _, tt := range tests { diff --git a/mark_line_test.go b/mark_line_test.go index 3ac4e5b..6f82b78 100644 --- a/mark_line_test.go +++ b/mark_line_test.go @@ -29,14 +29,14 @@ func TestMarkLine(t *testing.T) { FontColor: drawing.ColorBlack, StrokeColor: drawing.ColorBlack, Series: series, - Range: NewRange(p, p.Height(), 6, 0.0, 5.0, 1.0, 1.0), + Range: NewRange(p, p.Height(), 6, 0.0, 5.0, 0.0, 0.0), }) if _, err := markLine.Render(); err != nil { return nil, err } return p.Bytes() }, - result: "\\n321", + result: "\\n321", }, } for i, tt := range tests { diff --git a/range.go b/range.go index 3574d68..1d50a1b 100644 --- a/range.go +++ b/range.go @@ -8,6 +8,7 @@ const rangeMinPaddingPercentMin = 0.0 // increasing could result in forced negat const rangeMinPaddingPercentMax = 20.0 const rangeMaxPaddingPercentMin = 5.0 // set minimum spacing at the top of the graph const rangeMaxPaddingPercentMax = 20.0 +const zeroSpanAdjustment = 1 // Adjustment type axisRange struct { p *Painter @@ -77,8 +78,17 @@ rootLoop: minResult = minTrunk // remove possible float multiplication inaccuracies } - if maxPaddingScale <= 0.0 { + if max == minResult { + // no adjustment was made and there is no span, because of that the max calculation below can't function + // for that reason we apply a default constant span, still wanting to prefer a zero start if possible + if minResult == 0 { + return minResult, minResult + (2 * zeroSpanAdjustment) + } + return minResult - zeroSpanAdjustment, minResult + zeroSpanAdjustment + } else if maxPaddingScale <= 0.0 { return minResult, max + } else if math.Abs(max) < 10 { + return minResult, math.Ceil(max) + 1 } // update max to provide ideal padding and human friendly intervals