From c01f4001f1c6cdc0b8b5755a52867b5da07c1b1a Mon Sep 17 00:00:00 2001 From: vicanso Date: Sun, 6 Feb 2022 09:55:27 +0800 Subject: [PATCH] refactor: auto count the split number for x axis --- axis.go | 76 +++++++++++++++++++++++++++++++++++-------- axis_test.go | 91 +++++++++++++++++++++++++++++++++++++--------------- util.go | 13 ++------ util_test.go | 6 ---- xaxis.go | 4 ++- yaxis.go | 2 +- 6 files changed, 134 insertions(+), 58 deletions(-) diff --git a/axis.go b/axis.go index ba0604e..eb0327f 100644 --- a/axis.go +++ b/axis.go @@ -23,15 +23,18 @@ package charts import ( + "math" + "github.com/golang/freetype/truetype" "github.com/wcharczuk/go-chart/v2" "github.com/wcharczuk/go-chart/v2/drawing" ) -type AxisStyle struct { +type AxisOption struct { BoundaryGap *bool Show *bool Position string + SplitNumber int ClassName string StrokeColor drawing.Color StrokeWidth float64 @@ -48,10 +51,10 @@ type AxisStyle struct { type axis struct { d *Draw data *AxisDataList - style *AxisStyle + style *AxisOption } -func NewAxis(d *Draw, data AxisDataList, style AxisStyle) *axis { +func NewAxis(d *Draw, data AxisDataList, style AxisOption) *axis { return &axis{ d: d, data: &data, @@ -60,15 +63,15 @@ func NewAxis(d *Draw, data AxisDataList, style AxisStyle) *axis { } -func (as *AxisStyle) GetLabelMargin() int { +func (as *AxisOption) GetLabelMargin() int { return getDefaultInt(as.LabelMargin, 8) } -func (as *AxisStyle) GetTickLength() int { +func (as *AxisOption) GetTickLength() int { return getDefaultInt(as.TickLength, 5) } -func (as *AxisStyle) Style(f *truetype.Font) chart.Style { +func (as *AxisOption) Style(f *truetype.Font) chart.Style { s := chart.Style{ ClassName: as.ClassName, StrokeColor: as.StrokeColor, @@ -99,10 +102,12 @@ func (l AxisDataList) TextList() []string { return textList } -type axisOption struct { +type axisRenderOption struct { textMaxWith int textMaxHeight int boundaryGap bool + unitCount int + modValue int } func NewAxisDataListFromStringList(textList []string) AxisDataList { @@ -115,7 +120,7 @@ func NewAxisDataListFromStringList(textList []string) AxisDataList { return list } -func (a *axis) axisLabel(opt *axisOption) { +func (a *axis) axisLabel(opt *axisRenderOption) { style := a.style data := *a.data d := a.d @@ -131,11 +136,14 @@ func (a *axis) axisLabel(opt *axisOption) { height := d.Box.Height() textList := data.TextList() count := len(textList) + boundaryGap := opt.boundaryGap if !boundaryGap { count-- } + unitCount := opt.unitCount + modValue := opt.modValue labelMargin := style.GetLabelMargin() // 轴线 @@ -176,6 +184,9 @@ func (a *axis) axisLabel(opt *axisOption) { } values := autoDivide(width, count) for index, text := range data.TextList() { + if unitCount != 0 && index%unitCount != modValue { + continue + } x := values[index] leftOffset := 0 b := r.MeasureText(text) @@ -191,7 +202,7 @@ func (a *axis) axisLabel(opt *axisOption) { } } -func (a *axis) axisLine(opt *axisOption) { +func (a *axis) axisLine(opt *axisRenderOption) { d := a.d r := d.Render style := a.style @@ -239,7 +250,7 @@ func (a *axis) axisLine(opt *axisOption) { r.FillStroke() } -func (a *axis) axisTick(opt *axisOption) { +func (a *axis) axisTick(opt *axisRenderOption) { d := a.d r := d.Render @@ -259,6 +270,7 @@ func (a *axis) axisTick(opt *axisOption) { if isFalse(style.TickShow) { tickShow = false } + unitCount := opt.unitCount tickLengthValue := style.GetTickLength() labelHeight := labelMargin + opt.textMaxHeight @@ -308,7 +320,10 @@ func (a *axis) axisTick(opt *axisOption) { y0 = labelHeight } if tickShow { - for _, v := range values { + for index, v := range values { + if index%unitCount != 0 { + continue + } x := v y := y0 d.moveTo(x, y-tickLengthValue) @@ -326,7 +341,10 @@ func (a *axis) axisTick(opt *axisOption) { splitLineHeight = height - labelHeight } - for _, v := range values { + for index, v := range values { + if index%unitCount != 0 { + continue + } x := v y := y0 @@ -368,7 +386,7 @@ func (a *axis) Render() { return } textMaxWidth, textMaxHeight := a.axisMeasureTextMaxWidthHeight() - opt := &axisOption{ + opt := &axisRenderOption{ textMaxWith: textMaxWidth, textMaxHeight: textMaxHeight, boundaryGap: true, @@ -377,6 +395,38 @@ func (a *axis) Render() { opt.boundaryGap = false } + unitCount := chart.MaxInt(style.SplitNumber, 1) + width := a.d.Box.Width() + textList := a.data.TextList() + count := len(textList) + + position := style.Position + switch position { + case PositionLeft: + fallthrough + case PositionRight: + default: + maxCount := width / (opt.textMaxWith + 10) + // 可以显示所有 + if maxCount >= count { + unitCount = 1 + } else if maxCount < count/unitCount { + unitCount = int(math.Ceil(float64(count) / float64(maxCount))) + } + } + + boundaryGap := opt.boundaryGap + modValue := 0 + if boundaryGap && unitCount > 1 { + // 如果是居中,unit count需要设置为奇数 + if unitCount%2 == 0 { + unitCount++ + } + modValue = unitCount / 2 + } + opt.modValue = modValue + opt.unitCount = unitCount + // 坐标轴线 a.axisLine(opt) a.axisTick(opt) diff --git a/axis_test.go b/axis_test.go index e8efccd..a6a7690 100644 --- a/axis_test.go +++ b/axis_test.go @@ -31,10 +31,10 @@ import ( "github.com/wcharczuk/go-chart/v2/drawing" ) -func TestAxisStyle(t *testing.T) { +func TestAxisOption(t *testing.T) { assert := assert.New(t) - as := AxisStyle{} + as := AxisOption{} assert.Equal(8, as.GetLabelMargin()) as.LabelMargin = 10 @@ -64,7 +64,7 @@ func TestAxisDataList(t *testing.T) { func TestAxis(t *testing.T) { assert := assert.New(t) - data := NewAxisDataListFromStringList([]string{ + axisData := NewAxisDataListFromStringList([]string{ "Mon", "Tue", "Wed", @@ -73,8 +73,8 @@ func TestAxis(t *testing.T) { "Sat", "Sun", }) - getDefaultStyle := func() AxisStyle { - return AxisStyle{ + getDefaultOption := func() AxisOption { + return AxisOption{ StrokeColor: drawing.ColorBlack, StrokeWidth: 1, FontColor: drawing.ColorBlack, @@ -85,14 +85,15 @@ func TestAxis(t *testing.T) { } } tests := []struct { - newStyle func() AxisStyle - result string + newOption func() AxisOption + newData func() AxisDataList + result string }{ // 文本按起始位置展示 // axis位于bottom { - newStyle: func() AxisStyle { - opt := getDefaultStyle() + newOption: func() AxisOption { + opt := getDefaultOption() opt.BoundaryGap = FalseFlag() return opt }, @@ -101,8 +102,8 @@ func TestAxis(t *testing.T) { // 文本居中展示 // axis位于bottom { - newStyle: func() AxisStyle { - opt := getDefaultStyle() + newOption: func() AxisOption { + opt := getDefaultOption() return opt }, result: "\\nMonTueWedThuFriSatSun", @@ -110,8 +111,8 @@ func TestAxis(t *testing.T) { // 文本按起始位置展示 // axis位于top { - newStyle: func() AxisStyle { - opt := getDefaultStyle() + newOption: func() AxisOption { + opt := getDefaultOption() opt.Position = PositionTop opt.BoundaryGap = FalseFlag() return opt @@ -121,8 +122,8 @@ func TestAxis(t *testing.T) { // 文本居中展示 // axis位于top { - newStyle: func() AxisStyle { - opt := getDefaultStyle() + newOption: func() AxisOption { + opt := getDefaultOption() opt.Position = PositionTop return opt }, @@ -131,8 +132,8 @@ func TestAxis(t *testing.T) { // 文本按起始位置展示 // axis位于left { - newStyle: func() AxisStyle { - opt := getDefaultStyle() + newOption: func() AxisOption { + opt := getDefaultOption() opt.Position = PositionLeft opt.BoundaryGap = FalseFlag() return opt @@ -142,8 +143,8 @@ func TestAxis(t *testing.T) { // 文本居中展示 // axis位于left { - newStyle: func() AxisStyle { - opt := getDefaultStyle() + newOption: func() AxisOption { + opt := getDefaultOption() opt.Position = PositionLeft return opt }, @@ -152,8 +153,8 @@ func TestAxis(t *testing.T) { // 文本按起始位置展示 // axis位于right { - newStyle: func() AxisStyle { - opt := getDefaultStyle() + newOption: func() AxisOption { + opt := getDefaultOption() opt.Position = PositionRight opt.BoundaryGap = FalseFlag() return opt @@ -163,13 +164,47 @@ func TestAxis(t *testing.T) { // 文本居中展示 // axis位于right { - newStyle: func() AxisStyle { - opt := getDefaultStyle() + newOption: func() AxisOption { + opt := getDefaultOption() opt.Position = PositionRight return opt }, result: "\\nMonTueWedThuFriSatSun", }, + // text较多,仅展示部分 + { + newOption: func() AxisOption { + opt := getDefaultOption() + opt.Position = PositionBottom + return opt + }, + newData: func() AxisDataList { + return NewAxisDataListFromStringList([]string{ + "01-01", + "01-02", + "01-03", + "01-04", + "01-05", + "01-06", + "01-07", + "01-08", + "01-09", + "01-10", + "01-11", + "01-12", + "01-13", + "01-14", + "01-15", + "01-16", + "01-17", + "01-18", + "01-19", + "01-20", + "01-21", + }) + }, + result: "\\n01-0201-0501-0801-1101-1401-1701-20", + }, } for _, tt := range tests { d, err := NewDraw(DrawOption{ @@ -182,7 +217,11 @@ func TestAxis(t *testing.T) { Bottom: 5, })) assert.Nil(err) - style := tt.newStyle() + style := tt.newOption() + data := axisData + if tt.newData != nil { + data = tt.newData() + } NewAxis(d, data, style).Render() result, err := d.Bytes() @@ -204,14 +243,14 @@ func TestMeasureAxis(t *testing.T) { "Sun", }) f, _ := chart.GetDefaultFont() - width := NewAxis(d, data, AxisStyle{ + width := NewAxis(d, data, AxisOption{ FontSize: 12, Font: f, Position: PositionLeft, }).measureAxis() assert.Equal(44, width) - height := NewAxis(d, data, AxisStyle{ + height := NewAxis(d, data, AxisOption{ FontSize: 12, Font: f, Position: PositionTop, diff --git a/util.go b/util.go index c9c817f..10fcb18 100644 --- a/util.go +++ b/util.go @@ -62,15 +62,6 @@ func autoDivide(max, size int) []int { values[size] = max return values } -func maxInt(values ...int) int { - result := 0 - for _, v := range values { - if v > result { - result = v - } - } - return result -} // measureTextMaxWidthHeight returns maxWidth and maxHeight of text list func measureTextMaxWidthHeight(textList []string, r chart.Renderer) (int, int) { @@ -78,8 +69,8 @@ func measureTextMaxWidthHeight(textList []string, r chart.Renderer) (int, int) { maxHeight := 0 for _, text := range textList { box := r.MeasureText(text) - maxWidth = maxInt(maxWidth, box.Width()) - maxHeight = maxInt(maxHeight, box.Height()) + maxWidth = chart.MaxInt(maxWidth, box.Width()) + maxHeight = chart.MaxInt(maxHeight, box.Height()) } return maxWidth, maxHeight } diff --git a/util_test.go b/util_test.go index 983efbc..3d155ed 100644 --- a/util_test.go +++ b/util_test.go @@ -51,12 +51,6 @@ func TestAutoDivide(t *testing.T) { }, autoDivide(600, 7)) } -func TestMaxInt(t *testing.T) { - assert := assert.New(t) - - assert.Equal(5, maxInt(1, 3, 5, 2)) -} - func TestMeasureTextMaxWidthHeight(t *testing.T) { assert := assert.New(t) r, err := chart.SVG(400, 300) diff --git a/xaxis.go b/xaxis.go index 5993796..0383c73 100644 --- a/xaxis.go +++ b/xaxis.go @@ -29,6 +29,7 @@ type XAxisOption struct { Data []string Theme string Hidden bool + SplitNumber int // TODO split number } @@ -47,11 +48,12 @@ func drawXAxis(p *Draw, opt *XAxisOption) (int, *Range, error) { } theme := NewTheme(opt.Theme) data := NewAxisDataListFromStringList(opt.Data) - style := AxisStyle{ + style := AxisOption{ BoundaryGap: opt.BoundaryGap, StrokeColor: theme.GetAxisStrokeColor(), FontColor: theme.GetAxisStrokeColor(), StrokeWidth: 1, + SplitNumber: opt.SplitNumber, } boundary := true diff --git a/yaxis.go b/yaxis.go index 6b8af4c..79ed299 100644 --- a/yaxis.go +++ b/yaxis.go @@ -38,7 +38,7 @@ func drawYAxis(p *Draw, opt *ChartOption, xAxisHeight int, padding chart.Box) (* theme := NewTheme(opt.Theme) yRange := opt.getYRange(0) data := NewAxisDataListFromStringList(yRange.Values()) - style := AxisStyle{ + style := AxisOption{ Position: PositionLeft, BoundaryGap: FalseFlag(), FontColor: theme.GetAxisStrokeColor(),