Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 314711b

Browse files
committedFeb 7, 2024··
Add LabelSkipCount to the YAxisOption, plus more tweaks
This adds the `LabelSkipCount` configuration as a way to reduce the amount of y axis labels, while maintaining the same `LabelCount` number of horizontal lines. As part of testing around this change it was found that the Unit parameter was not handled in the best way possible. A long comment in charts.go decribes the circular relationship between the axis range and label count (particularly if a unit is configured).
1 parent 280a37d commit 314711b

13 files changed

+356
-205
lines changed
 

‎axis.go

+22-23
Original file line numberDiff line numberDiff line change
@@ -61,18 +61,21 @@ type AxisOption struct {
6161
Unit float64
6262
// LabelCount is the number of labels to show on the axis. Specify a smaller number to reduce writing collisions. This value takes priority over Unit.
6363
LabelCount int
64+
// LabelSkipCount specifies a number of lines between labels where there will be no label,
65+
// but a horizontal line will still be drawn.
66+
LabelSkipCount int
6467
}
6568

6669
func (a *axisPainter) Render() (Box, error) {
6770
opt := a.opt
71+
if isFalse(opt.Show) {
72+
return BoxZero, nil
73+
}
6874
top := a.p
6975
theme := opt.Theme
7076
if theme == nil {
7177
theme = top.theme
7278
}
73-
if isFalse(opt.Show) {
74-
return BoxZero, nil
75-
}
7679

7780
strokeWidth := opt.StrokeWidth
7881
if strokeWidth == 0 {
@@ -99,14 +102,13 @@ func (a *axisPainter) Render() (Box, error) {
99102
strokeColor = theme.GetAxisStrokeColor()
100103
}
101104

102-
data := opt.Data
103105
formatter := opt.Formatter
104106
if len(formatter) != 0 {
105-
for index, text := range data {
106-
data[index] = strings.ReplaceAll(formatter, "{value}", text)
107+
for index, text := range opt.Data {
108+
opt.Data[index] = strings.ReplaceAll(formatter, "{value}", text)
107109
}
108110
}
109-
dataCount := len(data)
111+
dataCount := len(opt.Data)
110112

111113
centerLabels := !isFalse(opt.BoundaryGap)
112114
isVertical := opt.Position == PositionLeft ||
@@ -130,7 +132,7 @@ func (a *axisPainter) Render() (Box, error) {
130132
if isTextRotation {
131133
top.SetTextRotation(opt.TextRotation)
132134
}
133-
textMaxWidth, textMaxHeight := top.MeasureTextMaxWidthHeight(data)
135+
textMaxWidth, textMaxHeight := top.MeasureTextMaxWidthHeight(opt.Data)
134136
if isTextRotation {
135137
top.ClearTextRotation()
136138
}
@@ -200,11 +202,11 @@ func (a *axisPainter) Render() (Box, error) {
200202
labelCount := opt.LabelCount
201203
if labelCount <= 0 {
202204
var maxLabelCount int
203-
// Add 10px for some minimal extra padding and calculate more suitable display item count on text width
205+
// Add 10px and remove one for some minimal extra padding so that letters don't collide
204206
if orient == OrientVertical {
205-
maxLabelCount = top.Height() / (textMaxHeight + 10)
207+
maxLabelCount = (top.Height() / (textMaxHeight + 10)) - 1
206208
} else {
207-
maxLabelCount = top.Width() / (textMaxWidth + 10)
209+
maxLabelCount = (top.Width() / (textMaxWidth + 10)) - 1
208210
}
209211
if opt.Unit > 0 {
210212
multiplier := 1.0
@@ -218,10 +220,6 @@ func (a *axisPainter) Render() (Box, error) {
218220
}
219221
} else {
220222
labelCount = maxLabelCount
221-
if ((dataCount/(labelCount-1))%5 == 0) || ((dataCount/(labelCount-1))%2 == 0) {
222-
// prefer %5 or %2 units if reasonable
223-
labelCount--
224-
}
225223
}
226224
}
227225
if labelCount > dataCount {
@@ -263,14 +261,15 @@ func (a *axisPainter) Render() (Box, error) {
263261
Top: labelPaddingTop,
264262
Right: labelPaddingRight,
265263
})).MultiText(MultiTextOption{
266-
First: opt.FirstAxis,
267-
Align: textAlign,
268-
TextList: data,
269-
Orient: orient,
270-
LabelCount: labelCount,
271-
CenterLabels: centerLabels,
272-
TextRotation: opt.TextRotation,
273-
Offset: opt.LabelOffset,
264+
First: opt.FirstAxis,
265+
Align: textAlign,
266+
TextList: opt.Data,
267+
Orient: orient,
268+
LabelCount: labelCount,
269+
LabelSkipCount: opt.LabelSkipCount,
270+
CenterLabels: centerLabels,
271+
TextRotation: opt.TextRotation,
272+
Offset: opt.LabelOffset,
274273
})
275274

276275
if opt.SplitLineShow { // show auxiliary lines

‎bar_chart.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B
4848
opt := b.opt
4949
seriesPainter := result.seriesPainter
5050

51-
xRange := NewRange(b.p, seriesPainter.Width(), len(opt.XAxis.Data), 0.0, 0.0, 0.0)
51+
xRange := NewRange(b.p, seriesPainter.Width(), len(opt.XAxis.Data), 0.0, 0.0, 0.0, 0.0)
5252
x0, x1 := xRange.GetRange(0)
5353
width := int(x1 - x0)
5454
// margin between each block

‎chart_option_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ func TestHorizontalBarRender(t *testing.T) {
295295
require.NoError(t, err)
296296
data, err := p.Bytes()
297297
require.NoError(t, err)
298-
assertEqualSVG(t, "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"600\" height=\"400\">\\n<path d=\"M 0 0\nL 600 0\nL 600 400\nL 0 400\nL 0 0\" style=\"stroke-width:0;stroke:none;fill:rgba(255,255,255,1.0)\"/><path d=\"M 214 29\nL 244 29\" style=\"stroke-width:3;stroke:rgba(84,112,198,1.0);fill:rgba(84,112,198,1.0)\"/><circle cx=\"229\" cy=\"29\" r=\"5\" style=\"stroke-width:3;stroke:rgba(84,112,198,1.0);fill:rgba(84,112,198,1.0)\"/><path d=\"\" style=\"stroke-width:3;stroke:rgba(84,112,198,1.0);fill:rgba(84,112,198,1.0)\"/><text x=\"246\" y=\"35\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">2011</text><path d=\"M 301 29\nL 331 29\" style=\"stroke-width:3;stroke:rgba(145,204,117,1.0);fill:rgba(145,204,117,1.0)\"/><circle cx=\"316\" cy=\"29\" r=\"5\" style=\"stroke-width:3;stroke:rgba(145,204,117,1.0);fill:rgba(145,204,117,1.0)\"/><path d=\"\" style=\"stroke-width:3;stroke:rgba(145,204,117,1.0);fill:rgba(145,204,117,1.0)\"/><text x=\"333\" y=\"35\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">2012</text><text x=\"20\" y=\"35\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">World Population</text><path d=\"M 93 55\nL 98 55\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><path d=\"M 93 104\nL 98 104\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><path d=\"M 93 153\nL 98 153\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><path d=\"M 93 202\nL 98 202\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><path d=\"M 93 251\nL 98 251\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><path d=\"M 93 300\nL 98 300\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><path d=\"M 93 350\nL 98 350\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><path d=\"M 98 55\nL 98 350\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><text x=\"47\" y=\"86\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">World</text><text x=\"48\" y=\"135\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">China</text><text x=\"54\" y=\"184\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">India</text><text x=\"58\" y=\"233\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">USA</text><text x=\"20\" y=\"282\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Indonesia</text><text x=\"49\" y=\"332\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Brazil</text><text x=\"98\" y=\"375\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">0</text><text x=\"190\" y=\"375\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">142.99k</text><text x=\"282\" y=\"375\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">285.99k</text><text x=\"375\" y=\"375\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">428.99k</text><text x=\"467\" y=\"375\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">571.98k</text><text x=\"504\" y=\"375\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">714.98k</text><path d=\"M 190 55\nL 190 350\" style=\"stroke-width:1;stroke:rgba(224,230,242,1.0);fill:none\"/><path d=\"M 282 55\nL 282 350\" style=\"stroke-width:1;stroke:rgba(224,230,242,1.0);fill:none\"/><path d=\"M 375 55\nL 375 350\" style=\"stroke-width:1;stroke:rgba(224,230,242,1.0);fill:none\"/><path d=\"M 467 55\nL 467 350\" style=\"stroke-width:1;stroke:rgba(224,230,242,1.0);fill:none\"/><path d=\"M 560 55\nL 560 350\" style=\"stroke-width:1;stroke:rgba(224,230,242,1.0);fill:none\"/><path d=\"M 98 305\nL 109 305\nL 109 323\nL 98 323\nL 98 305\" style=\"stroke-width:0;stroke:none;fill:rgba(84,112,198,1.0)\"/><path d=\"M 98 256\nL 112 256\nL 112 274\nL 98 274\nL 98 256\" style=\"stroke-width:0;stroke:none;fill:rgba(84,112,198,1.0)\"/><path d=\"M 98 207\nL 115 207\nL 115 225\nL 98 225\nL 98 207\" style=\"stroke-width:0;stroke:none;fill:rgba(84,112,198,1.0)\"/><path d=\"M 98 158\nL 162 158\nL 162 176\nL 98 176\nL 98 158\" style=\"stroke-width:0;stroke:none;fill:rgba(84,112,198,1.0)\"/><path d=\"M 98 109\nL 179 109\nL 179 127\nL 98 127\nL 98 109\" style=\"stroke-width:0;stroke:none;fill:rgba(84,112,198,1.0)\"/><path d=\"M 98 60\nL 486 60\nL 486 78\nL 98 78\nL 98 60\" style=\"stroke-width:0;stroke:none;fill:rgba(84,112,198,1.0)\"/><path d=\"M 98 326\nL 109 326\nL 109 344\nL 98 344\nL 98 326\" style=\"stroke-width:0;stroke:none;fill:rgba(145,204,117,1.0)\"/><path d=\"M 98 277\nL 112 277\nL 112 295\nL 98 295\nL 98 277\" style=\"stroke-width:0;stroke:none;fill:rgba(145,204,117,1.0)\"/><path d=\"M 98 228\nL 117 228\nL 117 246\nL 98 246\nL 98 228\" style=\"stroke-width:0;stroke:none;fill:rgba(145,204,117,1.0)\"/><path d=\"M 98 179\nL 172 179\nL 172 197\nL 98 197\nL 98 179\" style=\"stroke-width:0;stroke:none;fill:rgba(145,204,117,1.0)\"/><path d=\"M 98 130\nL 180 130\nL 180 148\nL 98 148\nL 98 130\" style=\"stroke-width:0;stroke:none;fill:rgba(145,204,117,1.0)\"/><path d=\"M 98 81\nL 517 81\nL 517 99\nL 98 99\nL 98 81\" style=\"stroke-width:0;stroke:none;fill:rgba(145,204,117,1.0)\"/></svg>", string(data))
298+
assertEqualSVG(t, "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"600\" height=\"400\">\\n<path d=\"M 0 0\nL 600 0\nL 600 400\nL 0 400\nL 0 0\" style=\"stroke-width:0;stroke:none;fill:rgba(255,255,255,1.0)\"/><path d=\"M 214 29\nL 244 29\" style=\"stroke-width:3;stroke:rgba(84,112,198,1.0);fill:rgba(84,112,198,1.0)\"/><circle cx=\"229\" cy=\"29\" r=\"5\" style=\"stroke-width:3;stroke:rgba(84,112,198,1.0);fill:rgba(84,112,198,1.0)\"/><path d=\"\" style=\"stroke-width:3;stroke:rgba(84,112,198,1.0);fill:rgba(84,112,198,1.0)\"/><text x=\"246\" y=\"35\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">2011</text><path d=\"M 301 29\nL 331 29\" style=\"stroke-width:3;stroke:rgba(145,204,117,1.0);fill:rgba(145,204,117,1.0)\"/><circle cx=\"316\" cy=\"29\" r=\"5\" style=\"stroke-width:3;stroke:rgba(145,204,117,1.0);fill:rgba(145,204,117,1.0)\"/><path d=\"\" style=\"stroke-width:3;stroke:rgba(145,204,117,1.0);fill:rgba(145,204,117,1.0)\"/><text x=\"333\" y=\"35\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">2012</text><text x=\"20\" y=\"35\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">World Population</text><path d=\"M 93 55\nL 98 55\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><path d=\"M 93 104\nL 98 104\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><path d=\"M 93 153\nL 98 153\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><path d=\"M 93 202\nL 98 202\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><path d=\"M 93 251\nL 98 251\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><path d=\"M 93 300\nL 98 300\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><path d=\"M 93 350\nL 98 350\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><path d=\"M 98 55\nL 98 350\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><text x=\"47\" y=\"86\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">World</text><text x=\"48\" y=\"135\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">China</text><text x=\"54\" y=\"184\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">India</text><text x=\"58\" y=\"233\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">USA</text><text x=\"20\" y=\"282\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Indonesia</text><text x=\"49\" y=\"332\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Brazil</text><text x=\"98\" y=\"375\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">0</text><text x=\"190\" y=\"375\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">142.99k</text><text x=\"282\" y=\"375\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">285.99k</text><text x=\"375\" y=\"375\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">428.99k</text><text x=\"467\" y=\"375\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">571.99k</text><text x=\"504\" y=\"375\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">714.98k</text><path d=\"M 190 55\nL 190 350\" style=\"stroke-width:1;stroke:rgba(224,230,242,1.0);fill:none\"/><path d=\"M 282 55\nL 282 350\" style=\"stroke-width:1;stroke:rgba(224,230,242,1.0);fill:none\"/><path d=\"M 375 55\nL 375 350\" style=\"stroke-width:1;stroke:rgba(224,230,242,1.0);fill:none\"/><path d=\"M 467 55\nL 467 350\" style=\"stroke-width:1;stroke:rgba(224,230,242,1.0);fill:none\"/><path d=\"M 560 55\nL 560 350\" style=\"stroke-width:1;stroke:rgba(224,230,242,1.0);fill:none\"/><path d=\"M 98 305\nL 109 305\nL 109 323\nL 98 323\nL 98 305\" style=\"stroke-width:0;stroke:none;fill:rgba(84,112,198,1.0)\"/><path d=\"M 98 256\nL 112 256\nL 112 274\nL 98 274\nL 98 256\" style=\"stroke-width:0;stroke:none;fill:rgba(84,112,198,1.0)\"/><path d=\"M 98 207\nL 115 207\nL 115 225\nL 98 225\nL 98 207\" style=\"stroke-width:0;stroke:none;fill:rgba(84,112,198,1.0)\"/><path d=\"M 98 158\nL 162 158\nL 162 176\nL 98 176\nL 98 158\" style=\"stroke-width:0;stroke:none;fill:rgba(84,112,198,1.0)\"/><path d=\"M 98 109\nL 179 109\nL 179 127\nL 98 127\nL 98 109\" style=\"stroke-width:0;stroke:none;fill:rgba(84,112,198,1.0)\"/><path d=\"M 98 60\nL 486 60\nL 486 78\nL 98 78\nL 98 60\" style=\"stroke-width:0;stroke:none;fill:rgba(84,112,198,1.0)\"/><path d=\"M 98 326\nL 109 326\nL 109 344\nL 98 344\nL 98 326\" style=\"stroke-width:0;stroke:none;fill:rgba(145,204,117,1.0)\"/><path d=\"M 98 277\nL 112 277\nL 112 295\nL 98 295\nL 98 277\" style=\"stroke-width:0;stroke:none;fill:rgba(145,204,117,1.0)\"/><path d=\"M 98 228\nL 117 228\nL 117 246\nL 98 246\nL 98 228\" style=\"stroke-width:0;stroke:none;fill:rgba(145,204,117,1.0)\"/><path d=\"M 98 179\nL 172 179\nL 172 197\nL 98 197\nL 98 179\" style=\"stroke-width:0;stroke:none;fill:rgba(145,204,117,1.0)\"/><path d=\"M 98 130\nL 180 130\nL 180 148\nL 98 148\nL 98 130\" style=\"stroke-width:0;stroke:none;fill:rgba(145,204,117,1.0)\"/><path d=\"M 98 81\nL 517 81\nL 517 99\nL 98 99\nL 98 81\" style=\"stroke-width:0;stroke:none;fill:rgba(145,204,117,1.0)\"/></svg>", string(data))
299299
}
300300

301301
func TestPieRender(t *testing.T) {

‎charts.go

+43-10
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const labelFontSize = 10
1212
const smallLabelFontSize = 8
1313
const defaultDotWidth = 2.0
1414
const defaultStrokeWidth = 2.0
15-
const defaultAxisLabelCount = 10
15+
const defaultYAxisLabelCount = 10
1616

1717
var defaultChartWidth = 600
1818
var defaultChartHeight = 400
@@ -160,28 +160,61 @@ func defaultRender(p *Painter, opt defaultRenderOption) (*defaultRenderResult, e
160160
if len(opt.YAxisOptions) > index {
161161
yAxisOption = opt.YAxisOptions[index]
162162
}
163-
padRange := 1.0
163+
minPadRange, maxPadRange := 1.0, 1.0
164+
if yAxisOption.RangeValuePaddingScale != nil {
165+
minPadRange = *yAxisOption.RangeValuePaddingScale
166+
maxPadRange = *yAxisOption.RangeValuePaddingScale
167+
}
164168
min, max := opt.SeriesList.GetMinMax(index)
165169
if yAxisOption.Min != nil && *yAxisOption.Min < min {
166-
padRange = 0.0
167170
min = *yAxisOption.Min
171+
minPadRange = 0.0
168172
}
169173
if yAxisOption.Max != nil && *yAxisOption.Max > max {
170-
padRange = 0.0
171174
max = *yAxisOption.Max
175+
maxPadRange = 0.0
172176
}
173-
if yAxisOption.RangeValuePaddingScale != nil {
174-
padRange = *yAxisOption.RangeValuePaddingScale
175-
}
177+
178+
// Label counts and y-axis padding are linked together to produce a user-friendly graph.
179+
// First when considering padding we want to prefer a zero axis start if reasonable, and add a slight
180+
// padding to the max so there is a little space at the top of the graph. In addition, we want to pick
181+
// a max value that will result in round intervals on the axis. These details are in range.go.
182+
// But in order to produce round intervals we need to have an idea of how many intervals there are.
183+
// In addition, if the user specified a `Unit` value we may need to adjust our label count calculation
184+
// based on the padded range.
185+
//
186+
// In order to accomplish this, we estimate the label count (if necessary), pad the range, then precisely
187+
// calculate the label count.
188+
// TODO - label counts are also calculated in axis.go, for the X axis, ideally we unify these implementations
176189
labelCount := yAxisOption.LabelCount
190+
padLabelCount := labelCount
191+
if padLabelCount < 1 {
192+
if yAxisOption.Unit > 0 {
193+
padLabelCount = int((max-min)/yAxisOption.Unit) + 1
194+
} else {
195+
padLabelCount = defaultYAxisLabelCount
196+
}
197+
}
198+
// we call padRange directly because we need to do this padding before we can calculate the final labelCount for the axisRange
199+
min, max = padRange(padLabelCount, min, max, minPadRange, maxPadRange)
177200
if labelCount <= 0 {
178201
if yAxisOption.Unit > 0 {
179-
labelCount = int((max - min) / yAxisOption.Unit)
202+
if yAxisOption.Max == nil {
203+
max = math.Trunc(math.Ceil(max/yAxisOption.Unit) * yAxisOption.Unit)
204+
}
205+
labelCount = int((max-min)/yAxisOption.Unit) + 1
180206
} else {
181-
labelCount = defaultAxisLabelCount
207+
labelCount = defaultYAxisLabelCount
182208
}
209+
yAxisOption.LabelCount = labelCount
210+
}
211+
r := axisRange{
212+
p: p,
213+
divideCount: labelCount,
214+
min: min,
215+
max: max,
216+
size: rangeHeight,
183217
}
184-
r := NewRange(p, rangeHeight, labelCount, min, max, padRange)
185218
result.axisRanges[index] = r
186219

187220
if yAxisOption.Theme == nil {

‎horizontal_bar_chart.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ func (h *horizontalBarChart) render(result *defaultRenderResult, seriesList Seri
6969
theme := opt.Theme
7070

7171
min, max := seriesList.GetMinMax(0)
72-
xRange := NewRange(p, seriesPainter.Width(), len(seriesList[0].Data), min, max, 1.0)
72+
xRange := NewRange(p, seriesPainter.Width(), len(seriesList[0].Data), min, max, 1.0, 1.0)
7373
seriesNames := seriesList.Names()
7474

7575
rendererList := []Renderer{}

‎line_chart_test.go

+246-148
Large diffs are not rendered by default.

‎mark_line_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func TestMarkLine(t *testing.T) {
3131
FontColor: drawing.ColorBlack,
3232
StrokeColor: drawing.ColorBlack,
3333
Series: series,
34-
Range: NewRange(p, p.Height(), 6, 0.0, 5.0, 1.0),
34+
Range: NewRange(p, p.Height(), 6, 0.0, 5.0, 1.0, 1.0),
3535
})
3636
if _, err := markLine.Render(); err != nil {
3737
return nil, err

‎painter.go

+13-3
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,9 @@ type MultiTextOption struct {
5252
TextRotation float64
5353
Offset Box
5454
// The first text index
55-
First int
56-
LabelCount int
55+
First int
56+
LabelCount int
57+
LabelSkipCount int
5758
}
5859

5960
type GridOption struct {
@@ -676,16 +677,25 @@ func (p *Painter) MultiText(opt MultiTextOption) *Painter {
676677
}
677678
isTextRotation := opt.TextRotation != 0
678679
positionCount := len(positions)
680+
681+
skippedLabels := opt.LabelSkipCount // specify the skip count to ensure the top value is listed
679682
for index, start := range positions {
680683
if opt.CenterLabels && index == positionCount-1 {
681684
break // positions have one item more than we can map to text, this extra value is used to center against
682685
} else if index < opt.First {
683686
continue
684687
} else if !isVertical &&
685-
index != count-1 /* one off case for last label due to values and label qty difference */ &&
688+
index != count-1 && // one off case for last label due to values and label qty difference
686689
!isTick(positionCount-opt.First, opt.LabelCount+1, index-opt.First) {
687690
continue
691+
} else if index != count-1 && // ensure the bottom value is always printed
692+
skippedLabels < opt.LabelSkipCount {
693+
skippedLabels++
694+
continue
695+
} else {
696+
skippedLabels = 0
688697
}
698+
689699
if isTextRotation {
690700
p.ClearTextRotation()
691701
p.SetTextRotation(opt.TextRotation)

‎range.go

+16-13
Original file line numberDiff line numberDiff line change
@@ -18,29 +18,28 @@ type axisRange struct {
1818
}
1919

2020
// NewRange returns a range of data for an axis, this range will have padding to better present the data.
21-
func NewRange(painter *Painter, size, labelCount int, min, max float64, paddingScale float64) axisRange {
22-
min, max = padRange(labelCount, min, max, paddingScale)
21+
func NewRange(painter *Painter, size, divideCount int, min, max, minPaddingScale, maxPaddingScale float64) axisRange {
22+
min, max = padRange(divideCount, min, max, minPaddingScale, maxPaddingScale)
2323
return axisRange{
2424
p: painter,
25-
divideCount: labelCount,
25+
divideCount: divideCount,
2626
min: min,
2727
max: max,
2828
size: size,
2929
}
3030
}
3131

32-
func padRange(labelCount int, min, max, paddingScale float64) (float64, float64) {
33-
if paddingScale <= 0 {
32+
func padRange(divideCount int, min, max, minPaddingScale, maxPaddingScale float64) (float64, float64) {
33+
if minPaddingScale <= 0.0 && maxPaddingScale <= 0.0 {
3434
return min, max
3535
}
3636
// scale percents for min value
37-
scaledMinPadPercentMin := rangeMinPaddingPercentMin * paddingScale
38-
scaledMinPadPercentMax := rangeMinPaddingPercentMax * paddingScale
37+
scaledMinPadPercentMin := rangeMinPaddingPercentMin * minPaddingScale
38+
scaledMinPadPercentMax := rangeMinPaddingPercentMax * minPaddingScale
3939
// scale percents for max value
40-
scaledMaxPadPercentMin := rangeMaxPaddingPercentMin * paddingScale
41-
scaledMaxPadPercentMax := rangeMaxPaddingPercentMax * paddingScale
40+
scaledMaxPadPercentMin := rangeMaxPaddingPercentMin * maxPaddingScale
41+
scaledMaxPadPercentMax := rangeMaxPaddingPercentMax * maxPaddingScale
4242
minResult := min
43-
maxResult := max
4443
spanIncrement := (max - min) * 0.01 // must be 1% of the span
4544
var spanIncrementMultiplier float64
4645
// find a min value to start our range from
@@ -78,12 +77,16 @@ rootLoop:
7877
minResult = minTrunk // remove possible float multiplication inaccuracies
7978
}
8079

80+
if maxPaddingScale <= 0.0 {
81+
return minResult, max
82+
}
83+
8184
// update max to provide ideal padding and human friendly intervals
82-
interval := (max - minResult) / float64(labelCount-1)
83-
roundedInterval, _ := friendlyRound(interval, spanIncrement/float64(labelCount-1),
85+
interval := (max - minResult) / float64(divideCount-1)
86+
roundedInterval, _ := friendlyRound(interval, spanIncrement/float64(divideCount-1),
8487
math.Max(spanIncrementMultiplier, scaledMaxPadPercentMin),
8588
scaledMaxPadPercentMin, scaledMaxPadPercentMax, true)
86-
maxResult = minResult + (roundedInterval * float64(labelCount-1))
89+
maxResult := minResult + (roundedInterval * float64(divideCount-1))
8790
if maxTrunk := math.Trunc(maxResult); maxTrunk >= max+(spanIncrement*scaledMaxPadPercentMin) {
8891
maxResult = maxTrunk // remove possible float multiplication inaccuracies
8992
}

‎range_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ func TestPadRange(t *testing.T) {
6161

6262
for _, tc := range testCases {
6363
t.Run(tc.name, func(t *testing.T) {
64-
min, max := padRange(tc.labelCount, tc.minValue, tc.maxValue, 1.0)
64+
min, max := padRange(tc.labelCount, tc.minValue, tc.maxValue, 1.0, 1.0)
6565

6666
assert.Equal(t, tc.expectedMinValue, min, "Unexpected value rounding %v", tc.minValue)
6767
assert.Equal(t, tc.expectedMaxValue, max, "Unexpected value rounding %v", tc.maxValue)

‎util.go

+5
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ func FalseFlag() *bool {
2121
return &f
2222
}
2323

24+
func ZeroFloat() *float64 {
25+
v := 0.0
26+
return &v
27+
}
28+
2429
func isFalse(flag *bool) bool {
2530
if flag != nil && !*flag {
2631
return true

‎xaxis.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,11 @@ type XAxisOption struct {
3030
FirstAxis int
3131
// The offset of label
3232
LabelOffset Box
33-
isValueAxis bool
3433
// Unit is a suggestion for how large the axis step is, this is a recommendation only. Larger numbers result in fewer labels.
3534
Unit float64
3635
// LabelCount is the number of labels to show on the axis. Specify a smaller number to reduce writing collisions.
37-
LabelCount int
36+
LabelCount int
37+
isValueAxis bool
3838
}
3939

4040
const defaultXAxisHeight = 30

‎yaxis.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ type YAxisOption struct {
3232
// Unit is a suggestion for how large the axis step is, this is a recommendation only. Larger numbers result in fewer labels.
3333
Unit float64
3434
// LabelCount is the number of labels to show on the axis. Specify a smaller number to reduce writing collisions.
35-
LabelCount int
35+
LabelCount int
36+
// LabelSkipCount specifies a number of lines between labels where there will be no label and instead just a horizontal line.
37+
LabelSkipCount int
3638
isCategoryAxis bool
3739
}
3840

@@ -72,6 +74,7 @@ func (opt *YAxisOption) ToAxisOption(p *Painter) AxisOption {
7274
BoundaryGap: FalseFlag(),
7375
Unit: opt.Unit,
7476
LabelCount: opt.LabelCount,
77+
LabelSkipCount: opt.LabelSkipCount,
7578
SplitLineShow: true,
7679
SplitLineColor: theme.GetAxisSplitLineColor(),
7780
Show: opt.Show,

0 commit comments

Comments
 (0)
Please sign in to comment.