From eef3a2f97b1ca9d1e337a025a493ee24c8832af4 Mon Sep 17 00:00:00 2001 From: vicanso Date: Wed, 6 Jul 2022 20:28:46 +0800 Subject: [PATCH 01/73] fix: fix label overflow, #13 --- legend.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/legend.go b/legend.go index 2acd35b..d3b135e 100644 --- a/legend.go +++ b/legend.go @@ -135,7 +135,11 @@ func (l *legendPainter) Render() (Box, error) { textOffset := 2 legendWidth := 30 legendHeight := 20 + itemMaxHeight := 0 for _, item := range measureList { + if item.Height() > itemMaxHeight { + itemMaxHeight = item.Height() + } if opt.Orient == OrientVertical { height += item.Height() } else { @@ -170,6 +174,10 @@ func (l *legendPainter) Render() (Box, error) { } top, _ := strconv.Atoi(opt.Top) + if left < 0 { + left = 0 + } + x := int(left) y := int(top) + 10 x0 := x @@ -199,6 +207,10 @@ func (l *legendPainter) Render() (Box, error) { FillColor: color, StrokeColor: color, }) + if x0+measureList[index].Width() > p.Width() { + x0 = 0 + y0 += itemMaxHeight + } if opt.Align != AlignRight { x0 = drawIcon(y0, x0) x0 += textOffset From 0a3ac7096a30b41861faa8cb96bfa1bf84a7465c Mon Sep 17 00:00:00 2001 From: vicanso Date: Wed, 6 Jul 2022 20:44:52 +0800 Subject: [PATCH 02/73] refactor: adjust text render of axis --- axis.go | 8 +++++++- painter.go | 3 ++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/axis.go b/axis.go index 53b5362..17e8e9f 100644 --- a/axis.go +++ b/axis.go @@ -153,8 +153,14 @@ func (a *axisPainter) Render() (Box, error) { top.SetDrawingStyle(style).OverrideTextStyle(style) textMaxWidth, textMaxHeight := top.MeasureTextMaxWidthHeight(data) - textCount := ceilFloatToInt(float64(top.Width()) / float64(textMaxWidth)) + + textFillWidth := float64(textMaxWidth) * 1.3 + textCount := ceilFloatToInt(float64(top.Width()) / textFillWidth) unit := ceilFloatToInt(float64(dataCount) / float64(chart.MaxInt(textCount, opt.SplitNumber))) + // 偶数 + if unit%2 == 0 && dataCount%(unit+1) == 0 { + unit++ + } width := 0 height := 0 diff --git a/painter.go b/painter.go index 62a4378..0771288 100644 --- a/painter.go +++ b/painter.go @@ -653,8 +653,9 @@ func (p *Painter) MultiText(opt MultiTextOption) *Painter { } else { values = autoDivide(width, count) } + showIndex := opt.Unit / 2 for index, text := range opt.TextList { - if opt.Unit != 0 && index%opt.Unit != 0 { + if opt.Unit != 0 && index%opt.Unit != showIndex { continue } box := p.MeasureText(text) From c220b10ae600c68112792277c5a1103499964e38 Mon Sep 17 00:00:00 2001 From: vicanso Date: Thu, 7 Jul 2022 20:50:29 +0800 Subject: [PATCH 03/73] refactor: adjust label padding of axis --- axis.go | 3 ++- bar_chart_test.go | 2 +- chart_option_test.go | 2 +- echarts_test.go | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/axis.go b/axis.go index 17e8e9f..ebc6782 100644 --- a/axis.go +++ b/axis.go @@ -154,7 +154,8 @@ func (a *axisPainter) Render() (Box, error) { textMaxWidth, textMaxHeight := top.MeasureTextMaxWidthHeight(data) - textFillWidth := float64(textMaxWidth) * 1.3 + // 增加30px来计算文本展示区域 + textFillWidth := float64(textMaxWidth + 20) textCount := ceilFloatToInt(float64(top.Width()) / textFillWidth) unit := ceilFloatToInt(float64(dataCount) / float64(chart.MaxInt(textCount, opt.SplitNumber))) // 偶数 diff --git a/bar_chart_test.go b/bar_chart_test.go index 138b3ca..f1bd688 100644 --- a/bar_chart_test.go +++ b/bar_chart_test.go @@ -102,7 +102,7 @@ func TestBarChart(t *testing.T) { } return p.Bytes() }, - result: "\\n24020016012080400JanFebMarAprMayJunJulAugSepOctNovDec24.9723.225.676.7135.6162.232.6206.43.32.65.9926.428.770.7175.6182.248.718.862.3", + result: "\\n24020016012080400FebMayAugNov24.9723.225.676.7135.6162.232.6206.43.32.65.9926.428.770.7175.6182.248.718.862.3", }, } diff --git a/chart_option_test.go b/chart_option_test.go index 5e53e46..1238422 100644 --- a/chart_option_test.go +++ b/chart_option_test.go @@ -277,7 +277,7 @@ func TestBarRender(t *testing.T) { assert.Nil(err) data, err := p.Bytes() assert.Nil(err) - assert.Equal("\\nRainfallEvaporation24020016012080400JanFebMarAprMayJunJulAugSepOctNovDec162.22182.22.341.6248.07", string(data)) + assert.Equal("\\nRainfallEvaporation24020016012080400FebMayAugNov162.22182.22.341.6248.07", string(data)) } func TestHorizontalBarRender(t *testing.T) { diff --git a/echarts_test.go b/echarts_test.go index 4d50d9e..8deda2d 100644 --- a/echarts_test.go +++ b/echarts_test.go @@ -578,5 +578,5 @@ func TestRenderEChartsToSVG(t *testing.T) { ] }`) assert.Nil(err) - assert.Equal("\\nRainfallEvaporationRainfall vs EvaporationFake Data24020016012080400JanFebMarAprMayJunJulAugSepOctNovDec162.22182.22.341.6248.07", string(data)) + assert.Equal("\\nRainfallEvaporationRainfall vs EvaporationFake Data24020016012080400FebMayAugNov162.22182.22.341.6248.07", string(data)) } From 959377542e2a1fa1daaec7defdfd56195d3ea4d9 Mon Sep 17 00:00:00 2001 From: vicanso Date: Fri, 8 Jul 2022 21:11:47 +0800 Subject: [PATCH 04/73] fix: fix multi line label --- chart_option_test.go | 4 ++-- legend.go | 7 +++++-- line_chart_test.go | 4 ++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/chart_option_test.go b/chart_option_test.go index 1238422..0cdc2aa 100644 --- a/chart_option_test.go +++ b/chart_option_test.go @@ -204,7 +204,7 @@ func TestLineRender(t *testing.T) { assert.Nil(err) data, err := p.Bytes() assert.Nil(err) - assert.Equal("\\nEmailUnion AdsVideo AdsDirectSearch EngineLine1.44k1.2k9607204802400MonTueWedThuFriSatSun", string(data)) + assert.Equal("\\nEmailUnion AdsVideo AdsDirectSearch EngineLine1.44k1.2k9607204802400MonTueWedThuFriSatSun", string(data)) } func TestBarRender(t *testing.T) { @@ -277,7 +277,7 @@ func TestBarRender(t *testing.T) { assert.Nil(err) data, err := p.Bytes() assert.Nil(err) - assert.Equal("\\nRainfallEvaporation24020016012080400FebMayAugNov162.22182.22.341.6248.07", string(data)) + assert.Equal("\\nRainfallEvaporation24020016012080400FebMayAugNov162.22182.22.341.6248.07", string(data)) } func TestHorizontalBarRender(t *testing.T) { diff --git a/legend.go b/legend.go index d3b135e..820f1b5 100644 --- a/legend.go +++ b/legend.go @@ -146,6 +146,8 @@ func (l *legendPainter) Render() (Box, error) { width += item.Width() } } + // 增加padding + itemMaxHeight += 10 if opt.Orient == OrientVertical { width = maxTextWidth + textOffset + legendWidth height = offset * len(opt.Data) @@ -207,9 +209,10 @@ func (l *legendPainter) Render() (Box, error) { FillColor: color, StrokeColor: color, }) - if x0+measureList[index].Width() > p.Width() { + if x0+measureList[index].Width()+textOffset+offset+legendWidth > p.Width() { x0 = 0 - y0 += itemMaxHeight + y += itemMaxHeight + y0 = y } if opt.Align != AlignRight { x0 = drawIcon(y0, x0) diff --git a/line_chart_test.go b/line_chart_test.go index 856cdf3..ff80741 100644 --- a/line_chart_test.go +++ b/line_chart_test.go @@ -117,7 +117,7 @@ func TestLineChart(t *testing.T) { } return p.Bytes() }, - result: "\\nEmailUnion AdsVideo AdsDirectSearch EngineLine1.44k1.2k9607204802400MonTueWedThuFriSatSun", + result: "\\nEmailUnion AdsVideo AdsDirectSearch EngineLine1.44k1.2k9607204802400MonTueWedThuFriSatSun", }, { render: func(p *Painter) ([]byte, error) { @@ -201,7 +201,7 @@ func TestLineChart(t *testing.T) { } return p.Bytes() }, - result: "\\nEmailUnion AdsVideo AdsDirectSearch EngineLine1.44k1.2k9607204802400MonTueWedThuFriSatSun", + result: "\\nEmailUnion AdsVideo AdsDirectSearch EngineLine1.44k1.2k9607204802400MonTueWedThuFriSatSun", }, } From 805f4381a31f3f46c0a711623ed6f7b94ac98ffa Mon Sep 17 00:00:00 2001 From: vicanso Date: Mon, 11 Jul 2022 20:20:41 +0800 Subject: [PATCH 05/73] fix: fix multi line legend --- chart_option_test.go | 6 +++--- charts.go | 9 +++++++-- legend.go | 9 ++++++++- line_chart_test.go | 4 ++-- pie_chart_test.go | 2 +- 5 files changed, 21 insertions(+), 9 deletions(-) diff --git a/chart_option_test.go b/chart_option_test.go index 0cdc2aa..a025c25 100644 --- a/chart_option_test.go +++ b/chart_option_test.go @@ -204,7 +204,7 @@ func TestLineRender(t *testing.T) { assert.Nil(err) data, err := p.Bytes() assert.Nil(err) - assert.Equal("\\nEmailUnion AdsVideo AdsDirectSearch EngineLine1.44k1.2k9607204802400MonTueWedThuFriSatSun", string(data)) + assert.Equal("\\nEmailUnion AdsVideo AdsDirectSearch EngineLine1.44k1.2k9607204802400MonTueWedThuFriSatSun", string(data)) } func TestBarRender(t *testing.T) { @@ -277,7 +277,7 @@ func TestBarRender(t *testing.T) { assert.Nil(err) data, err := p.Bytes() assert.Nil(err) - assert.Equal("\\nRainfallEvaporation24020016012080400FebMayAugNov162.22182.22.341.6248.07", string(data)) + assert.Equal("\\nRainfallEvaporation24020016012080400FebMayAugNov162.22182.22.341.6248.07", string(data)) } func TestHorizontalBarRender(t *testing.T) { @@ -368,7 +368,7 @@ func TestPieRender(t *testing.T) { assert.Nil(err) data, err := p.Bytes() assert.Nil(err) - assert.Equal("\\nSearch EngineDirectEmailUnion AdsVideo AdsRainfall vs EvaporationFake DataSearch Engine: 33.3%Direct: 23.35%Email: 18.43%Union Ads: 15.37%Video Ads: 9.53%", string(data)) + assert.Equal("\\nSearch EngineDirectEmailUnion AdsVideo AdsRainfall vs EvaporationFake DataSearch Engine: 33.3%Direct: 23.35%Email: 18.43%Union Ads: 15.37%Video Ads: 9.53%", string(data)) } func TestRadarRender(t *testing.T) { diff --git a/charts.go b/charts.go index 6c1c92b..41802d9 100644 --- a/charts.go +++ b/charts.go @@ -25,6 +25,8 @@ package charts import ( "errors" "sort" + + "github.com/wcharczuk/go-chart/v2" ) const labelFontSize = 10 @@ -110,14 +112,16 @@ func defaultRender(p *Painter, opt defaultRenderOption) (*defaultRenderResult, e p = p.Child(PainterPaddingOption(opt.Padding)) } + legendHeight := 0 if len(opt.LegendOption.Data) != 0 { if opt.LegendOption.Theme == nil { opt.LegendOption.Theme = opt.Theme } - _, err := NewLegendPainter(p, opt.LegendOption).Render() + legendResult, err := NewLegendPainter(p, opt.LegendOption).Render() if err != nil { return nil, err } + legendHeight = legendResult.Height() } // 如果有标题 @@ -131,9 +135,10 @@ func defaultRender(p *Painter, opt defaultRenderOption) (*defaultRenderResult, e if err != nil { return nil, err } + p = p.Child(PainterPaddingOption(Box{ // 标题下留白 - Top: titleBox.Height() + 20, + Top: chart.MaxInt(legendHeight, titleBox.Height()) + 20, })) } diff --git a/legend.go b/legend.go index 820f1b5..4e2bc82 100644 --- a/legend.go +++ b/legend.go @@ -182,6 +182,7 @@ func (l *legendPainter) Render() (Box, error) { x := int(left) y := int(top) + 10 + startY := y x0 := x y0 := y @@ -203,13 +204,18 @@ func (l *legendPainter) Render() (Box, error) { } return left + legendWidth } + lastIndex := len(opt.Data) - 1 for index, text := range opt.Data { color := theme.GetSeriesColor(index) p.SetDrawingStyle(Style{ FillColor: color, StrokeColor: color, }) - if x0+measureList[index].Width()+textOffset+offset+legendWidth > p.Width() { + itemWidth := x0 + measureList[index].Width() + textOffset + offset + legendWidth + if lastIndex == index { + itemWidth = x0 + measureList[index].Width() + legendWidth + } + if itemWidth > p.Width() { x0 = 0 y += itemMaxHeight y0 = y @@ -231,6 +237,7 @@ func (l *legendPainter) Render() (Box, error) { x0 += offset y0 = y } + height = y0 - startY + 10 } return Box{ diff --git a/line_chart_test.go b/line_chart_test.go index ff80741..856cdf3 100644 --- a/line_chart_test.go +++ b/line_chart_test.go @@ -117,7 +117,7 @@ func TestLineChart(t *testing.T) { } return p.Bytes() }, - result: "\\nEmailUnion AdsVideo AdsDirectSearch EngineLine1.44k1.2k9607204802400MonTueWedThuFriSatSun", + result: "\\nEmailUnion AdsVideo AdsDirectSearch EngineLine1.44k1.2k9607204802400MonTueWedThuFriSatSun", }, { render: func(p *Painter) ([]byte, error) { @@ -201,7 +201,7 @@ func TestLineChart(t *testing.T) { } return p.Bytes() }, - result: "\\nEmailUnion AdsVideo AdsDirectSearch EngineLine1.44k1.2k9607204802400MonTueWedThuFriSatSun", + result: "\\nEmailUnion AdsVideo AdsDirectSearch EngineLine1.44k1.2k9607204802400MonTueWedThuFriSatSun", }, } diff --git a/pie_chart_test.go b/pie_chart_test.go index c373a7e..070fb03 100644 --- a/pie_chart_test.go +++ b/pie_chart_test.go @@ -78,7 +78,7 @@ func TestPieChart(t *testing.T) { } return p.Bytes() }, - result: "\\nSearch EngineDirectEmailUnion AdsVideo AdsRainfall vs EvaporationFake DataSearch Engine: 33.3%Direct: 23.35%Email: 18.43%Union Ads: 15.37%Video Ads: 9.53%", + result: "\\nSearch EngineDirectEmailUnion AdsVideo AdsRainfall vs EvaporationFake DataSearch Engine: 33.3%Direct: 23.35%Email: 18.43%Union Ads: 15.37%Video Ads: 9.53%", }, } for _, tt := range tests { From b5b2d37e875cf765e04ba2a312a8b1efdc6d5d03 Mon Sep 17 00:00:00 2001 From: vicanso Date: Mon, 11 Jul 2022 20:44:28 +0800 Subject: [PATCH 06/73] fix: fix axis boundary gap, #13 --- painter.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/painter.go b/painter.go index 0771288..1a954e2 100644 --- a/painter.go +++ b/painter.go @@ -637,12 +637,15 @@ func (p *Painter) MultiText(opt MultiTextOption) *Painter { } count := len(opt.TextList) positionCenter := true + showIndex := opt.Unit / 2 if containsString([]string{ PositionLeft, PositionTop, }, opt.Position) { positionCenter = false count-- + // 非居中 + showIndex = 0 } width := p.Width() height := p.Height() @@ -653,7 +656,6 @@ func (p *Painter) MultiText(opt MultiTextOption) *Painter { } else { values = autoDivide(width, count) } - showIndex := opt.Unit / 2 for index, text := range opt.TextList { if opt.Unit != 0 && index%opt.Unit != showIndex { continue From 3af0d4d4450652f132a8f5482aa30e0eb39ba9d4 Mon Sep 17 00:00:00 2001 From: vicanso Date: Thu, 14 Jul 2022 20:14:32 +0800 Subject: [PATCH 07/73] fix: fix pie chart legend --- chart_option_test.go | 2 +- charts.go | 7 ++++++- pie_chart_test.go | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/chart_option_test.go b/chart_option_test.go index a025c25..1238422 100644 --- a/chart_option_test.go +++ b/chart_option_test.go @@ -368,7 +368,7 @@ func TestPieRender(t *testing.T) { assert.Nil(err) data, err := p.Bytes() assert.Nil(err) - assert.Equal("\\nSearch EngineDirectEmailUnion AdsVideo AdsRainfall vs EvaporationFake DataSearch Engine: 33.3%Direct: 23.35%Email: 18.43%Union Ads: 15.37%Video Ads: 9.53%", string(data)) + assert.Equal("\\nSearch EngineDirectEmailUnion AdsVideo AdsRainfall vs EvaporationFake DataSearch Engine: 33.3%Direct: 23.35%Email: 18.43%Union Ads: 15.37%Video Ads: 9.53%", string(data)) } func TestRadarRender(t *testing.T) { diff --git a/charts.go b/charts.go index 41802d9..36bb17e 100644 --- a/charts.go +++ b/charts.go @@ -136,9 +136,14 @@ func defaultRender(p *Painter, opt defaultRenderOption) (*defaultRenderResult, e return nil, err } + top := chart.MaxInt(legendHeight, titleBox.Height()) + // 如果是垂直方式,则不计算legend高度 + if opt.LegendOption.Orient == OrientVertical { + top = titleBox.Height() + } p = p.Child(PainterPaddingOption(Box{ // 标题下留白 - Top: chart.MaxInt(legendHeight, titleBox.Height()) + 20, + Top: top + 20, })) } diff --git a/pie_chart_test.go b/pie_chart_test.go index 070fb03..c373a7e 100644 --- a/pie_chart_test.go +++ b/pie_chart_test.go @@ -78,7 +78,7 @@ func TestPieChart(t *testing.T) { } return p.Bytes() }, - result: "\\nSearch EngineDirectEmailUnion AdsVideo AdsRainfall vs EvaporationFake DataSearch Engine: 33.3%Direct: 23.35%Email: 18.43%Union Ads: 15.37%Video Ads: 9.53%", + result: "\\nSearch EngineDirectEmailUnion AdsVideo AdsRainfall vs EvaporationFake DataSearch Engine: 33.3%Direct: 23.35%Email: 18.43%Union Ads: 15.37%Video Ads: 9.53%", }, } for _, tt := range tests { From 8740c55a1a90f6f895f6601a6afd8f3c4fcf0cbd Mon Sep 17 00:00:00 2001 From: vicanso Date: Tue, 19 Jul 2022 20:12:31 +0800 Subject: [PATCH 08/73] feat: support padding for legend --- examples/line_chart/main.go | 8 +++++++- legend.go | 12 ++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/examples/line_chart/main.go b/examples/line_chart/main.go index 45ff894..a941bca 100644 --- a/examples/line_chart/main.go +++ b/examples/line_chart/main.go @@ -89,7 +89,13 @@ func main() { "Video Ads", "Direct", "Search Engine", - }, charts.PositionCenter), + }, "50"), + func(opt *charts.ChartOption) { + opt.Legend.Padding = charts.Box{ + Top: 5, + Bottom: 10, + } + }, ) if err != nil { diff --git a/legend.go b/legend.go index 4e2bc82..8f21afb 100644 --- a/legend.go +++ b/legend.go @@ -59,6 +59,8 @@ type LegendOption struct { FontColor Color // The flag for show legend, set this to *false will hide legend Show *bool + // The padding of legend + Padding Box } // NewLegendOption returns a legend option @@ -111,9 +113,11 @@ func (l *legendPainter) Render() (Box, error) { if opt.Left == "" { opt.Left = PositionCenter } - p := l.p.Child(PainterPaddingOption(Box{ - Top: 5, - })) + padding := opt.Padding + if padding.IsZero() { + padding.Top = 5 + } + p := l.p.Child(PainterPaddingOption(padding)) p.SetTextStyle(Style{ FontSize: opt.FontSize, FontColor: opt.FontColor, @@ -242,6 +246,6 @@ func (l *legendPainter) Render() (Box, error) { return Box{ Right: width, - Bottom: height, + Bottom: height + padding.Bottom + padding.Top, }, nil } From 3d20bea84663801858ac28067bdbe2f536bb7408 Mon Sep 17 00:00:00 2001 From: vicanso Date: Fri, 22 Jul 2022 20:25:12 +0800 Subject: [PATCH 09/73] refactor: remove unused code --- title.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/title.go b/title.go index 5cdd161..74ab4f9 100644 --- a/title.go +++ b/title.go @@ -36,10 +36,6 @@ type TitleOption struct { Text string // Subtitle text, support \n for new line Subtext string - // // Title style - // Style Style - // // Subtitle style - // SubtextStyle Style // Distance between title component and the left side of the container. // It can be pixel value: 20, percentage value: 20%, // or position value: right, center. From cac6fd03d31477505f3c0e2fdeb62a546e0c8f3b Mon Sep 17 00:00:00 2001 From: vicanso Date: Tue, 26 Jul 2022 20:44:50 +0800 Subject: [PATCH 10/73] fix: fix unit count of xasix --- axis.go | 7 +++++-- chart_option.go | 5 ++++- legend.go | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/axis.go b/axis.go index ebc6782..8e5bfda 100644 --- a/axis.go +++ b/axis.go @@ -156,8 +156,11 @@ func (a *axisPainter) Render() (Box, error) { // 增加30px来计算文本展示区域 textFillWidth := float64(textMaxWidth + 20) - textCount := ceilFloatToInt(float64(top.Width()) / textFillWidth) - unit := ceilFloatToInt(float64(dataCount) / float64(chart.MaxInt(textCount, opt.SplitNumber))) + // 根据文本宽度计算较为符合的展示项 + fitTextCount := ceilFloatToInt(float64(top.Width()) / textFillWidth) + + unit := ceilFloatToInt(float64(dataCount) / float64(fitTextCount)) + unit = chart.MaxInt(unit, opt.SplitNumber) // 偶数 if unit%2 == 0 && dataCount%(unit+1) == 0 { unit++ diff --git a/chart_option.go b/chart_option.go index 39de686..41fda46 100644 --- a/chart_option.go +++ b/chart_option.go @@ -108,9 +108,12 @@ func TitleOptionFunc(title TitleOption) OptionFunc { } // TitleTextOptionFunc set title text of chart -func TitleTextOptionFunc(text string) OptionFunc { +func TitleTextOptionFunc(text string, subtext ...string) OptionFunc { return func(opt *ChartOption) { opt.Title.Text = text + if len(subtext) != 0 { + opt.Title.Subtext = subtext[0] + } } } diff --git a/legend.go b/legend.go index 8f21afb..035642c 100644 --- a/legend.go +++ b/legend.go @@ -232,7 +232,7 @@ func (l *legendPainter) Render() (Box, error) { x0 += measureList[index].Width() if opt.Align == AlignRight { x0 += textOffset - x0 = drawIcon(0, x0) + x0 = drawIcon(y0, x0) } if opt.Orient == OrientVertical { y0 += offset From 1713bc283f6e742cf45cbed65cce010509945130 Mon Sep 17 00:00:00 2001 From: vicanso Date: Tue, 26 Jul 2022 20:45:04 +0800 Subject: [PATCH 11/73] docs: add doc --- start_zh.md | 254 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 254 insertions(+) create mode 100644 start_zh.md diff --git a/start_zh.md b/start_zh.md new file mode 100644 index 0000000..ee8359c --- /dev/null +++ b/start_zh.md @@ -0,0 +1,254 @@ +# go-charts + +`go-charts`主要分为了下几个模块: + +- `标题`:图表的标题,包括主副标题,位置为图表的顶部 +- `图例`:图表的图例列表,用于标识每个图例对应的颜色与名称信息,默认为图表的顶部,可自定义位置 +- `X轴`:图表的x轴,用于折线图、柱状图中,表示每个点对应的时间,位置图表的底部 +- `Y轴`:图表的y轴,用于折线图、柱状图中,最多可使用两组y轴(一左一右),默认位置图表的左侧 +- `内容`: 图表的内容,折线图、柱状图、饼图等,在图表的中间区域 + +## 标题 + +### 常用设置 + +标题一般仅需要设置主副标题即可,其它的属性均会设置默认值,常用的方式是使用`TitleTextOptionFunc`设置,其中副标题为可选值,方式如下: + +```go + charts.TitleTextOptionFunc("Text", "Subtext"), +``` + +### 个性化设置 + +```go +func(opt *charts.ChartOption) { + opt.Title = charts.TitleOption{ + // 主标题 + Text: "Text", + // 副标题 + Subtext: "Subtext", + // 标题左侧位置,可设置为"center","right",数值("20")或百份比("20%") + Left: charts.PositionRight, + // 标题顶部位置,只可调为数值 + Top: "20", + // 主标题文字大小 + FontSize: 14, + // 副标题文字大小 + SubtextFontSize: 12, + // 主标题字体颜色 + FontColor: charts.Color{ + R: 100, + G: 100, + B: 100, + A: 255, + }, + // 副标题字体影响 + SubtextFontColor: charts.Color{ + R: 200, + G: 200, + B: 200, + A: 255, + }, + } +}, +``` + +### 部分属性个性化设置 + +```go +charts.TitleTextOptionFunc("Text", "Subtext"), +func(opt *charts.ChartOption) { + // 修改top的值 + opt.Title.Top = "20" +}, +``` + +## 图例 + +### 常用设置 + +图例组件与图表中的数据一一对应,常用仅设置其名称及左侧的值即可(可选),方式如下: + + +```go +charts.LegendLabelsOptionFunc([]string{ + "Email", + "Union Ads", + "Video Ads", + "Direct", + "Search Engine", +}, "50"), +``` + +### 个性化设置 + +```go +func(opt *charts.ChartOption) { + opt.Legend = charts.LegendOption{ + // 图例名称 + Data: []string{ + "Email", + "Union Ads", + "Video Ads", + "Direct", + "Search Engine", + }, + // 图例左侧位置,可设置为"center","right",数值("20")或百份比("20%") + // 如果示例有多行,只影响第一行,而且对于多行的示例,设置"center", "right"无效 + Left: "50", + // 图例顶部位置,只可调为数值 + Top: "10", + // 图例图标的位置,默认为左侧,只允许左或右 + Align: charts.AlignRight, + // 图例排列方式,默认为水平,只允许水平或垂直 + Orient: charts.OrientVertical, + // 图标类型,提供"rect"与"lineDot"两种类型 + Icon: charts.IconRect, + // 字体大小 + FontSize: 14, + // 字体颜色 + FontColor: charts.Color{ + R: 150, + G: 150, + B: 150, + A: 255, + }, + // 是否展示,如果不需要展示则设置 + // Show: charts.FalseFlag(), + // 图例区域的padding值 + Padding: charts.Box{ + Top: 10, + Left: 10, + }, + } +}, +``` + +### 部分属性个性化设置 + +```go +charts.LegendLabelsOptionFunc([]string{ + "Email", + "Union Ads", + "Video Ads", + "Direct", + "Search Engine", +}, "50"), +func(opt *charts.ChartOption) { + opt.Legend.Top = "10" +}, +``` + +## X轴 + +### 常用设置 + +图表中X轴的展示,常用的设置方式是指定数组即可: + +```go +charts.XAxisDataOptionFunc([]string{ + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + "Sun", +}), +``` + +### 个性化设置 + +```go +func(opt *charts.ChartOption) { + opt.XAxis = charts.XAxisOption{ + // X轴内容 + Data: []string{ + "01", + "02", + "03", + "04", + "05", + "06", + "07", + "08", + "09", + }, + // 如果数据点不居中,则设置为false + BoundaryGap: charts.FalseFlag(), + // 字体大小 + FontSize: 14, + // 是否展示,如果不需要展示则设置 + // Show: charts.FalseFlag(), + // 会根据文本内容以及此值选择适合的分块大小,一般不需要设置 + // SplitNumber: 3, + // 线条颜色 + StrokeColor: charts.Color{ + R: 200, + G: 200, + B: 200, + A: 255, + }, + // 文字颜色 + FontColor: charts.Color{ + R: 100, + G: 100, + B: 100, + A: 255, + }, + } +}, +``` + +### 部分属性个性化设置 + +```go +charts.XAxisDataOptionFunc([]string{ + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + "Sun", +}), +func(opt *charts.ChartOption) { + opt.XAxis.FontColor = charts.Color{ + R: 100, + G: 100, + B: 100, + A: 255, + }, +}, +``` + +## Y轴 + +图表中的y轴展示的相关数据会根据图表中的数据自动生成适合的值,如果需要自定义,则可自定义以下部分数据: + +```go +func(opt *charts.ChartOption) { + opt.YAxisOptions = []charts.YAxisOption{ + { + // 字体大小 + FontSize: 16, + // 字体颜色 + FontColor: charts.Color{ + R: 100, + G: 100, + B: 100, + A: 255, + }, + // 内容,{value}会替换为对应的值 + Formatter: "{value} ml", + // Y轴颜色,如果设置此值,会覆盖font color + Color: charts.Color{ + R: 255, + G: 0, + B: 0, + A: 255, + }, + }, + } +}, +``` From e095223705464e4724ca3fb29512def0daecde55 Mon Sep 17 00:00:00 2001 From: vicanso Date: Wed, 27 Jul 2022 20:27:49 +0800 Subject: [PATCH 12/73] fix: fix font setting for title, #15 --- .gitignore | 1 + chart_option.go | 3 +++ examples/chinese/main.go | 5 +++-- theme.go | 39 +++++++++++++++++++++++++++++++++++++-- 4 files changed, 44 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 2e33342..4a7b0d9 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ *.png *.svg tmp +NotoSansSC.ttf diff --git a/chart_option.go b/chart_option.go index 41fda46..cb3bd3f 100644 --- a/chart_option.go +++ b/chart_option.go @@ -259,6 +259,9 @@ func (o *ChartOption) fillDefault() { if o.font == nil { o.font, _ = chart.GetDefaultFont() + } else { + // 如果指定了字体,则设置主题的字体 + t.SetFont(o.font) } if o.BackgroundColor.IsZero() { o.BackgroundColor = t.GetBackgroundColor() diff --git a/examples/chinese/main.go b/examples/chinese/main.go index bb7cc00..9068a08 100644 --- a/examples/chinese/main.go +++ b/examples/chinese/main.go @@ -25,7 +25,8 @@ func writeFile(buf []byte) error { func main() { // 字体文件需要自行下载 - buf, err := ioutil.ReadFile("../NotoSansSC.ttf") + // https://github.com/googlefonts/noto-cjk + buf, err := ioutil.ReadFile("./NotoSansSC.ttf") if err != nil { panic(err) } @@ -83,7 +84,7 @@ func main() { } p, err := charts.LineRender( values, - charts.TitleTextOptionFunc("Line"), + charts.TitleTextOptionFunc("测试"), charts.FontFamilyOptionFunc("noto"), charts.XAxisDataOptionFunc([]string{ "星期一", diff --git a/theme.go b/theme.go index 31c3bf8..8068687 100644 --- a/theme.go +++ b/theme.go @@ -36,12 +36,19 @@ const ThemeAnt = "ant" type ColorPalette interface { IsDark() bool GetAxisStrokeColor() Color + SetAxisStrokeColor(Color) GetAxisSplitLineColor() Color + SetAxisSplitLineColor(Color) GetSeriesColor(int) Color + SetSeriesColor([]Color) GetBackgroundColor() Color + SetBackgroundColor(Color) GetTextColor() Color + SetTextColor(Color) GetFontSize() float64 + SetFontSize(float64) GetFont() *truetype.Font + SetFont(*truetype.Font) } type themeColorPalette struct { @@ -64,7 +71,7 @@ type ThemeOption struct { SeriesColors []Color } -var palettes = map[string]ColorPalette{} +var palettes = map[string]*themeColorPalette{} const defaultFontSize = 12.0 @@ -241,7 +248,8 @@ func NewTheme(name string) ColorPalette { if !ok { p = palettes[ThemeLight] } - return p + clone := *p + return &clone } func (t *themeColorPalette) IsDark() bool { @@ -252,23 +260,42 @@ func (t *themeColorPalette) GetAxisStrokeColor() Color { return t.axisStrokeColor } +func (t *themeColorPalette) SetAxisStrokeColor(c Color) { + t.axisStrokeColor = c +} + func (t *themeColorPalette) GetAxisSplitLineColor() Color { return t.axisSplitLineColor } +func (t *themeColorPalette) SetAxisSplitLineColor(c Color) { + t.axisSplitLineColor = c +} + func (t *themeColorPalette) GetSeriesColor(index int) Color { colors := t.seriesColors return colors[index%len(colors)] } +func (t *themeColorPalette) SetSeriesColor(colors []Color) { + t.seriesColors = colors +} func (t *themeColorPalette) GetBackgroundColor() Color { return t.backgroundColor } +func (t *themeColorPalette) SetBackgroundColor(c Color) { + t.backgroundColor = c +} + func (t *themeColorPalette) GetTextColor() Color { return t.textColor } +func (t *themeColorPalette) SetTextColor(c Color) { + t.textColor = c +} + func (t *themeColorPalette) GetFontSize() float64 { if t.fontSize != 0 { return t.fontSize @@ -276,6 +303,10 @@ func (t *themeColorPalette) GetFontSize() float64 { return defaultFontSize } +func (t *themeColorPalette) SetFontSize(fontSize float64) { + t.fontSize = fontSize +} + func (t *themeColorPalette) GetFont() *truetype.Font { if t.font != nil { return t.font @@ -283,3 +314,7 @@ func (t *themeColorPalette) GetFont() *truetype.Font { f, _ := chart.GetDefaultFont() return f } + +func (t *themeColorPalette) SetFont(f *truetype.Font) { + t.font = f +} From 817fceff73798bba9450192a3dfa844319d08e8d Mon Sep 17 00:00:00 2001 From: vicanso Date: Wed, 27 Jul 2022 20:32:31 +0800 Subject: [PATCH 13/73] feat: support hide symbol of line chart --- chart_option.go | 2 ++ charts.go | 7 ++++--- examples/line_chart/main.go | 1 + line_chart.go | 6 +++++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/chart_option.go b/chart_option.go index cb3bd3f..71e9dfc 100644 --- a/chart_option.go +++ b/chart_option.go @@ -62,6 +62,8 @@ type ChartOption struct { RadarIndicators []RadarIndicator // The background color of chart BackgroundColor Color + // The flag for show symbol of line, set this to *false will hide symbol + SymbolShow *bool // The child charts Children []ChartOption } diff --git a/charts.go b/charts.go index 36bb17e..92a7e54 100644 --- a/charts.go +++ b/charts.go @@ -377,9 +377,10 @@ func Render(opt ChartOption, opts ...OptionFunc) (*Painter, error) { if len(lineSeriesList) != 0 { handler.Add(func() error { _, err := NewLineChart(p, LineChartOption{ - Theme: opt.theme, - Font: opt.font, - XAxis: opt.XAxis, + Theme: opt.theme, + Font: opt.font, + XAxis: opt.XAxis, + SymbolShow: opt.SymbolShow, }).render(renderResult, lineSeriesList) return err }) diff --git a/examples/line_chart/main.go b/examples/line_chart/main.go index a941bca..5edf65b 100644 --- a/examples/line_chart/main.go +++ b/examples/line_chart/main.go @@ -95,6 +95,7 @@ func main() { Top: 5, Bottom: 10, } + opt.SymbolShow = charts.FalseFlag() }, ) diff --git a/line_chart.go b/line_chart.go index 0770447..dee122f 100644 --- a/line_chart.go +++ b/line_chart.go @@ -60,6 +60,8 @@ type LineChartOption struct { Title TitleOption // The legend option Legend LegendOption + // The flag for show symbol of line, set this to *false will hide symbol + SymbolShow *bool // background is filled backgroundIsFilled bool } @@ -123,7 +125,9 @@ func (l *lineChart) render(result *defaultRenderResult, seriesList SeriesList) ( } drawingStyle.StrokeWidth = 1 seriesPainter.SetDrawingStyle(drawingStyle) - seriesPainter.Dots(points) + if !isFalse(opt.SymbolShow) { + seriesPainter.Dots(points) + } markPointPainter.Add(markPointRenderOption{ FillColor: seriesColor, Font: opt.Font, From e530adccb66738e41d8eb9c7d042e38d3221f869 Mon Sep 17 00:00:00 2001 From: vicanso Date: Thu, 28 Jul 2022 20:49:00 +0800 Subject: [PATCH 14/73] feat: support stroke width of line chart --- chart_option.go | 2 ++ charts.go | 9 +++++---- examples/line_chart/main.go | 1 + line_chart.go | 8 +++++++- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/chart_option.go b/chart_option.go index 71e9dfc..58001bd 100644 --- a/chart_option.go +++ b/chart_option.go @@ -64,6 +64,8 @@ type ChartOption struct { BackgroundColor Color // The flag for show symbol of line, set this to *false will hide symbol SymbolShow *bool + // The stroke width of line chart + LineStrokeWidth float64 // The child charts Children []ChartOption } diff --git a/charts.go b/charts.go index 92a7e54..d65f3c9 100644 --- a/charts.go +++ b/charts.go @@ -377,10 +377,11 @@ func Render(opt ChartOption, opts ...OptionFunc) (*Painter, error) { if len(lineSeriesList) != 0 { handler.Add(func() error { _, err := NewLineChart(p, LineChartOption{ - Theme: opt.theme, - Font: opt.font, - XAxis: opt.XAxis, - SymbolShow: opt.SymbolShow, + Theme: opt.theme, + Font: opt.font, + XAxis: opt.XAxis, + SymbolShow: opt.SymbolShow, + StrokeWidth: opt.LineStrokeWidth, }).render(renderResult, lineSeriesList) return err }) diff --git a/examples/line_chart/main.go b/examples/line_chart/main.go index 5edf65b..36eabee 100644 --- a/examples/line_chart/main.go +++ b/examples/line_chart/main.go @@ -96,6 +96,7 @@ func main() { Bottom: 10, } opt.SymbolShow = charts.FalseFlag() + opt.LineStrokeWidth = 1 }, ) diff --git a/line_chart.go b/line_chart.go index dee122f..3942d70 100644 --- a/line_chart.go +++ b/line_chart.go @@ -62,6 +62,8 @@ type LineChartOption struct { Legend LegendOption // The flag for show symbol of line, set this to *false will hide symbol SymbolShow *bool + // The stroke width of line + StrokeWidth float64 // background is filled backgroundIsFilled bool } @@ -95,12 +97,16 @@ func (l *lineChart) render(result *defaultRenderResult, seriesList SeriesList) ( markPointPainter, markLinePainter, } + strokeWidth := opt.StrokeWidth + if strokeWidth == 0 { + strokeWidth = defaultStrokeWidth + } for index := range seriesList { series := seriesList[index] seriesColor := opt.Theme.GetSeriesColor(series.index) drawingStyle := Style{ StrokeColor: seriesColor, - StrokeWidth: defaultStrokeWidth, + StrokeWidth: strokeWidth, } seriesPainter.SetDrawingStyle(drawingStyle) From 550b9874d23dc966e54248455a6e639c29affd26 Mon Sep 17 00:00:00 2001 From: vicanso Date: Fri, 29 Jul 2022 20:42:13 +0800 Subject: [PATCH 15/73] refactor: remove unused path --- axis.go | 5 ++++- axis_test.go | 4 ++-- bar_chart_test.go | 2 +- chart_option_test.go | 12 ++++++------ charts.go | 5 ++--- echarts_test.go | 2 +- horizontal_bar_chart_test.go | 2 +- line_chart_test.go | 4 ++-- 8 files changed, 19 insertions(+), 17 deletions(-) diff --git a/axis.go b/axis.go index 8e5bfda..3c0484c 100644 --- a/axis.go +++ b/axis.go @@ -265,6 +265,7 @@ func (a *axisPainter) Render() (Box, error) { // 显示辅助线 if opt.SplitLineShow { style.StrokeColor = opt.SplitLineColor + style.StrokeWidth = 1 top.OverrideDrawingStyle(style) if isVertical { x0 := p.Width() @@ -273,7 +274,9 @@ func (a *axisPainter) Render() (Box, error) { x0 = 0 x1 = top.Width() - p.Width() } - for _, y := range autoDivide(height, tickCount) { + yValues := autoDivide(height, tickCount) + yValues = yValues[0 : len(yValues)-1] + for _, y := range yValues { top.LineStroke([]Point{ { X: x0, diff --git a/axis_test.go b/axis_test.go index 17fe8d6..d0cff41 100644 --- a/axis_test.go +++ b/axis_test.go @@ -113,7 +113,7 @@ func TestAxis(t *testing.T) { }).Render() return p.Bytes() }, - result: "\\nMonTueWedThuFriSatSun", + result: "\\nMonTueWedThuFriSatSun", }, // 右侧 { @@ -135,7 +135,7 @@ func TestAxis(t *testing.T) { }).Render() return p.Bytes() }, - result: "\\nMonTueWedThuFriSatSun", + result: "\\nMonTueWedThuFriSatSun", }, // 顶部 { diff --git a/bar_chart_test.go b/bar_chart_test.go index f1bd688..bee0583 100644 --- a/bar_chart_test.go +++ b/bar_chart_test.go @@ -102,7 +102,7 @@ func TestBarChart(t *testing.T) { } return p.Bytes() }, - result: "\\n24020016012080400FebMayAugNov24.9723.225.676.7135.6162.232.6206.43.32.65.9926.428.770.7175.6182.248.718.862.3", + result: "\\n24020016012080400FebMayAugNov24.9723.225.676.7135.6162.232.6206.43.32.65.9926.428.770.7175.6182.248.718.862.3", }, } diff --git a/chart_option_test.go b/chart_option_test.go index 1238422..6f331b3 100644 --- a/chart_option_test.go +++ b/chart_option_test.go @@ -204,7 +204,7 @@ func TestLineRender(t *testing.T) { assert.Nil(err) data, err := p.Bytes() assert.Nil(err) - assert.Equal("\\nEmailUnion AdsVideo AdsDirectSearch EngineLine1.44k1.2k9607204802400MonTueWedThuFriSatSun", string(data)) + assert.Equal("\\nEmailUnion AdsVideo AdsDirectSearch EngineLine1.44k1.2k9607204802400MonTueWedThuFriSatSun", string(data)) } func TestBarRender(t *testing.T) { @@ -277,7 +277,7 @@ func TestBarRender(t *testing.T) { assert.Nil(err) data, err := p.Bytes() assert.Nil(err) - assert.Equal("\\nRainfallEvaporation24020016012080400FebMayAugNov162.22182.22.341.6248.07", string(data)) + assert.Equal("\\nRainfallEvaporation24020016012080400FebMayAugNov162.22182.22.341.6248.07", string(data)) } func TestHorizontalBarRender(t *testing.T) { @@ -326,7 +326,7 @@ func TestHorizontalBarRender(t *testing.T) { assert.Nil(err) data, err := p.Bytes() assert.Nil(err) - assert.Equal("\\n20112012World PopulationWorldChinaIndiaUSAIndonesiaBrazil0122.28k244.56k366.84k489.12k611.4k733.68k", string(data)) + assert.Equal("\\n20112012World PopulationWorldChinaIndiaUSAIndonesiaBrazil0122.28k244.56k366.84k489.12k611.4k733.68k", string(data)) } func TestPieRender(t *testing.T) { @@ -368,7 +368,7 @@ func TestPieRender(t *testing.T) { assert.Nil(err) data, err := p.Bytes() assert.Nil(err) - assert.Equal("\\nSearch EngineDirectEmailUnion AdsVideo AdsRainfall vs EvaporationFake DataSearch Engine: 33.3%Direct: 23.35%Email: 18.43%Union Ads: 15.37%Video Ads: 9.53%", string(data)) + assert.Equal("\\nSearch EngineDirectEmailUnion AdsVideo AdsRainfall vs EvaporationFake DataSearch Engine: 33.3%Direct: 23.35%Email: 18.43%Union Ads: 15.37%Video Ads: 9.53%", string(data)) } func TestRadarRender(t *testing.T) { @@ -419,7 +419,7 @@ func TestRadarRender(t *testing.T) { assert.Nil(err) data, err := p.Bytes() assert.Nil(err) - assert.Equal("\\nAllocated BudgetActual SpendingBasic Radar ChartSalesAdministrationInformation TechnologyCustomer SupportDevelopmentMarketing", string(data)) + assert.Equal("\\nAllocated BudgetActual SpendingBasic Radar ChartSalesAdministrationInformation TechnologyCustomer SupportDevelopmentMarketing", string(data)) } func TestFunnelRender(t *testing.T) { @@ -447,5 +447,5 @@ func TestFunnelRender(t *testing.T) { assert.Nil(err) data, err := p.Bytes() assert.Nil(err) - assert.Equal("\\nShowClickVisitInquiryOrderFunnelShow(100%)Click(80%)Visit(60%)Inquiry(40%)Order(20%)", string(data)) + assert.Equal("\\nShowClickVisitInquiryOrderFunnelShow(100%)Click(80%)Visit(60%)Inquiry(40%)Order(20%)", string(data)) } diff --git a/charts.go b/charts.go index d65f3c9..185e638 100644 --- a/charts.go +++ b/charts.go @@ -316,9 +316,8 @@ func Render(opt ChartOption, opts ...OptionFunc) (*Painter, error) { TitleOption: opt.Title, LegendOption: opt.Legend, axisReversed: axisReversed, - } - if isChild { - renderOpt.backgroundIsFilled = true + // 前置已设置背景色 + backgroundIsFilled: true, } if len(pieSeriesList) != 0 || len(radarSeriesList) != 0 || diff --git a/echarts_test.go b/echarts_test.go index 8deda2d..5c2dbad 100644 --- a/echarts_test.go +++ b/echarts_test.go @@ -578,5 +578,5 @@ func TestRenderEChartsToSVG(t *testing.T) { ] }`) assert.Nil(err) - assert.Equal("\\nRainfallEvaporationRainfall vs EvaporationFake Data24020016012080400FebMayAugNov162.22182.22.341.6248.07", string(data)) + assert.Equal("\\nRainfallEvaporationRainfall vs EvaporationFake Data24020016012080400FebMayAugNov162.22182.22.341.6248.07", string(data)) } diff --git a/horizontal_bar_chart_test.go b/horizontal_bar_chart_test.go index 5555df6..e078c4a 100644 --- a/horizontal_bar_chart_test.go +++ b/horizontal_bar_chart_test.go @@ -83,7 +83,7 @@ func TestHorizontalBarChart(t *testing.T) { } return p.Bytes() }, - result: "\\n20112012World PopulationWorldChinaIndiaUSAIndonesiaBrazil0122.28k244.56k366.84k489.12k611.4k733.68k", + result: "\\n20112012World PopulationWorldChinaIndiaUSAIndonesiaBrazil0122.28k244.56k366.84k489.12k611.4k733.68k", }, } for _, tt := range tests { diff --git a/line_chart_test.go b/line_chart_test.go index 856cdf3..e169f90 100644 --- a/line_chart_test.go +++ b/line_chart_test.go @@ -117,7 +117,7 @@ func TestLineChart(t *testing.T) { } return p.Bytes() }, - result: "\\nEmailUnion AdsVideo AdsDirectSearch EngineLine1.44k1.2k9607204802400MonTueWedThuFriSatSun", + result: "\\nEmailUnion AdsVideo AdsDirectSearch EngineLine1.44k1.2k9607204802400MonTueWedThuFriSatSun", }, { render: func(p *Painter) ([]byte, error) { @@ -201,7 +201,7 @@ func TestLineChart(t *testing.T) { } return p.Bytes() }, - result: "\\nEmailUnion AdsVideo AdsDirectSearch EngineLine1.44k1.2k9607204802400MonTueWedThuFriSatSun", + result: "\\nEmailUnion AdsVideo AdsDirectSearch EngineLine1.44k1.2k9607204802400MonTueWedThuFriSatSun", }, } From 93e03856cac44d574178f85f70fff4d4bc2ac1b3 Mon Sep 17 00:00:00 2001 From: vicanso Date: Wed, 10 Aug 2022 20:39:14 +0800 Subject: [PATCH 16/73] fix: fix NaN of radar chart, #17 --- radar_chart.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/radar_chart.go b/radar_chart.go index eab70d5..429850d 100644 --- a/radar_chart.go +++ b/radar_chart.go @@ -200,7 +200,11 @@ func (r *radarChart) render(result *defaultRenderResult, seriesList SeriesList) continue } indicator := indicators[j] - percent := (item.Value - indicator.Min) / (indicator.Max - indicator.Min) + var percent float64 + offset := indicator.Max - indicator.Min + if offset > 0 { + percent = (item.Value - indicator.Min) / offset + } r := percent * radius p := getPolygonPoint(center, r, angles[j]) linePoints = append(linePoints, p) From dc1a89d3ff8937afc58a4f90a6c935d11aa859ab Mon Sep 17 00:00:00 2001 From: vicanso Date: Thu, 25 Aug 2022 20:19:05 +0800 Subject: [PATCH 17/73] feat: support fill area of line chart --- .gitignore | 1 + chart_option.go | 2 + charts.go | 1 + examples/area_line_chart/main.go | 74 ++++++++++++++++++++++++++++++++ examples/charts/main.go | 31 +++++++++++++ line_chart.go | 22 +++++++++- 6 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 examples/area_line_chart/main.go diff --git a/.gitignore b/.gitignore index 4a7b0d9..57206ee 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ *.svg tmp NotoSansSC.ttf +.vscode \ No newline at end of file diff --git a/chart_option.go b/chart_option.go index 58001bd..93b81ba 100644 --- a/chart_option.go +++ b/chart_option.go @@ -66,6 +66,8 @@ type ChartOption struct { SymbolShow *bool // The stroke width of line chart LineStrokeWidth float64 + // Fill the area of line chart + FillArea bool // The child charts Children []ChartOption } diff --git a/charts.go b/charts.go index 185e638..849f0c7 100644 --- a/charts.go +++ b/charts.go @@ -381,6 +381,7 @@ func Render(opt ChartOption, opts ...OptionFunc) (*Painter, error) { XAxis: opt.XAxis, SymbolShow: opt.SymbolShow, StrokeWidth: opt.LineStrokeWidth, + FillArea: opt.FillArea, }).render(renderResult, lineSeriesList) return err }) diff --git a/examples/area_line_chart/main.go b/examples/area_line_chart/main.go new file mode 100644 index 0000000..7a84df0 --- /dev/null +++ b/examples/area_line_chart/main.go @@ -0,0 +1,74 @@ +package main + +import ( + "io/ioutil" + "os" + "path/filepath" + + "github.com/vicanso/go-charts/v2" +) + +func writeFile(buf []byte) error { + tmpPath := "./tmp" + err := os.MkdirAll(tmpPath, 0700) + if err != nil { + return err + } + + file := filepath.Join(tmpPath, "area-line-chart.png") + err = ioutil.WriteFile(file, buf, 0600) + if err != nil { + return err + } + return nil +} + +func main() { + values := [][]float64{ + { + 120, + 132, + 101, + 134, + 90, + 230, + 210, + }, + } + p, err := charts.LineRender( + values, + charts.TitleTextOptionFunc("Line"), + charts.XAxisDataOptionFunc([]string{ + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + "Sun", + }), + charts.LegendLabelsOptionFunc([]string{ + "Email", + }, "50"), + func(opt *charts.ChartOption) { + opt.Legend.Padding = charts.Box{ + Top: 5, + Bottom: 10, + } + opt.FillArea = true + }, + ) + + if err != nil { + panic(err) + } + + buf, err := p.Bytes() + if err != nil { + panic(err) + } + err = writeFile(buf) + if err != nil { + panic(err) + } +} diff --git a/examples/charts/main.go b/examples/charts/main.go index 7b14919..c3bb486 100644 --- a/examples/charts/main.go +++ b/examples/charts/main.go @@ -2,6 +2,7 @@ package main import ( "bytes" + "fmt" "net/http" "strconv" @@ -261,6 +262,35 @@ func indexHandler(w http.ResponseWriter, req *http.Request) { }, }, }, + { + Title: charts.TitleOption{ + Text: "Line Area", + }, + Legend: charts.NewLegendOption([]string{ + "Email", + }), + XAxis: charts.NewXAxisOption([]string{ + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + "Sun", + }), + SeriesList: []charts.Series{ + charts.NewSeriesFromValues([]float64{ + 120, + 132, + 101, + 134, + 90, + 230, + 210, + }), + }, + FillArea: true, + }, // 柱状图 { Title: charts.TitleOption{ @@ -1935,5 +1965,6 @@ func echartsHandler(w http.ResponseWriter, req *http.Request) { func main() { http.HandleFunc("/", indexHandler) http.HandleFunc("/echarts", echartsHandler) + fmt.Println("http://127.0.0.1:3012/") http.ListenAndServe(":3012", nil) } diff --git a/line_chart.go b/line_chart.go index 3942d70..0b44cdf 100644 --- a/line_chart.go +++ b/line_chart.go @@ -64,6 +64,8 @@ type LineChartOption struct { SymbolShow *bool // The stroke width of line StrokeWidth float64 + // Fill the area of line + FillArea bool // background is filled backgroundIsFilled bool } @@ -109,7 +111,6 @@ func (l *lineChart) render(result *defaultRenderResult, seriesList SeriesList) ( StrokeWidth: strokeWidth, } - seriesPainter.SetDrawingStyle(drawingStyle) yRange := result.axisRanges[series.AxisIndex] points := make([]Point, 0) for i, item := range series.Data { @@ -120,6 +121,25 @@ func (l *lineChart) render(result *defaultRenderResult, seriesList SeriesList) ( } points = append(points, p) } + // 如果需要填充区域 + if opt.FillArea { + areaPoints := make([]Point, len(points)) + copy(areaPoints, points) + bottomY := yRange.getRestHeight(yRange.min) + areaPoints = append(areaPoints, Point{ + X: areaPoints[len(areaPoints)-1].X, + Y: bottomY, + }, Point{ + X: areaPoints[0].X, + Y: bottomY, + }, areaPoints[0]) + seriesPainter.SetDrawingStyle(Style{ + FillColor: seriesColor.WithAlpha(200), + }) + seriesPainter.FillArea(areaPoints) + } + seriesPainter.SetDrawingStyle(drawingStyle) + // 画线 seriesPainter.LineStroke(points) From 128d5b277410e3c8c5a016dfcfade05f4bbd5cfb Mon Sep 17 00:00:00 2001 From: vicanso Date: Sun, 28 Aug 2022 09:43:18 +0800 Subject: [PATCH 18/73] refactor: adjust max value of axis, #19 --- mark_line_test.go | 2 +- range.go | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/mark_line_test.go b/mark_line_test.go index 84152ce..ef29e6f 100644 --- a/mark_line_test.go +++ b/mark_line_test.go @@ -67,7 +67,7 @@ func TestMarkLine(t *testing.T) { } return p.Bytes() }, - result: "\\n321", + result: "\\n321", }, } for _, tt := range tests { diff --git a/range.go b/range.go index 579a77f..ebd0b2d 100644 --- a/range.go +++ b/range.go @@ -60,7 +60,10 @@ func NewRange(opt AxisRangeOption) axisRange { r := math.Abs(max - min) // 最小单位计算 - unit := 2 + unit := 1 + if r > 5 { + unit = 2 + } if r > 10 { unit = 4 } @@ -85,6 +88,10 @@ func NewRange(opt AxisRangeOption) axisRange { } } max = min + float64(unit*divideCount) + expectMax := opt.Max * 2 + if max > expectMax { + max = float64(ceilFloatToInt(expectMax)) + } return axisRange{ divideCount: divideCount, min: min, From 4a1ff8055656382652b49cafc7ee22132884ebcc Mon Sep 17 00:00:00 2001 From: vicanso Date: Thu, 1 Sep 2022 20:20:51 +0800 Subject: [PATCH 19/73] fix: fix min and max option of y axis --- .github/workflows/test.yml | 1 + charts.go | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 22e77a8..61449a3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,6 +14,7 @@ jobs: strategy: matrix: go: + - '1.19' - '1.18' - '1.17' - '1.16' diff --git a/charts.go b/charts.go index 849f0c7..6d5dc56 100644 --- a/charts.go +++ b/charts.go @@ -174,12 +174,6 @@ func defaultRender(p *Painter, opt defaultRenderOption) (*defaultRenderResult, e yAxisOption = opt.YAxisOptions[index] } max, min := opt.SeriesList.GetMaxMin(index) - if yAxisOption.Min != nil { - min = *yAxisOption.Min - } - if yAxisOption.Max != nil { - max = *yAxisOption.Max - } r := NewRange(AxisRangeOption{ Min: min, Max: max, @@ -188,6 +182,12 @@ func defaultRender(p *Painter, opt defaultRenderOption) (*defaultRenderResult, e // 分隔数量 DivideCount: defaultAxisDivideCount, }) + if yAxisOption.Min != nil && *yAxisOption.Min <= min { + r.min = *yAxisOption.Min + } + if yAxisOption.Max != nil && *yAxisOption.Max >= max { + r.max = *yAxisOption.Max + } result.axisRanges[index] = r if yAxisOption.Theme == nil { From bb9af986be59b6c3be100e81ae54a4b713bf0a6c Mon Sep 17 00:00:00 2001 From: vicanso Date: Fri, 2 Sep 2022 20:42:10 +0800 Subject: [PATCH 20/73] chore: update go modules --- go.mod | 4 ++-- go.sum | 11 +++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 66145c7..de0bb9c 100644 --- a/go.mod +++ b/go.mod @@ -5,13 +5,13 @@ go 1.17 require ( github.com/dustin/go-humanize v1.0.0 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 - github.com/stretchr/testify v1.7.2 + github.com/stretchr/testify v1.8.0 github.com/wcharczuk/go-chart/v2 v2.1.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/image v0.0.0-20220617043117-41969df76e82 // indirect + golang.org/x/image v0.0.0-20220902085622-e7cb96979f69 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 5f953b0..e0b1547 100644 --- a/go.sum +++ b/go.sum @@ -8,17 +8,20 @@ github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGw github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= -github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/wcharczuk/go-chart/v2 v2.1.0 h1:tY2slqVQ6bN+yHSnDYwZebLQFkphK4WNrVwnt7CJZ2I= github.com/wcharczuk/go-chart/v2 v2.1.0/go.mod h1:yx7MvAVNcP/kN9lKXM/NTce4au4DFN99j6i1OwDclNA= golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20220617043117-41969df76e82 h1:KpZB5pUSBvrHltNEdK/tw0xlPeD13M6M6aGP32gKqiw= -golang.org/x/image v0.0.0-20220617043117-41969df76e82/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY= +golang.org/x/image v0.0.0-20220902085622-e7cb96979f69 h1:Lj6HJGCSn5AjxRAH2+r35Mir4icalbqku+CLUtjnvXY= +golang.org/x/image v0.0.0-20220902085622-e7cb96979f69/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 50605907c761ba72f14f9f666775b394202195c8 Mon Sep 17 00:00:00 2001 From: vicanso Date: Thu, 15 Sep 2022 20:09:00 +0800 Subject: [PATCH 21/73] feat: support null value for line chart --- charts.go | 13 +++++++++++++ examples/line_chart/main.go | 3 ++- line_chart.go | 5 +++++ painter.go | 9 ++++++++- series.go | 4 ++++ 5 files changed, 32 insertions(+), 2 deletions(-) diff --git a/charts.go b/charts.go index 6d5dc56..f7e52e4 100644 --- a/charts.go +++ b/charts.go @@ -24,6 +24,7 @@ package charts import ( "errors" + "math" "sort" "github.com/wcharczuk/go-chart/v2" @@ -51,6 +52,18 @@ func SetDefaultHeight(height int) { } } +var nullValue = math.MaxFloat64 + +// SetNullValue sets the null value, default is MaxFloat64 +func SetNullValue(v float64) { + nullValue = v +} + +// GetNullValue gets the null value +func GetNullValue() float64 { + return nullValue +} + type Renderer interface { Render() (Box, error) } diff --git a/examples/line_chart/main.go b/examples/line_chart/main.go index 36eabee..97d5859 100644 --- a/examples/line_chart/main.go +++ b/examples/line_chart/main.go @@ -29,7 +29,8 @@ func main() { 120, 132, 101, - 134, + // 134, + charts.GetNullValue(), 90, 230, 210, diff --git a/line_chart.go b/line_chart.go index 0b44cdf..839aa6f 100644 --- a/line_chart.go +++ b/line_chart.go @@ -23,6 +23,8 @@ package charts import ( + "math" + "github.com/golang/freetype/truetype" "github.com/wcharczuk/go-chart/v2/drawing" ) @@ -115,6 +117,9 @@ func (l *lineChart) render(result *defaultRenderResult, seriesList SeriesList) ( points := make([]Point, 0) for i, item := range series.Data { h := yRange.getRestHeight(item.Value) + if item.Value == nullValue { + h = math.MaxInt + } p := Point{ X: xValues[i], Y: h, diff --git a/painter.go b/painter.go index 1a954e2..f172cb3 100644 --- a/painter.go +++ b/painter.go @@ -438,11 +438,18 @@ func (p *Painter) MeasureTextMaxWidthHeight(textList []string) (int, int) { } func (p *Painter) LineStroke(points []Point) *Painter { + shouldMoveTo := false for index, point := range points { x := point.X y := point.Y - if index == 0 { + if y == math.MaxInt { + p.Stroke() + shouldMoveTo = true + continue + } + if shouldMoveTo || index == 0 { p.MoveTo(x, y) + shouldMoveTo = false } else { p.LineTo(x, y) } diff --git a/series.go b/series.go index ea71869..7bd6834 100644 --- a/series.go +++ b/series.go @@ -165,6 +165,10 @@ func (sl SeriesList) GetMaxMin(axisIndex int) (float64, float64) { continue } for _, item := range series.Data { + // 如果为空值,忽略 + if item.Value == nullValue { + continue + } if item.Value > max { max = item.Value } From 825e65d93078aee8ae99916ff916daf7a425a56d Mon Sep 17 00:00:00 2001 From: vicanso Date: Thu, 15 Sep 2022 20:15:05 +0800 Subject: [PATCH 22/73] refactor: use MaxInt32 instead of MaxInt --- line_chart.go | 2 +- painter.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/line_chart.go b/line_chart.go index 839aa6f..cdec280 100644 --- a/line_chart.go +++ b/line_chart.go @@ -118,7 +118,7 @@ func (l *lineChart) render(result *defaultRenderResult, seriesList SeriesList) ( for i, item := range series.Data { h := yRange.getRestHeight(item.Value) if item.Value == nullValue { - h = math.MaxInt + h = int(math.MaxInt32) } p := Point{ X: xValues[i], diff --git a/painter.go b/painter.go index f172cb3..b7122b7 100644 --- a/painter.go +++ b/painter.go @@ -442,7 +442,7 @@ func (p *Painter) LineStroke(points []Point) *Painter { for index, point := range points { x := point.X y := point.Y - if y == math.MaxInt { + if y == int(math.MaxInt32) { p.Stroke() shouldMoveTo = true continue From de49ef8c68ea47881d103d6f650c17ac5a1ea14a Mon Sep 17 00:00:00 2001 From: vicanso Date: Thu, 22 Sep 2022 20:10:45 +0800 Subject: [PATCH 23/73] feat: support label for line chart, #23 --- bar_chart.go | 17 +++------------ line_chart.go | 30 ++++++++++++++++++++++++++ series_label.go | 56 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 14 deletions(-) create mode 100644 series_label.go diff --git a/bar_chart.go b/bar_chart.go index 26f8da5..797f710 100644 --- a/bar_chart.go +++ b/bar_chart.go @@ -62,13 +62,6 @@ type BarChartOption struct { Legend LegendOption } -type barChartLabelRenderOption struct { - Text string - Style Style - X int - Y int -} - func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (Box, error) { p := b.p opt := b.opt @@ -100,11 +93,12 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B markPointPainter := NewMarkPointPainter(seriesPainter) markLinePainter := NewMarkLinePainter(seriesPainter) + labelPainter := NewSeriesLabelPainter(seriesPainter) rendererList := []Renderer{ + labelPainter, markPointPainter, markLinePainter, } - labelRenderOptions := make([]barChartLabelRenderOption, 0) for index := range seriesList { series := seriesList[index] yRange := result.axisRanges[series.AxisIndex] @@ -168,8 +162,7 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B } textBox := seriesPainter.MeasureText(text) - - labelRenderOptions = append(labelRenderOptions, barChartLabelRenderOption{ + labelPainter.Add(LabelValue{ Text: text, Style: labelStyle, X: x + (barWidth-textBox.Width())>>1, @@ -192,10 +185,6 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B Range: yRange, }) } - for _, labelOpt := range labelRenderOptions { - seriesPainter.OverrideTextStyle(labelOpt.Style) - seriesPainter.Text(labelOpt.Text, labelOpt.X, labelOpt.Y) - } // 最大、最小的mark point err := doRender(rendererList...) if err != nil { diff --git a/line_chart.go b/line_chart.go index cdec280..bf39ae2 100644 --- a/line_chart.go +++ b/line_chart.go @@ -97,7 +97,9 @@ func (l *lineChart) render(result *defaultRenderResult, seriesList SeriesList) ( } markPointPainter := NewMarkPointPainter(seriesPainter) markLinePainter := NewMarkLinePainter(seriesPainter) + labelPainter := NewSeriesLabelPainter(seriesPainter) rendererList := []Renderer{ + labelPainter, markPointPainter, markLinePainter, } @@ -105,6 +107,8 @@ func (l *lineChart) render(result *defaultRenderResult, seriesList SeriesList) ( if strokeWidth == 0 { strokeWidth = defaultStrokeWidth } + seriesNames := seriesList.Names() + theme := opt.Theme for index := range seriesList { series := seriesList[index] seriesColor := opt.Theme.GetSeriesColor(series.index) @@ -125,6 +129,32 @@ func (l *lineChart) render(result *defaultRenderResult, seriesList SeriesList) ( Y: h, } points = append(points, p) + + // 如果label不需要展示,则返回 + if !series.Label.Show { + continue + } + distance := series.Label.Distance + if distance == 0 { + distance = 5 + } + text := NewValueLabelFormatter(seriesNames, series.Label.Formatter)(index, item.Value, -1) + labelStyle := Style{ + FontColor: theme.GetTextColor(), + FontSize: labelFontSize, + Font: opt.Font, + } + if !series.Label.Color.IsZero() { + labelStyle.FontColor = series.Label.Color + } + + textBox := seriesPainter.MeasureText(text) + labelPainter.Add(LabelValue{ + Text: text, + Style: labelStyle, + X: p.X - textBox.Width()>>1, + Y: p.Y - distance, + }) } // 如果需要填充区域 if opt.FillArea { diff --git a/series_label.go b/series_label.go new file mode 100644 index 0000000..c1850bb --- /dev/null +++ b/series_label.go @@ -0,0 +1,56 @@ +// MIT License + +// Copyright (c) 2022 Tree Xie + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package charts + +import "github.com/wcharczuk/go-chart/v2" + +type LabelValue struct { + Text string + Style Style + X int + Y int +} + +type SeriesLabelPainter struct { + p *Painter + values []LabelValue +} + +func NewSeriesLabelPainter(p *Painter) *SeriesLabelPainter { + return &SeriesLabelPainter{ + p: p, + values: make([]LabelValue, 0), + } +} + +func (o *SeriesLabelPainter) Add(value LabelValue) { + o.values = append(o.values, value) +} + +func (o *SeriesLabelPainter) Render() (Box, error) { + for _, item := range o.values { + o.p.OverrideTextStyle(item.Style) + o.p.Text(item.Text, item.X, item.Y) + } + return chart.BoxZero, nil +} From 1f5b9d513ee4387b2fc5a0f8a2b3ccb3836ad42f Mon Sep 17 00:00:00 2001 From: vicanso Date: Fri, 23 Sep 2022 20:50:42 +0800 Subject: [PATCH 24/73] refactor: adjust series label render --- bar_chart.go | 39 ++++++++++++--------------- bar_chart_test.go | 2 +- line_chart.go | 39 +++++++++++---------------- series_label.go | 69 ++++++++++++++++++++++++++++++++++++++++------- 4 files changed, 94 insertions(+), 55 deletions(-) diff --git a/bar_chart.go b/bar_chart.go index 797f710..8826ffb 100644 --- a/bar_chart.go +++ b/bar_chart.go @@ -93,9 +93,7 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B markPointPainter := NewMarkPointPainter(seriesPainter) markLinePainter := NewMarkLinePainter(seriesPainter) - labelPainter := NewSeriesLabelPainter(seriesPainter) rendererList := []Renderer{ - labelPainter, markPointPainter, markLinePainter, } @@ -106,6 +104,18 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B divideValues := xRange.AutoDivide() points := make([]Point, len(series.Data)) + var labelPainter *SeriesLabelPainter + if series.Label.Show { + labelPainter = NewSeriesLabelPainter(SeriesLabelPainterParams{ + P: seriesPainter, + SeriesNames: seriesNames, + Label: series.Label, + Theme: opt.Theme, + Font: opt.Font, + }) + rendererList = append(rendererList, labelPainter) + } + for j, item := range series.Data { if j >= xRange.divideCount { continue @@ -144,29 +154,14 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B Y: top, } // 如果label不需要展示,则返回 - if !series.Label.Show { + if labelPainter == nil { continue } - distance := series.Label.Distance - if distance == 0 { - distance = 5 - } - text := NewValueLabelFormatter(seriesNames, series.Label.Formatter)(index, item.Value, -1) - labelStyle := Style{ - FontColor: theme.GetTextColor(), - FontSize: labelFontSize, - Font: opt.Font, - } - if !series.Label.Color.IsZero() { - labelStyle.FontColor = series.Label.Color - } - - textBox := seriesPainter.MeasureText(text) labelPainter.Add(LabelValue{ - Text: text, - Style: labelStyle, - X: x + (barWidth-textBox.Width())>>1, - Y: barMaxHeight - h - distance, + Index: index, + Value: item.Value, + X: x + barWidth>>1, + Y: barMaxHeight - h, }) } diff --git a/bar_chart_test.go b/bar_chart_test.go index bee0583..e1522d6 100644 --- a/bar_chart_test.go +++ b/bar_chart_test.go @@ -102,7 +102,7 @@ func TestBarChart(t *testing.T) { } return p.Bytes() }, - result: "\\n24020016012080400FebMayAugNov24.9723.225.676.7135.6162.232.6206.43.32.65.9926.428.770.7175.6182.248.718.862.3", + result: "\\n24020016012080400FebMayAugNov24.9723.225.676.7135.6162.232.6206.43.32.65.9926.428.770.7175.6182.248.718.862.3", }, } diff --git a/line_chart.go b/line_chart.go index bf39ae2..26f94a4 100644 --- a/line_chart.go +++ b/line_chart.go @@ -97,9 +97,7 @@ func (l *lineChart) render(result *defaultRenderResult, seriesList SeriesList) ( } markPointPainter := NewMarkPointPainter(seriesPainter) markLinePainter := NewMarkLinePainter(seriesPainter) - labelPainter := NewSeriesLabelPainter(seriesPainter) rendererList := []Renderer{ - labelPainter, markPointPainter, markLinePainter, } @@ -108,7 +106,6 @@ func (l *lineChart) render(result *defaultRenderResult, seriesList SeriesList) ( strokeWidth = defaultStrokeWidth } seriesNames := seriesList.Names() - theme := opt.Theme for index := range seriesList { series := seriesList[index] seriesColor := opt.Theme.GetSeriesColor(series.index) @@ -119,6 +116,17 @@ func (l *lineChart) render(result *defaultRenderResult, seriesList SeriesList) ( yRange := result.axisRanges[series.AxisIndex] points := make([]Point, 0) + var labelPainter *SeriesLabelPainter + if series.Label.Show { + labelPainter = NewSeriesLabelPainter(SeriesLabelPainterParams{ + P: seriesPainter, + SeriesNames: seriesNames, + Label: series.Label, + Theme: opt.Theme, + Font: opt.Font, + }) + rendererList = append(rendererList, labelPainter) + } for i, item := range series.Data { h := yRange.getRestHeight(item.Value) if item.Value == nullValue { @@ -131,29 +139,14 @@ func (l *lineChart) render(result *defaultRenderResult, seriesList SeriesList) ( points = append(points, p) // 如果label不需要展示,则返回 - if !series.Label.Show { + if labelPainter == nil { continue } - distance := series.Label.Distance - if distance == 0 { - distance = 5 - } - text := NewValueLabelFormatter(seriesNames, series.Label.Formatter)(index, item.Value, -1) - labelStyle := Style{ - FontColor: theme.GetTextColor(), - FontSize: labelFontSize, - Font: opt.Font, - } - if !series.Label.Color.IsZero() { - labelStyle.FontColor = series.Label.Color - } - - textBox := seriesPainter.MeasureText(text) labelPainter.Add(LabelValue{ - Text: text, - Style: labelStyle, - X: p.X - textBox.Width()>>1, - Y: p.Y - distance, + Index: index, + Value: item.Value, + X: p.X, + Y: p.Y, }) } // 如果需要填充区域 diff --git a/series_label.go b/series_label.go index c1850bb..57bd1bf 100644 --- a/series_label.go +++ b/series_label.go @@ -22,29 +22,80 @@ package charts -import "github.com/wcharczuk/go-chart/v2" +import ( + "github.com/golang/freetype/truetype" + "github.com/wcharczuk/go-chart/v2" +) -type LabelValue struct { +type labelRenderValue struct { Text string Style Style X int Y int } -type SeriesLabelPainter struct { - p *Painter - values []LabelValue +type LabelValue struct { + Index int + Value float64 + X int + Y int } -func NewSeriesLabelPainter(p *Painter) *SeriesLabelPainter { +type SeriesLabelPainter struct { + p *Painter + seriesNames []string + label *SeriesLabel + theme ColorPalette + font *truetype.Font + values []labelRenderValue +} + +type SeriesLabelPainterParams struct { + P *Painter + SeriesNames []string + Label SeriesLabel + Theme ColorPalette + Font *truetype.Font +} + +func NewSeriesLabelPainter(params SeriesLabelPainterParams) *SeriesLabelPainter { return &SeriesLabelPainter{ - p: p, - values: make([]LabelValue, 0), + p: params.P, + seriesNames: params.SeriesNames, + label: ¶ms.Label, + theme: params.Theme, + font: params.Font, + values: make([]labelRenderValue, 0), } } func (o *SeriesLabelPainter) Add(value LabelValue) { - o.values = append(o.values, value) + label := o.label + distance := label.Distance + if distance == 0 { + distance = 5 + } + text := NewValueLabelFormatter(o.seriesNames, label.Formatter)(value.Index, value.Value, -1) + labelStyle := Style{ + FontColor: o.theme.GetTextColor(), + FontSize: labelFontSize, + Font: o.font, + } + if !label.Color.IsZero() { + labelStyle.FontColor = label.Color + } + o.p.OverrideDrawingStyle(labelStyle) + textBox := o.p.MeasureText(text) + renderValue := labelRenderValue{ + Text: text, + Style: labelStyle, + X: value.X - textBox.Width()>>1, + Y: value.Y - distance, + } + if textBox.Width()%2 != 0 { + renderValue.X++ + } + o.values = append(o.values, renderValue) } func (o *SeriesLabelPainter) Render() (Box, error) { From 0a80e7056f69f21d1561f4426877ab7fa376dd4f Mon Sep 17 00:00:00 2001 From: vicanso Date: Wed, 28 Sep 2022 20:29:22 +0800 Subject: [PATCH 25/73] feat: support setting bar width for bar chart, #24 --- bar_chart.go | 10 ++++++++-- chart_option.go | 2 ++ charts.go | 7 ++++--- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/bar_chart.go b/bar_chart.go index 8826ffb..2addd17 100644 --- a/bar_chart.go +++ b/bar_chart.go @@ -59,7 +59,8 @@ type BarChartOption struct { // The option of title Title TitleOption // The legend option - Legend LegendOption + Legend LegendOption + BarWidth int } func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (Box, error) { @@ -86,7 +87,12 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B } seriesCount := len(seriesList) // 总的宽度-两个margin-(总数-1)的barMargin - barWidth := (width - 2*margin - barMargin*(seriesCount-1)) / len(seriesList) + barWidth := (width - 2*margin - barMargin*(seriesCount-1)) / seriesCount + if opt.BarWidth > 0 && opt.BarWidth < barWidth { + barWidth = opt.BarWidth + // 重新计算margin + margin = (width - len(seriesList)*barWidth - barMargin*(seriesCount-1)) / 2 + } barMaxHeight := seriesPainter.Height() theme := opt.Theme seriesNames := seriesList.Names() diff --git a/chart_option.go b/chart_option.go index 93b81ba..447ef52 100644 --- a/chart_option.go +++ b/chart_option.go @@ -66,6 +66,8 @@ type ChartOption struct { SymbolShow *bool // The stroke width of line chart LineStrokeWidth float64 + // The bar with of bar chart + BarWidth int // Fill the area of line chart FillArea bool // The child charts diff --git a/charts.go b/charts.go index f7e52e4..f8c94a3 100644 --- a/charts.go +++ b/charts.go @@ -354,9 +354,10 @@ func Render(opt ChartOption, opts ...OptionFunc) (*Painter, error) { if len(barSeriesList) != 0 { handler.Add(func() error { _, err := NewBarChart(p, BarChartOption{ - Theme: opt.theme, - Font: opt.font, - XAxis: opt.XAxis, + Theme: opt.theme, + Font: opt.font, + XAxis: opt.XAxis, + BarWidth: opt.BarWidth, }).render(renderResult, barSeriesList) return err }) From 6652ece0fed83a33ea6dbe7bd0d99c8b14e945bc Mon Sep 17 00:00:00 2001 From: vicanso Date: Thu, 29 Sep 2022 20:20:54 +0800 Subject: [PATCH 26/73] feat: support bar height for horizontal bar chart --- bar_chart.go | 2 +- chart_option.go | 2 ++ charts.go | 1 + horizontal_bar_chart.go | 9 +++++++-- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/bar_chart.go b/bar_chart.go index 2addd17..d798c07 100644 --- a/bar_chart.go +++ b/bar_chart.go @@ -91,7 +91,7 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B if opt.BarWidth > 0 && opt.BarWidth < barWidth { barWidth = opt.BarWidth // 重新计算margin - margin = (width - len(seriesList)*barWidth - barMargin*(seriesCount-1)) / 2 + margin = (width - seriesCount*barWidth - barMargin*(seriesCount-1)) / 2 } barMaxHeight := seriesPainter.Height() theme := opt.Theme diff --git a/chart_option.go b/chart_option.go index 447ef52..f3bf2cb 100644 --- a/chart_option.go +++ b/chart_option.go @@ -68,6 +68,8 @@ type ChartOption struct { LineStrokeWidth float64 // The bar with of bar chart BarWidth int + // The bar height of horizontal bar chart + BarHeight int // Fill the area of line chart FillArea bool // The child charts diff --git a/charts.go b/charts.go index f8c94a3..c7923f1 100644 --- a/charts.go +++ b/charts.go @@ -369,6 +369,7 @@ func Render(opt ChartOption, opts ...OptionFunc) (*Painter, error) { _, err := NewHorizontalBarChart(p, HorizontalBarChartOption{ Theme: opt.theme, Font: opt.font, + BarHeight: opt.BarHeight, YAxisOptions: opt.YAxisOptions, }).render(renderResult, horizontalBarSeriesList) return err diff --git a/horizontal_bar_chart.go b/horizontal_bar_chart.go index 30a9b7d..8ffac44 100644 --- a/horizontal_bar_chart.go +++ b/horizontal_bar_chart.go @@ -48,7 +48,8 @@ type HorizontalBarChartOption struct { // The option of title Title TitleOption // The legend option - Legend LegendOption + Legend LegendOption + BarHeight int } // NewHorizontalBarChart returns a horizontal bar chart renderer @@ -82,7 +83,11 @@ func (h *horizontalBarChart) render(result *defaultRenderResult, seriesList Seri } seriesCount := len(seriesList) // 总的高度-两个margin-(总数-1)的barMargin - barHeight := (height - 2*margin - barMargin*(seriesCount-1)) / len(seriesList) + barHeight := (height - 2*margin - barMargin*(seriesCount-1)) / seriesCount + if opt.BarHeight > 0 && opt.BarHeight < barHeight { + barHeight = opt.BarHeight + margin = (height - seriesCount*barHeight - barMargin*(seriesCount-1)) / 2 + } theme := opt.Theme From 0a1061a8db90cf3829279afae2c72ca4968b7c8d Mon Sep 17 00:00:00 2001 From: vicanso Date: Tue, 11 Oct 2022 20:17:22 +0800 Subject: [PATCH 27/73] docs: update document --- README_zh.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README_zh.md b/README_zh.md index 87c42fa..c31cf77 100644 --- a/README_zh.md +++ b/README_zh.md @@ -569,7 +569,7 @@ BenchmarkMultiChartSVGRender-8 367 3356325 ns/op 默认使用的字符为`roboto`为英文字体库,因此如果需要显示中文字符需要增加中文字体库,`InstallFont`函数可添加对应的字体库,成功添加之后则指定`title.textStyle.fontFamily`即可。 在浏览器中使用`svg`时,如果指定的`fontFamily`不支持中文字符,展示的中文并不会乱码,但是会导致在计算字符宽度等错误。 -字体文件可以在[中文字库noto-cjk](https://github.com/googlefonts/noto-cjk)下载,注意下载时选择字体格式为 `ttf` 格式,如果选用 `otf` 格式可能会加载失败。 +字体文件可以在[中文字库noto-cjk](https://github.com/googlefonts/noto-cjk)下载,注意下载时选择字体格式为 `ttf` 格式,如果选用 `otf` 格式可能会加载失败,字体尽量选择Bold类型,否则生成的图片会有点模糊。 示例见 [examples/chinese/main.go](examples/chinese/main.go) From 74a47a9858bd82972d25f39ae64bae7783721353 Mon Sep 17 00:00:00 2001 From: vicanso Date: Thu, 20 Oct 2022 20:27:42 +0800 Subject: [PATCH 28/73] refactor: enhance value format, #28 --- util.go | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/util.go b/util.go index a33c6d2..f8a451e 100644 --- a/util.go +++ b/util.go @@ -160,15 +160,25 @@ func NewFloatPoint(f float64) *float64 { v := f return &v } + +const K_VALUE = float64(1000) +const M_VALUE = K_VALUE * K_VALUE +const G_VALUE = M_VALUE * K_VALUE +const T_VALUE = G_VALUE * K_VALUE + func commafWithDigits(value float64) string { decimals := 2 - m := float64(1000 * 1000) - if value >= m { - return humanize.CommafWithDigits(value/m, decimals) + "M" + if value >= T_VALUE { + return humanize.CommafWithDigits(value/T_VALUE, decimals) + "T" } - k := float64(1000) - if value >= k { - return humanize.CommafWithDigits(value/k, decimals) + "k" + if value >= G_VALUE { + return humanize.CommafWithDigits(value/G_VALUE, decimals) + "G" + } + if value >= M_VALUE { + return humanize.CommafWithDigits(value/M_VALUE, decimals) + "M" + } + if value >= K_VALUE { + return humanize.CommafWithDigits(value/K_VALUE, decimals) + "k" } return humanize.CommafWithDigits(value, decimals) } From a88e607bfc83b27502a54879ae749050683c9123 Mon Sep 17 00:00:00 2001 From: vicanso Date: Fri, 21 Oct 2022 20:37:09 +0800 Subject: [PATCH 29/73] refactor: support custom value formatter --- bar_chart.go | 1 + chart_option.go | 2 ++ charts.go | 8 ++++++-- examples/line_chart/main.go | 4 ++++ horizontal_bar_chart.go | 1 + mark_line_test.go | 1 + painter.go | 8 +++++++- range.go | 9 ++++++++- 8 files changed, 30 insertions(+), 4 deletions(-) diff --git a/bar_chart.go b/bar_chart.go index d798c07..19c1664 100644 --- a/bar_chart.go +++ b/bar_chart.go @@ -69,6 +69,7 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B seriesPainter := result.seriesPainter xRange := NewRange(AxisRangeOption{ + Painter: b.p, DivideCount: len(opt.XAxis.Data), Size: seriesPainter.Width(), }) diff --git a/chart_option.go b/chart_option.go index f3bf2cb..d4605a1 100644 --- a/chart_option.go +++ b/chart_option.go @@ -74,6 +74,8 @@ type ChartOption struct { FillArea bool // The child charts Children []ChartOption + // The value formatter + ValueFormatter ValueFormatter } // OptionFunc option function diff --git a/charts.go b/charts.go index c7923f1..b66437c 100644 --- a/charts.go +++ b/charts.go @@ -188,8 +188,9 @@ func defaultRender(p *Painter, opt defaultRenderOption) (*defaultRenderResult, e } max, min := opt.SeriesList.GetMaxMin(index) r := NewRange(AxisRangeOption{ - Min: min, - Max: max, + Painter: p, + Min: min, + Max: max, // 高度需要减去x轴的高度 Size: rangeHeight, // 分隔数量 @@ -287,6 +288,9 @@ func Render(opt ChartOption, opts ...OptionFunc) (*Painter, error) { opt.Parent = p } p := opt.Parent + if opt.ValueFormatter != nil { + p.valueFormatter = opt.ValueFormatter + } if !opt.Box.IsZero() { p = p.Child(PainterBoxOption(opt.Box)) } diff --git a/examples/line_chart/main.go b/examples/line_chart/main.go index 97d5859..c1478a6 100644 --- a/examples/line_chart/main.go +++ b/examples/line_chart/main.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "io/ioutil" "os" "path/filepath" @@ -98,6 +99,9 @@ func main() { } opt.SymbolShow = charts.FalseFlag() opt.LineStrokeWidth = 1 + opt.ValueFormatter = func(f float64) string { + return fmt.Sprintf("%.0f", f) + } }, ) diff --git a/horizontal_bar_chart.go b/horizontal_bar_chart.go index 8ffac44..58c6e19 100644 --- a/horizontal_bar_chart.go +++ b/horizontal_bar_chart.go @@ -93,6 +93,7 @@ func (h *horizontalBarChart) render(result *defaultRenderResult, seriesList Seri max, min := seriesList.GetMaxMin(0) xRange := NewRange(AxisRangeOption{ + Painter: p, Min: min, Max: max, DivideCount: defaultAxisDivideCount, diff --git a/mark_line_test.go b/mark_line_test.go index ef29e6f..00d19ef 100644 --- a/mark_line_test.go +++ b/mark_line_test.go @@ -55,6 +55,7 @@ func TestMarkLine(t *testing.T) { StrokeColor: drawing.ColorBlack, Series: series, Range: NewRange(AxisRangeOption{ + Painter: p, Min: 0, Max: 5, Size: p.Height(), diff --git a/painter.go b/painter.go index b7122b7..efd5045 100644 --- a/painter.go +++ b/painter.go @@ -31,6 +31,8 @@ import ( "github.com/wcharczuk/go-chart/v2" ) +type ValueFormatter func(float64) string + type Painter struct { render chart.Renderer box Box @@ -39,7 +41,8 @@ type Painter struct { style Style theme ColorPalette // 类型 - outputType string + outputType string + valueFormatter ValueFormatter } type PainterOptions struct { @@ -188,6 +191,9 @@ func (p *Painter) setOptions(opts ...PainterOption) { func (p *Painter) Child(opt ...PainterOption) *Painter { child := &Painter{ + // 格式化 + valueFormatter: p.valueFormatter, + // render render: p.render, box: p.box.Clone(), font: p.font, diff --git a/range.go b/range.go index ebd0b2d..51d3332 100644 --- a/range.go +++ b/range.go @@ -29,6 +29,7 @@ import ( const defaultAxisDivideCount = 6 type axisRange struct { + p *Painter divideCount int min float64 max float64 @@ -37,6 +38,7 @@ type axisRange struct { } type AxisRangeOption struct { + Painter *Painter // The min value of axis Min float64 // The max value of axis @@ -93,6 +95,7 @@ func NewRange(opt AxisRangeOption) axisRange { max = float64(ceilFloatToInt(expectMax)) } return axisRange{ + p: opt.Painter, divideCount: divideCount, min: min, max: max, @@ -105,9 +108,13 @@ func NewRange(opt AxisRangeOption) axisRange { func (r axisRange) Values() []string { offset := (r.max - r.min) / float64(r.divideCount) values := make([]string, 0) + formatter := commafWithDigits + if r.p != nil && r.p.valueFormatter != nil { + formatter = r.p.valueFormatter + } for i := 0; i <= r.divideCount; i++ { v := r.min + float64(i)*offset - value := commafWithDigits(v) + value := formatter(v) values = append(values, value) } return values From bdcc871ab194dcaeeae1d934cc088bcffdff5fd2 Mon Sep 17 00:00:00 2001 From: vicanso Date: Thu, 3 Nov 2022 21:31:53 +0800 Subject: [PATCH 30/73] fix: fix series render of horizontal bar, #31 --- axis.go | 15 ++++++++++----- charts.go | 10 +++++++++- examples/horizontal_bar_chart/main.go | 3 +++ yaxis.go | 3 +++ 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/axis.go b/axis.go index 3c0484c..578813c 100644 --- a/axis.go +++ b/axis.go @@ -75,6 +75,7 @@ type AxisOption struct { SplitLineShow bool // The color of split line SplitLineColor Color + Unit int } func (a *axisPainter) Render() (Box, error) { @@ -159,11 +160,15 @@ func (a *axisPainter) Render() (Box, error) { // 根据文本宽度计算较为符合的展示项 fitTextCount := ceilFloatToInt(float64(top.Width()) / textFillWidth) - unit := ceilFloatToInt(float64(dataCount) / float64(fitTextCount)) - unit = chart.MaxInt(unit, opt.SplitNumber) - // 偶数 - if unit%2 == 0 && dataCount%(unit+1) == 0 { - unit++ + unit := opt.Unit + if unit <= 0 { + + unit = ceilFloatToInt(float64(dataCount) / float64(fitTextCount)) + unit = chart.MaxInt(unit, opt.SplitNumber) + // 偶数 + if unit%2 == 0 && dataCount%(unit+1) == 0 { + unit++ + } } width := 0 diff --git a/charts.go b/charts.go index b66437c..d6745d3 100644 --- a/charts.go +++ b/charts.go @@ -186,6 +186,10 @@ func defaultRender(p *Painter, opt defaultRenderOption) (*defaultRenderResult, e if len(opt.YAxisOptions) > index { yAxisOption = opt.YAxisOptions[index] } + divideCount := yAxisOption.DivideCount + if divideCount <= 0 { + divideCount = defaultAxisDivideCount + } max, min := opt.SeriesList.GetMaxMin(index) r := NewRange(AxisRangeOption{ Painter: p, @@ -194,7 +198,7 @@ func defaultRender(p *Painter, opt defaultRenderOption) (*defaultRenderResult, e // 高度需要减去x轴的高度 Size: rangeHeight, // 分隔数量 - DivideCount: defaultAxisDivideCount, + DivideCount: divideCount, }) if yAxisOption.Min != nil && *yAxisOption.Min <= min { r.min = *yAxisOption.Min @@ -346,6 +350,10 @@ func Render(opt ChartOption, opts ...OptionFunc) (*Painter, error) { }, } } + if len(horizontalBarSeriesList) != 0 { + renderOpt.YAxisOptions[0].DivideCount = len(renderOpt.YAxisOptions[0].Data) + renderOpt.YAxisOptions[0].Unit = 1 + } renderResult, err := defaultRender(p, renderOpt) if err != nil { diff --git a/examples/horizontal_bar_chart/main.go b/examples/horizontal_bar_chart/main.go index 8b996b6..a0f5bda 100644 --- a/examples/horizontal_bar_chart/main.go +++ b/examples/horizontal_bar_chart/main.go @@ -26,6 +26,7 @@ func writeFile(buf []byte) error { func main() { values := [][]float64{ { + 8203, 18203, 23489, 29034, @@ -34,6 +35,7 @@ func main() { 630230, }, { + 9325, 19325, 23438, 31000, @@ -56,6 +58,7 @@ func main() { "2012", }), charts.YAxisDataOptionFunc([]string{ + "UN", "Brazil", "Indonesia", "USA", diff --git a/yaxis.go b/yaxis.go index eb9034c..bece2cc 100644 --- a/yaxis.go +++ b/yaxis.go @@ -47,6 +47,8 @@ type YAxisOption struct { Color Color // The flag for show axis, set this to *false will hide axis Show *bool + DivideCount int + Unit int isCategoryAxis bool } @@ -87,6 +89,7 @@ func (opt *YAxisOption) ToAxisOption(p *Painter) AxisOption { SplitLineShow: true, SplitLineColor: theme.GetAxisSplitLineColor(), Show: opt.Show, + Unit: opt.Unit, } if !opt.Color.IsZero() { axisOpt.FontColor = opt.Color From 6f6d6c344730f48b5297bb9695a16f9ecc7874f1 Mon Sep 17 00:00:00 2001 From: vicanso Date: Mon, 7 Nov 2022 20:34:28 +0800 Subject: [PATCH 31/73] fix: fix label render of pie chart, #34 --- pie_chart.go | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/pie_chart.go b/pie_chart.go index 0075ffc..b4714ac 100644 --- a/pie_chart.go +++ b/pie_chart.go @@ -101,8 +101,23 @@ func (p *pieChart) render(result *defaultRenderResult, seriesList SeriesList) (B theme := opt.Theme currentValue := float64(0) - prevEndX := 0 - prevEndY := 0 + prevPoints := make([]Point, 0) + + isOverride := func(x, y int) bool { + for _, p := range prevPoints { + if math.Abs(float64(p.Y-y)) > labelFontSize { + continue + } + // label可能较多内容,不好计算横向占用空间 + // 因此x的位置需要中间位置两侧,否则认为override + if (p.X <= cx && x <= cx) || + (p.X > cx && x > cx) { + return true + } + } + return false + } + for index, v := range values { seriesPainter.OverrideDrawingStyle(Style{ StrokeWidth: 1, @@ -134,13 +149,17 @@ func (p *pieChart) render(result *defaultRenderResult, seriesList SeriesList) (B endx := cx + int(labelRadius*math.Cos(angle)) endy := cy + int(labelRadius*math.Sin(angle)) // 计算是否有重叠,如果有则调整y坐标位置 - if index != 0 && - math.Abs(float64(endx-prevEndX)) < labelFontSize && - math.Abs(float64(endy-prevEndY)) < labelFontSize { + // 最多只尝试5次 + for i := 0; i < 5; i++ { + if !isOverride(endx, endy) { + break + } endy -= (labelFontSize << 1) } - prevEndX = endx - prevEndY = endy + prevPoints = append(prevPoints, Point{ + X: endx, + Y: endy, + }) seriesPainter.MoveTo(startx, starty) seriesPainter.LineTo(endx, endy) From 2ed86a81d018bcf9d0105bf217a9c424aa42bf5e Mon Sep 17 00:00:00 2001 From: vicanso Date: Sat, 12 Nov 2022 10:48:24 +0800 Subject: [PATCH 32/73] fix: fix setting font family for table render --- chart_option.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/chart_option.go b/chart_option.go index d4605a1..3c8ac4b 100644 --- a/chart_option.go +++ b/chart_option.go @@ -384,6 +384,9 @@ func TableOptionRender(opt TableChartOption) (*Painter, error) { if opt.Width <= 0 { opt.Width = defaultChartWidth } + if opt.FontFamily != "" { + opt.Font, _ = GetFont(opt.FontFamily) + } if opt.Font == nil { opt.Font, _ = chart.GetDefaultFont() } From de4250f60bfad7d22847b089bef62f2dce30091b Mon Sep 17 00:00:00 2001 From: vicanso Date: Sat, 12 Nov 2022 20:01:36 +0800 Subject: [PATCH 33/73] feat: support get and set default font --- chart_option.go | 5 ++--- font.go | 19 ++++++++++++++++++- mark_line.go | 3 +-- painter.go | 2 +- painter_test.go | 2 +- theme.go | 3 +-- 6 files changed, 24 insertions(+), 10 deletions(-) diff --git a/chart_option.go b/chart_option.go index 3c8ac4b..ee6851f 100644 --- a/chart_option.go +++ b/chart_option.go @@ -26,7 +26,6 @@ import ( "sort" "github.com/golang/freetype/truetype" - "github.com/wcharczuk/go-chart/v2" ) type ChartOption struct { @@ -270,7 +269,7 @@ func (o *ChartOption) fillDefault() { o.font, _ = GetFont(o.FontFamily) if o.font == nil { - o.font, _ = chart.GetDefaultFont() + o.font, _ = GetDefaultFont() } else { // 如果指定了字体,则设置主题的字体 t.SetFont(o.font) @@ -388,7 +387,7 @@ func TableOptionRender(opt TableChartOption) (*Painter, error) { opt.Font, _ = GetFont(opt.FontFamily) } if opt.Font == nil { - opt.Font, _ = chart.GetDefaultFont() + opt.Font, _ = GetDefaultFont() } p, err := NewPainter(PainterOptions{ diff --git a/font.go b/font.go index c40b51e..dae5141 100644 --- a/font.go +++ b/font.go @@ -32,9 +32,13 @@ import ( var fonts = sync.Map{} var ErrFontNotExists = errors.New("font is not exists") +var defaultFontFamily = "defaultFontFamily" func init() { - _ = InstallFont("roboto", roboto.Roboto) + name := "roboto" + _ = InstallFont(name, roboto.Roboto) + font, _ := GetFont(name) + SetDefaultFont(font) } // InstallFont installs the font for charts @@ -47,6 +51,19 @@ func InstallFont(fontFamily string, data []byte) error { return nil } +// GetDefaultFont get default font +func GetDefaultFont() (*truetype.Font, error) { + return GetFont(defaultFontFamily) +} + +// SetDefaultFont set default font +func SetDefaultFont(font *truetype.Font) { + if font == nil { + return + } + fonts.Store(defaultFontFamily, font) +} + // GetFont get the font by font family func GetFont(fontFamily string) (*truetype.Font, error) { value, ok := fonts.Load(fontFamily) diff --git a/mark_line.go b/mark_line.go index af1062d..bc850bb 100644 --- a/mark_line.go +++ b/mark_line.go @@ -24,7 +24,6 @@ package charts import ( "github.com/golang/freetype/truetype" - "github.com/wcharczuk/go-chart/v2" ) // NewMarkLine returns a series mark line @@ -75,7 +74,7 @@ func (m *markLinePainter) Render() (Box, error) { } font := opt.Font if font == nil { - font, _ = chart.GetDefaultFont() + font, _ = GetDefaultFont() } summary := s.Summary() for _, markLine := range s.MarkLine.Data { diff --git a/painter.go b/painter.go index efd5045..97ad205 100644 --- a/painter.go +++ b/painter.go @@ -149,7 +149,7 @@ func NewPainter(opts PainterOptions, opt ...PainterOption) (*Painter, error) { } font := opts.Font if font == nil { - f, err := chart.GetDefaultFont() + f, err := GetDefaultFont() if err != nil { return nil, err } diff --git a/painter_test.go b/painter_test.go index 96e41ef..2392d5b 100644 --- a/painter_test.go +++ b/painter_test.go @@ -351,7 +351,7 @@ func TestPainterTextFit(t *testing.T) { Type: ChartOutputSVG, }) assert.Nil(err) - f, _ := chart.GetDefaultFont() + f, _ := GetDefaultFont() style := Style{ FontSize: 12, FontColor: chart.ColorBlack, diff --git a/theme.go b/theme.go index 8068687..17706ad 100644 --- a/theme.go +++ b/theme.go @@ -24,7 +24,6 @@ package charts import ( "github.com/golang/freetype/truetype" - "github.com/wcharczuk/go-chart/v2" "github.com/wcharczuk/go-chart/v2/drawing" ) @@ -311,7 +310,7 @@ func (t *themeColorPalette) GetFont() *truetype.Font { if t.font != nil { return t.font } - f, _ := chart.GetDefaultFont() + f, _ := GetDefaultFont() return f } From 7e1f003be85d09216e71a89337634bd38e4abed2 Mon Sep 17 00:00:00 2001 From: vicanso Date: Sat, 12 Nov 2022 20:18:02 +0800 Subject: [PATCH 34/73] refactor: update demo --- examples/chinese/main.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/chinese/main.go b/examples/chinese/main.go index 9068a08..d77216a 100644 --- a/examples/chinese/main.go +++ b/examples/chinese/main.go @@ -34,6 +34,8 @@ func main() { if err != nil { panic(err) } + font, _ := charts.GetFont("noto") + charts.SetDefaultFont(font) values := [][]float64{ { @@ -85,7 +87,6 @@ func main() { p, err := charts.LineRender( values, charts.TitleTextOptionFunc("测试"), - charts.FontFamilyOptionFunc("noto"), charts.XAxisDataOptionFunc([]string{ "星期一", "星期二", From a42d0727df41f5f788ae015c9472a5675bf27774 Mon Sep 17 00:00:00 2001 From: vicanso Date: Tue, 15 Nov 2022 20:09:29 +0800 Subject: [PATCH 35/73] feat: support text rotation --- painter.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/painter.go b/painter.go index 97ad205..6743b37 100644 --- a/painter.go +++ b/painter.go @@ -558,6 +558,12 @@ func (p *Painter) Text(body string, x, y int) *Painter { return p } +func (p *Painter) TextRotation(body string, x, y int, radians float64) { + p.render.SetTextRotation(radians) + p.render.Text(body, x, y) + p.render.ClearTextRotation() +} + func (p *Painter) TextFit(body string, x, y, width int, textAligns ...string) chart.Box { style := p.style textWarp := style.TextWrap From 55eca7b0b9331b660ea1f1c03fab3d803769e815 Mon Sep 17 00:00:00 2001 From: vicanso Date: Wed, 16 Nov 2022 20:46:19 +0800 Subject: [PATCH 36/73] feat: support detect color dark or light --- chart_option_test.go | 2 +- echarts_test.go | 2 +- mark_point.go | 12 +++++------- theme.go | 13 +++++++++++++ util.go | 7 +++++++ util_test.go | 32 ++++++++++++++++++++++++++++++++ 6 files changed, 59 insertions(+), 9 deletions(-) diff --git a/chart_option_test.go b/chart_option_test.go index 6f331b3..ff17750 100644 --- a/chart_option_test.go +++ b/chart_option_test.go @@ -277,7 +277,7 @@ func TestBarRender(t *testing.T) { assert.Nil(err) data, err := p.Bytes() assert.Nil(err) - assert.Equal("\\nRainfallEvaporation24020016012080400FebMayAugNov162.22182.22.341.6248.07", string(data)) + assert.Equal("\\nRainfallEvaporation24020016012080400FebMayAugNov162.22182.22.341.6248.07", string(data)) } func TestHorizontalBarRender(t *testing.T) { diff --git a/echarts_test.go b/echarts_test.go index 5c2dbad..2ce1715 100644 --- a/echarts_test.go +++ b/echarts_test.go @@ -578,5 +578,5 @@ func TestRenderEChartsToSVG(t *testing.T) { ] }`) assert.Nil(err) - assert.Equal("\\nRainfallEvaporationRainfall vs EvaporationFake Data24020016012080400FebMayAugNov162.22182.22.341.6248.07", string(data)) + assert.Equal("\\nRainfallEvaporationRainfall vs EvaporationFake Data24020016012080400FebMayAugNov162.22182.22.341.6248.07", string(data)) } diff --git a/mark_point.go b/mark_point.go index f6c93f3..fd8a88b 100644 --- a/mark_point.go +++ b/mark_point.go @@ -24,7 +24,6 @@ package charts import ( "github.com/golang/freetype/truetype" - "github.com/wcharczuk/go-chart/v2/drawing" ) // NewMarkPoint returns a series mark point @@ -78,16 +77,15 @@ func (m *markPointPainter) Render() (Box, error) { symbolSize = 30 } textStyle := Style{ - FontColor: drawing.Color{ - R: 238, - G: 238, - B: 238, - A: 255, - }, FontSize: labelFontSize, StrokeWidth: 1, Font: opt.Font, } + if isLightColor(opt.FillColor) { + textStyle.FontColor = defaultLightFontColor + } else { + textStyle.FontColor = defaultDarkFontColor + } painter.OverrideDrawingStyle(Style{ FillColor: opt.FillColor, }).OverrideTextStyle(textStyle) diff --git a/theme.go b/theme.go index 17706ad..a6d624f 100644 --- a/theme.go +++ b/theme.go @@ -76,6 +76,19 @@ const defaultFontSize = 12.0 var defaultTheme ColorPalette +var defaultLightFontColor = drawing.Color{ + R: 70, + G: 70, + B: 70, + A: 255, +} +var defaultDarkFontColor = drawing.Color{ + R: 238, + G: 238, + B: 238, + A: 255, +} + func init() { echartSeriesColors := []Color{ parseColor("#5470c6"), diff --git a/util.go b/util.go index f8a451e..b333e6d 100644 --- a/util.go +++ b/util.go @@ -262,3 +262,10 @@ func getPolygonPoints(center Point, radius float64, sides int) []Point { } return points } + +func isLightColor(c Color) bool { + r := float64(c.R) * float64(c.R) * 0.299 + g := float64(c.G) * float64(c.G) * 0.587 + b := float64(c.B) * float64(c.B) * 0.114 + return math.Sqrt(r+g+b) > 127.5 +} diff --git a/util_test.go b/util_test.go index 7c2ab2f..62fd08d 100644 --- a/util_test.go +++ b/util_test.go @@ -189,3 +189,35 @@ func TestParseColor(t *testing.T) { A: 250, }, c) } + +func TestIsLightColor(t *testing.T) { + assert := assert.New(t) + + assert.True(isLightColor(drawing.Color{ + R: 255, + G: 255, + B: 255, + })) + assert.True(isLightColor(drawing.Color{ + R: 145, + G: 204, + B: 117, + })) + + assert.False(isLightColor(drawing.Color{ + R: 88, + G: 112, + B: 198, + })) + + assert.False(isLightColor(drawing.Color{ + R: 0, + G: 0, + B: 0, + })) + assert.False(isLightColor(drawing.Color{ + R: 16, + G: 12, + B: 42, + })) +} From 4fc250aefc0ec4e50099ca483b2a7a3f497b33a8 Mon Sep 17 00:00:00 2001 From: vicanso Date: Tue, 22 Nov 2022 22:41:56 +0800 Subject: [PATCH 37/73] feat: support rotate series label --- bar_chart.go | 19 +++++++++++++++++- examples/charts/main.go | 4 ++++ painter.go | 9 ++++++++- series.go | 2 ++ series_label.go | 44 +++++++++++++++++++++++++++++++---------- 5 files changed, 66 insertions(+), 12 deletions(-) diff --git a/bar_chart.go b/bar_chart.go index 19c1664..695b9fd 100644 --- a/bar_chart.go +++ b/bar_chart.go @@ -23,6 +23,8 @@ package charts import ( + "math" + "github.com/golang/freetype/truetype" "github.com/wcharczuk/go-chart/v2" ) @@ -164,11 +166,26 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B if labelPainter == nil { continue } + y := barMaxHeight - h + radians := float64(0) + var fontColor Color + if series.Label.Position == PositionBottom { + y = barMaxHeight + radians = -math.Pi / 2 + if isLightColor(fillColor) { + fontColor = defaultLightFontColor + } else { + fontColor = defaultDarkFontColor + } + } labelPainter.Add(LabelValue{ Index: index, Value: item.Value, X: x + barWidth>>1, - Y: barMaxHeight - h, + Y: y, + // 旋转 + Radians: radians, + FontColor: fontColor, }) } diff --git a/examples/charts/main.go b/examples/charts/main.go index c3bb486..76aa42c 100644 --- a/examples/charts/main.go +++ b/examples/charts/main.go @@ -355,6 +355,10 @@ func indexHandler(w http.ResponseWriter, req *http.Request) { Value: 180, }, }, + Label: charts.SeriesLabel{ + Show: true, + Position: charts.PositionBottom, + }, }, }, }, diff --git a/painter.go b/painter.go index 6743b37..a0f81ed 100644 --- a/painter.go +++ b/painter.go @@ -560,7 +560,14 @@ func (p *Painter) Text(body string, x, y int) *Painter { func (p *Painter) TextRotation(body string, x, y int, radians float64) { p.render.SetTextRotation(radians) - p.render.Text(body, x, y) + p.render.Text(body, x+p.box.Left, y+p.box.Top) + p.render.ClearTextRotation() +} + +func (p *Painter) SetTextRotation(radians float64) { + p.render.SetTextRotation(radians) +} +func (p *Painter) ClearTextRotation() { p.render.ClearTextRotation() } diff --git a/series.go b/series.go index 7bd6834..373c7dc 100644 --- a/series.go +++ b/series.go @@ -79,6 +79,8 @@ type SeriesLabel struct { Show bool // Distance to the host graphic element. Distance int + // The position of label + Position string } const ( diff --git a/series_label.go b/series_label.go index 57bd1bf..f2dd40f 100644 --- a/series_label.go +++ b/series_label.go @@ -32,6 +32,8 @@ type labelRenderValue struct { Style Style X int Y int + // 旋转 + Radians float64 } type LabelValue struct { @@ -39,6 +41,10 @@ type LabelValue struct { Value float64 X int Y int + // 旋转 + Radians float64 + // 字体颜色 + FontColor Color } type SeriesLabelPainter struct { @@ -81,19 +87,33 @@ func (o *SeriesLabelPainter) Add(value LabelValue) { FontSize: labelFontSize, Font: o.font, } + if !value.FontColor.IsZero() { + label.Color = value.FontColor + } if !label.Color.IsZero() { labelStyle.FontColor = label.Color } - o.p.OverrideDrawingStyle(labelStyle) - textBox := o.p.MeasureText(text) - renderValue := labelRenderValue{ - Text: text, - Style: labelStyle, - X: value.X - textBox.Width()>>1, - Y: value.Y - distance, + p := o.p + p.OverrideDrawingStyle(labelStyle) + rotated := value.Radians != 0 + if rotated { + p.SetTextRotation(value.Radians) } - if textBox.Width()%2 != 0 { - renderValue.X++ + textBox := p.MeasureText(text) + renderValue := labelRenderValue{ + Text: text, + Style: labelStyle, + X: value.X - textBox.Width()>>1, + Y: value.Y - distance, + Radians: value.Radians, + } + if rotated { + renderValue.X = value.X + textBox.Width()>>1 - 1 + p.ClearTextRotation() + } else { + if textBox.Width()%2 != 0 { + renderValue.X++ + } } o.values = append(o.values, renderValue) } @@ -101,7 +121,11 @@ func (o *SeriesLabelPainter) Add(value LabelValue) { func (o *SeriesLabelPainter) Render() (Box, error) { for _, item := range o.values { o.p.OverrideTextStyle(item.Style) - o.p.Text(item.Text, item.X, item.Y) + if item.Radians != 0 { + o.p.TextRotation(item.Text, item.X, item.Y, item.Radians) + } else { + o.p.Text(item.Text, item.X, item.Y) + } } return chart.BoxZero, nil } From 6db8e2c8dc5f5ead957474fddb4af20787b82b95 Mon Sep 17 00:00:00 2001 From: vicanso Date: Wed, 23 Nov 2022 23:01:52 +0800 Subject: [PATCH 38/73] feat: support series label for horizontal bar --- bar_chart.go | 1 + horizontal_bar_chart.go | 41 +++++++++++++++++++++++++++++++++++++++++ series.go | 2 ++ series_label.go | 16 ++++++++++++++-- 4 files changed, 58 insertions(+), 2 deletions(-) diff --git a/bar_chart.go b/bar_chart.go index 695b9fd..8219472 100644 --- a/bar_chart.go +++ b/bar_chart.go @@ -186,6 +186,7 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B // 旋转 Radians: radians, FontColor: fontColor, + Offset: series.Label.Offset, }) } diff --git a/horizontal_bar_chart.go b/horizontal_bar_chart.go index 58c6e19..5e433a6 100644 --- a/horizontal_bar_chart.go +++ b/horizontal_bar_chart.go @@ -99,11 +99,25 @@ func (h *horizontalBarChart) render(result *defaultRenderResult, seriesList Seri DivideCount: defaultAxisDivideCount, Size: seriesPainter.Width(), }) + seriesNames := seriesList.Names() + rendererList := []Renderer{} for index := range seriesList { series := seriesList[index] seriesColor := theme.GetSeriesColor(series.index) divideValues := yRange.AutoDivide() + + var labelPainter *SeriesLabelPainter + if series.Label.Show { + labelPainter = NewSeriesLabelPainter(SeriesLabelPainterParams{ + P: seriesPainter, + SeriesNames: seriesNames, + Label: series.Label, + Theme: opt.Theme, + Font: opt.Font, + }) + rendererList = append(rendererList, labelPainter) + } for j, item := range series.Data { if j >= yRange.divideCount { continue @@ -130,8 +144,35 @@ func (h *horizontalBarChart) render(result *defaultRenderResult, seriesList Seri Right: right, Bottom: y + barHeight, }) + // 如果label不需要展示,则返回 + if labelPainter == nil { + continue + } + x := right + var fontColor Color + if series.Label.Position == PositionLeft { + x = 0 + if isLightColor(fillColor) { + fontColor = defaultLightFontColor + } else { + fontColor = defaultDarkFontColor + } + } + labelPainter.Add(LabelValue{ + Orient: OrientHorizontal, + Index: index, + Value: item.Value, + X: x, + Y: y + barHeight>>1, + FontColor: fontColor, + Offset: series.Label.Offset, + }) } } + err := doRender(rendererList...) + if err != nil { + return BoxZero, err + } return p.box, nil } diff --git a/series.go b/series.go index 373c7dc..c36fa8b 100644 --- a/series.go +++ b/series.go @@ -81,6 +81,8 @@ type SeriesLabel struct { Distance int // The position of label Position string + // The offset of label's position + Offset Box } const ( diff --git a/series_label.go b/series_label.go index f2dd40f..f0fb2ec 100644 --- a/series_label.go +++ b/series_label.go @@ -45,6 +45,8 @@ type LabelValue struct { Radians float64 // 字体颜色 FontColor Color + Orient string + Offset Box } type SeriesLabelPainter struct { @@ -103,10 +105,18 @@ func (o *SeriesLabelPainter) Add(value LabelValue) { renderValue := labelRenderValue{ Text: text, Style: labelStyle, - X: value.X - textBox.Width()>>1, - Y: value.Y - distance, + X: value.X, + Y: value.Y, Radians: value.Radians, } + if value.Orient != OrientHorizontal { + renderValue.X -= textBox.Width() >> 1 + renderValue.Y -= distance + } else { + renderValue.X += distance + renderValue.Y += textBox.Height() >> 1 + renderValue.Y -= 2 + } if rotated { renderValue.X = value.X + textBox.Width()>>1 - 1 p.ClearTextRotation() @@ -115,6 +125,8 @@ func (o *SeriesLabelPainter) Add(value LabelValue) { renderValue.X++ } } + renderValue.X += value.Offset.Left + renderValue.Y += value.Offset.Top o.values = append(o.values, renderValue) } From 5f0aec60d3d3300316bd9d4d1ad58e357f7c4caf Mon Sep 17 00:00:00 2001 From: vicanso Date: Thu, 24 Nov 2022 20:12:19 +0800 Subject: [PATCH 39/73] refactor: adjust label value of horizontal bar --- horizontal_bar_chart.go | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/horizontal_bar_chart.go b/horizontal_bar_chart.go index 5e433a6..1340103 100644 --- a/horizontal_bar_chart.go +++ b/horizontal_bar_chart.go @@ -148,25 +148,23 @@ func (h *horizontalBarChart) render(result *defaultRenderResult, seriesList Seri if labelPainter == nil { continue } - x := right - var fontColor Color + labelValue := LabelValue{ + Orient: OrientHorizontal, + Index: index, + Value: item.Value, + X: right, + Y: y + barHeight>>1, + Offset: series.Label.Offset, + } if series.Label.Position == PositionLeft { - x = 0 + labelValue.X = 0 if isLightColor(fillColor) { - fontColor = defaultLightFontColor + labelValue.FontColor = defaultLightFontColor } else { - fontColor = defaultDarkFontColor + labelValue.FontColor = defaultDarkFontColor } } - labelPainter.Add(LabelValue{ - Orient: OrientHorizontal, - Index: index, - Value: item.Value, - X: x, - Y: y + barHeight>>1, - FontColor: fontColor, - Offset: series.Label.Offset, - }) + labelPainter.Add(labelValue) } } err := doRender(rendererList...) From df6180e59aea0a7d3d45be72a9b49bb4a09df0c9 Mon Sep 17 00:00:00 2001 From: vicanso Date: Mon, 28 Nov 2022 19:55:14 +0800 Subject: [PATCH 40/73] fix: fix zero max value of nan, #37 --- range.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/range.go b/range.go index 51d3332..ec64c2d 100644 --- a/range.go +++ b/range.go @@ -121,6 +121,9 @@ func (r axisRange) Values() []string { } func (r *axisRange) getHeight(value float64) int { + if r.max <= r.min { + return 0 + } v := (value - r.min) / (r.max - r.min) return int(v * float64(r.size)) } From f9a534ea02fe56f1c4ec79c73839664ba8cb51a6 Mon Sep 17 00:00:00 2001 From: vicanso Date: Wed, 7 Dec 2022 19:57:35 +0800 Subject: [PATCH 41/73] fix: fix the color of series label, #37 --- bar_chart.go | 12 +++++++----- horizontal_bar_chart.go | 23 +++++++++++++---------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/bar_chart.go b/bar_chart.go index 8219472..d8a307e 100644 --- a/bar_chart.go +++ b/bar_chart.go @@ -168,14 +168,16 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B } y := barMaxHeight - h radians := float64(0) - var fontColor Color + fontColor := series.Label.Color if series.Label.Position == PositionBottom { y = barMaxHeight radians = -math.Pi / 2 - if isLightColor(fillColor) { - fontColor = defaultLightFontColor - } else { - fontColor = defaultDarkFontColor + if fontColor.IsZero() { + if isLightColor(fillColor) { + fontColor = defaultLightFontColor + } else { + fontColor = defaultDarkFontColor + } } } labelPainter.Add(LabelValue{ diff --git a/horizontal_bar_chart.go b/horizontal_bar_chart.go index 1340103..95d9a3d 100644 --- a/horizontal_bar_chart.go +++ b/horizontal_bar_chart.go @@ -149,19 +149,22 @@ func (h *horizontalBarChart) render(result *defaultRenderResult, seriesList Seri continue } labelValue := LabelValue{ - Orient: OrientHorizontal, - Index: index, - Value: item.Value, - X: right, - Y: y + barHeight>>1, - Offset: series.Label.Offset, + Orient: OrientHorizontal, + Index: index, + Value: item.Value, + X: right, + Y: y + barHeight>>1, + Offset: series.Label.Offset, + FontColor: series.Label.Color, } if series.Label.Position == PositionLeft { labelValue.X = 0 - if isLightColor(fillColor) { - labelValue.FontColor = defaultLightFontColor - } else { - labelValue.FontColor = defaultDarkFontColor + if labelValue.FontColor.IsZero() { + if isLightColor(fillColor) { + labelValue.FontColor = defaultLightFontColor + } else { + labelValue.FontColor = defaultDarkFontColor + } } } labelPainter.Add(labelValue) From ef04ac14abcfe6380464fdc4b3d923448286e198 Mon Sep 17 00:00:00 2001 From: vicanso Date: Fri, 9 Dec 2022 20:08:02 +0800 Subject: [PATCH 42/73] feat: support font size for series label, #38 --- bar_chart.go | 1 + horizontal_bar_chart.go | 1 + line_chart.go | 2 ++ series.go | 2 ++ series_label.go | 9 +++++++-- 5 files changed, 13 insertions(+), 2 deletions(-) diff --git a/bar_chart.go b/bar_chart.go index d8a307e..efeb465 100644 --- a/bar_chart.go +++ b/bar_chart.go @@ -189,6 +189,7 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B Radians: radians, FontColor: fontColor, Offset: series.Label.Offset, + FontSize: series.Label.FontSize, }) } diff --git a/horizontal_bar_chart.go b/horizontal_bar_chart.go index 95d9a3d..2ab4c03 100644 --- a/horizontal_bar_chart.go +++ b/horizontal_bar_chart.go @@ -156,6 +156,7 @@ func (h *horizontalBarChart) render(result *defaultRenderResult, seriesList Seri Y: y + barHeight>>1, Offset: series.Label.Offset, FontColor: series.Label.Color, + FontSize: series.Label.FontSize, } if series.Label.Position == PositionLeft { labelValue.X = 0 diff --git a/line_chart.go b/line_chart.go index 26f94a4..9f350bd 100644 --- a/line_chart.go +++ b/line_chart.go @@ -147,6 +147,8 @@ func (l *lineChart) render(result *defaultRenderResult, seriesList SeriesList) ( Value: item.Value, X: p.X, Y: p.Y, + // 字体大小 + FontSize: series.Label.FontSize, }) } // 如果需要填充区域 diff --git a/series.go b/series.go index c36fa8b..13c637e 100644 --- a/series.go +++ b/series.go @@ -83,6 +83,8 @@ type SeriesLabel struct { Position string // The offset of label's position Offset Box + // The font size of label + FontSize float64 } const ( diff --git a/series_label.go b/series_label.go index f0fb2ec..10fd148 100644 --- a/series_label.go +++ b/series_label.go @@ -45,8 +45,10 @@ type LabelValue struct { Radians float64 // 字体颜色 FontColor Color - Orient string - Offset Box + // 字体大小 + FontSize float64 + Orient string + Offset Box } type SeriesLabelPainter struct { @@ -89,6 +91,9 @@ func (o *SeriesLabelPainter) Add(value LabelValue) { FontSize: labelFontSize, Font: o.font, } + if value.FontSize != 0 { + labelStyle.FontSize = value.FontSize + } if !value.FontColor.IsZero() { label.Color = value.FontColor } From d5533447f565ed55b604a59d0578375c61d496cd Mon Sep 17 00:00:00 2001 From: vicanso Date: Sun, 11 Dec 2022 14:57:05 +0800 Subject: [PATCH 43/73] feat: support text rotation for series label, #38 --- axis.go | 26 ++++++++++++++++++++------ painter.go | 9 +++++++++ xaxis.go | 8 +++++++- 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/axis.go b/axis.go index 578813c..3f71451 100644 --- a/axis.go +++ b/axis.go @@ -75,7 +75,11 @@ type AxisOption struct { SplitLineShow bool // The color of split line SplitLineColor Color - Unit int + // The text rotation of label + TextRotation float64 + // The offset of label + LabelOffset Box + Unit int } func (a *axisPainter) Render() (Box, error) { @@ -153,7 +157,15 @@ func (a *axisPainter) Render() (Box, error) { } top.SetDrawingStyle(style).OverrideTextStyle(style) + isTextRotation := opt.TextRotation != 0 + + if isTextRotation { + top.SetTextRotation(opt.TextRotation) + } textMaxWidth, textMaxHeight := top.MeasureTextMaxWidthHeight(data) + if isTextRotation { + top.ClearTextRotation() + } // 增加30px来计算文本展示区域 textFillWidth := float64(textMaxWidth + 20) @@ -261,11 +273,13 @@ func (a *axisPainter) Render() (Box, error) { Top: labelPaddingTop, Right: labelPaddingRight, })).MultiText(MultiTextOption{ - Align: textAlign, - TextList: data, - Orient: orient, - Unit: unit, - Position: labelPosition, + Align: textAlign, + TextList: data, + Orient: orient, + Unit: unit, + Position: labelPosition, + TextRotation: opt.TextRotation, + Offset: opt.LabelOffset, }) // 显示辅助线 if opt.SplitLineShow { diff --git a/painter.go b/painter.go index a0f81ed..71d205f 100644 --- a/painter.go +++ b/painter.go @@ -71,6 +71,9 @@ type MultiTextOption struct { Unit int Position string Align string + // The text rotation of label + TextRotation float64 + Offset Box } type GridOption struct { @@ -682,10 +685,13 @@ func (p *Painter) MultiText(opt MultiTextOption) *Painter { } else { values = autoDivide(width, count) } + offset := opt.Offset for index, text := range opt.TextList { if opt.Unit != 0 && index%opt.Unit != showIndex { continue } + p.ClearTextRotation() + p.SetTextRotation(opt.TextRotation) box := p.MeasureText(text) start := values[index] if positionCenter { @@ -706,8 +712,11 @@ func (p *Painter) MultiText(opt MultiTextOption) *Painter { } else { x = start - box.Width()>>1 } + x += offset.Left + y += offset.Top p.Text(text, x, y) } + p.ClearTextRotation() return p } diff --git a/xaxis.go b/xaxis.go index 00636a5..95578ff 100644 --- a/xaxis.go +++ b/xaxis.go @@ -47,7 +47,11 @@ type XAxisOption struct { // The line color of axis StrokeColor Color // The color of label - FontColor Color + FontColor Color + // The text rotation of label + TextRotation float64 + // The offset of label + LabelOffset Box isValueAxis bool } @@ -81,6 +85,8 @@ func (opt *XAxisOption) ToAxisOption() AxisOption { FontColor: opt.FontColor, Show: opt.Show, SplitLineColor: opt.Theme.GetAxisSplitLineColor(), + TextRotation: opt.TextRotation, + LabelOffset: opt.LabelOffset, } if opt.isValueAxis { axisOpt.SplitLineShow = true From 830d4bdd21201985bba34404086e7fbdcf8134fd Mon Sep 17 00:00:00 2001 From: vicanso Date: Sun, 11 Dec 2022 14:59:37 +0800 Subject: [PATCH 44/73] fix: fix test for text roration --- painter.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/painter.go b/painter.go index 71d205f..8f43940 100644 --- a/painter.go +++ b/painter.go @@ -685,13 +685,16 @@ func (p *Painter) MultiText(opt MultiTextOption) *Painter { } else { values = autoDivide(width, count) } + isTextRotation := opt.TextRotation != 0 offset := opt.Offset for index, text := range opt.TextList { if opt.Unit != 0 && index%opt.Unit != showIndex { continue } - p.ClearTextRotation() - p.SetTextRotation(opt.TextRotation) + if isTextRotation { + p.ClearTextRotation() + p.SetTextRotation(opt.TextRotation) + } box := p.MeasureText(text) start := values[index] if positionCenter { @@ -716,7 +719,9 @@ func (p *Painter) MultiText(opt MultiTextOption) *Painter { y += offset.Top p.Text(text, x, y) } - p.ClearTextRotation() + if isTextRotation { + p.ClearTextRotation() + } return p } From a767b3e1af4fc0a275b97a68483292ae53445a54 Mon Sep 17 00:00:00 2001 From: Thomas Knierim Date: Mon, 26 Dec 2022 15:06:53 +0700 Subject: [PATCH 45/73] added option for line chart bg fill opacity --- chart_option.go | 2 ++ charts.go | 1 + line_chart.go | 8 +++++++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/chart_option.go b/chart_option.go index ee6851f..5311d50 100644 --- a/chart_option.go +++ b/chart_option.go @@ -71,6 +71,8 @@ type ChartOption struct { BarHeight int // Fill the area of line chart FillArea bool + // background fill (alpha) opacity + Opacity uint8 // The child charts Children []ChartOption // The value formatter diff --git a/charts.go b/charts.go index d6745d3..8613050 100644 --- a/charts.go +++ b/charts.go @@ -409,6 +409,7 @@ func Render(opt ChartOption, opts ...OptionFunc) (*Painter, error) { SymbolShow: opt.SymbolShow, StrokeWidth: opt.LineStrokeWidth, FillArea: opt.FillArea, + Opacity: opt.Opacity, }).render(renderResult, lineSeriesList) return err }) diff --git a/line_chart.go b/line_chart.go index 9f350bd..bdbd38e 100644 --- a/line_chart.go +++ b/line_chart.go @@ -70,6 +70,8 @@ type LineChartOption struct { FillArea bool // background is filled backgroundIsFilled bool + // background fill (alpha) opacity + Opacity uint8 } func (l *lineChart) render(result *defaultRenderResult, seriesList SeriesList) (Box, error) { @@ -156,6 +158,10 @@ func (l *lineChart) render(result *defaultRenderResult, seriesList SeriesList) ( areaPoints := make([]Point, len(points)) copy(areaPoints, points) bottomY := yRange.getRestHeight(yRange.min) + var opacity uint8 = 200 + if opt.Opacity != 0 { + opacity = opt.Opacity + } areaPoints = append(areaPoints, Point{ X: areaPoints[len(areaPoints)-1].X, Y: bottomY, @@ -164,7 +170,7 @@ func (l *lineChart) render(result *defaultRenderResult, seriesList SeriesList) ( Y: bottomY, }, areaPoints[0]) seriesPainter.SetDrawingStyle(Style{ - FillColor: seriesColor.WithAlpha(200), + FillColor: seriesColor.WithAlpha(opacity), }) seriesPainter.FillArea(areaPoints) } From e10175594b517f9a12217478b440faecc8d3c455 Mon Sep 17 00:00:00 2001 From: vicanso Date: Thu, 5 Jan 2023 19:15:58 +0800 Subject: [PATCH 46/73] feat: support label format for funnel chart, #41 --- funnel_chart.go | 8 +++----- series.go | 8 ++++++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/funnel_chart.go b/funnel_chart.go index 719853a..300b539 100644 --- a/funnel_chart.go +++ b/funnel_chart.go @@ -23,9 +23,6 @@ package charts import ( - "fmt" - - "github.com/dustin/go-humanize" "github.com/golang/freetype/truetype" ) @@ -95,13 +92,14 @@ func (f *funnelChart) render(result *defaultRenderResult, seriesList SeriesList) y := 0 widthList := make([]int, len(seriesList)) textList := make([]string, len(seriesList)) + seriesNames := seriesList.Names() for index, item := range seriesList { value := item.Data[0].Value widthPercent := (value - min) / (max - min) w := int(widthPercent * float64(width)) widthList[index] = w - p := humanize.CommafWithDigits(value/max*100, 2) + "%" - textList[index] = fmt.Sprintf("%s(%s)", item.Name, p) + percent := value / max + textList[index] = NewFunnelLabelFormatter(seriesNames, item.Label.Formatter)(index, value, percent) } for index, w := range widthList { diff --git a/series.go b/series.go index 13c637e..f28bfa9 100644 --- a/series.go +++ b/series.go @@ -279,6 +279,14 @@ func NewPieLabelFormatter(seriesNames []string, layout string) LabelFormatter { return NewLabelFormatter(seriesNames, layout) } +// NewFunnelLabelFormatter returns a funner label formatter +func NewFunnelLabelFormatter(seriesNames []string, layout string) LabelFormatter { + if len(layout) == 0 { + layout = "{b}({d})" + } + return NewLabelFormatter(seriesNames, layout) +} + // NewValueLabelFormatter returns a value formatter func NewValueLabelFormatter(seriesNames []string, layout string) LabelFormatter { if len(layout) == 0 { From 8ba9e2e1b207e0ead3826ba28b3bbd15304651e8 Mon Sep 17 00:00:00 2001 From: vicanso Date: Wed, 11 Jan 2023 20:41:16 +0800 Subject: [PATCH 47/73] fix: fix x axis label of horizontal bar chart, #42 --- charts.go | 11 ++++++++++- examples/horizontal_bar_chart/main.go | 28 +++++++++++++-------------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/charts.go b/charts.go index 8613050..74db733 100644 --- a/charts.go +++ b/charts.go @@ -215,7 +215,16 @@ func defaultRender(p *Painter, opt defaultRenderOption) (*defaultRenderResult, e yAxisOption.Data = r.Values() } else { yAxisOption.isCategoryAxis = true - opt.XAxis.Data = r.Values() + // 由于x轴为value部分,因此计算其label单独处理 + opt.XAxis.Data = NewRange(AxisRangeOption{ + Painter: p, + Min: min, + Max: max, + // 高度需要减去x轴的高度 + Size: rangeHeight, + // 分隔数量 + DivideCount: defaultAxisDivideCount, + }).Values() opt.XAxis.isValueAxis = true } reverseStringSlice(yAxisOption.Data) diff --git a/examples/horizontal_bar_chart/main.go b/examples/horizontal_bar_chart/main.go index a0f5bda..a1c50a7 100644 --- a/examples/horizontal_bar_chart/main.go +++ b/examples/horizontal_bar_chart/main.go @@ -26,22 +26,22 @@ func writeFile(buf []byte) error { func main() { values := [][]float64{ { - 8203, - 18203, - 23489, - 29034, - 104970, - 131744, - 630230, + 10, + 30, + 50, + 70, + 90, + 110, + 130, }, { - 9325, - 19325, - 23438, - 31000, - 121594, - 134141, - 681807, + 20, + 40, + 60, + 80, + 100, + 120, + 140, }, } p, err := charts.HorizontalBarRender( From d3f7a773afc152f3be5b6237baa23bd2f40177db Mon Sep 17 00:00:00 2001 From: vicanso Date: Thu, 12 Jan 2023 20:20:36 +0800 Subject: [PATCH 48/73] fix: fix zero value of funnel chart, #43 --- examples/funnel_chart/main.go | 4 ++++ funnel_chart.go | 13 +++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/examples/funnel_chart/main.go b/examples/funnel_chart/main.go index 8f21db6..24f8afe 100644 --- a/examples/funnel_chart/main.go +++ b/examples/funnel_chart/main.go @@ -30,6 +30,8 @@ func main() { 60, 40, 20, + 10, + 0, } p, err := charts.FunnelRender( values, @@ -40,6 +42,8 @@ func main() { "Visit", "Inquiry", "Order", + "Pay", + "Cancel", }), ) if err != nil { diff --git a/funnel_chart.go b/funnel_chart.go index 300b539..d4a8bdd 100644 --- a/funnel_chart.go +++ b/funnel_chart.go @@ -93,12 +93,21 @@ func (f *funnelChart) render(result *defaultRenderResult, seriesList SeriesList) widthList := make([]int, len(seriesList)) textList := make([]string, len(seriesList)) seriesNames := seriesList.Names() + offset := max - min for index, item := range seriesList { value := item.Data[0].Value - widthPercent := (value - min) / (max - min) + // 最大最小值一致则为100% + widthPercent := 100.0 + if offset != 0 { + widthPercent = (value - min) / offset + } w := int(widthPercent * float64(width)) widthList[index] = w - percent := value / max + // 如果最大值为0,则占比100% + percent := 1.0 + if max != 0 { + percent = value / max + } textList[index] = NewFunnelLabelFormatter(seriesNames, item.Label.Formatter)(index, value, percent) } From 29a5ece5458638b95f3d85218f96be56abdadb0d Mon Sep 17 00:00:00 2001 From: vicanso Date: Tue, 14 Feb 2023 20:35:54 +0800 Subject: [PATCH 49/73] chore: update go modules --- .github/workflows/test.yml | 1 + go.mod | 6 +++--- go.sum | 13 ++++++------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 61449a3..f591a3a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,6 +14,7 @@ jobs: strategy: matrix: go: + - '1.20' - '1.19' - '1.18' - '1.17' diff --git a/go.mod b/go.mod index de0bb9c..e265627 100644 --- a/go.mod +++ b/go.mod @@ -3,15 +3,15 @@ module github.com/vicanso/go-charts/v2 go 1.17 require ( - github.com/dustin/go-humanize v1.0.0 + github.com/dustin/go-humanize v1.0.1 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 - github.com/stretchr/testify v1.8.0 + github.com/stretchr/testify v1.8.1 github.com/wcharczuk/go-chart/v2 v2.1.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/image v0.0.0-20220902085622-e7cb96979f69 // indirect + golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index e0b1547..ef2a000 100644 --- a/go.sum +++ b/go.sum @@ -1,25 +1,24 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/wcharczuk/go-chart/v2 v2.1.0 h1:tY2slqVQ6bN+yHSnDYwZebLQFkphK4WNrVwnt7CJZ2I= github.com/wcharczuk/go-chart/v2 v2.1.0/go.mod h1:yx7MvAVNcP/kN9lKXM/NTce4au4DFN99j6i1OwDclNA= +golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 h1:QelT11PB4FXiDEXucrfNckHoFxwt8USGY1ajP1ZF5lM= golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20220902085622-e7cb96979f69 h1:Lj6HJGCSn5AjxRAH2+r35Mir4icalbqku+CLUtjnvXY= -golang.org/x/image v0.0.0-20220902085622-e7cb96979f69/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 20e8d4a078b7b17d373aea3a75e95d4119b5c12f Mon Sep 17 00:00:00 2001 From: vicanso Date: Sat, 25 Feb 2023 14:04:30 +0800 Subject: [PATCH 50/73] feat: support to set the first axis --- axis.go | 4 ++ examples/time_line_chart/main.go | 82 ++++++++++++++++++++++++++++++++ painter.go | 15 +++++- xaxis.go | 3 ++ 4 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 examples/time_line_chart/main.go diff --git a/axis.go b/axis.go index 3f71451..762a6a2 100644 --- a/axis.go +++ b/axis.go @@ -63,6 +63,8 @@ type AxisOption struct { StrokeWidth float64 // The length of the axis tick TickLength int + // The first axis + FirstAxis int // The margin value of label LabelMargin int // The font size of label @@ -255,6 +257,7 @@ func (a *axisPainter) Render() (Box, error) { Length: tickLength, Unit: unit, Orient: orient, + First: opt.FirstAxis, }) p.LineStroke([]Point{ { @@ -273,6 +276,7 @@ func (a *axisPainter) Render() (Box, error) { Top: labelPaddingTop, Right: labelPaddingRight, })).MultiText(MultiTextOption{ + First: opt.FirstAxis, Align: textAlign, TextList: data, Orient: orient, diff --git a/examples/time_line_chart/main.go b/examples/time_line_chart/main.go new file mode 100644 index 0000000..10932cd --- /dev/null +++ b/examples/time_line_chart/main.go @@ -0,0 +1,82 @@ +package main + +import ( + "crypto/rand" + "fmt" + "io/ioutil" + "math/big" + "os" + "path/filepath" + "time" + + "github.com/vicanso/go-charts/v2" +) + +func writeFile(buf []byte) error { + tmpPath := "./tmp" + err := os.MkdirAll(tmpPath, 0700) + if err != nil { + return err + } + + file := filepath.Join(tmpPath, "time-line-chart.png") + err = ioutil.WriteFile(file, buf, 0600) + if err != nil { + return err + } + return nil +} + +func main() { + xAxisValue := []string{} + values := []float64{} + now := time.Now() + firstAxis := 0 + for i := 0; i < 300; i++ { + // 设置首个axis为xx:00的时间点 + if firstAxis == 0 && now.Minute() == 0 { + firstAxis = i + } + xAxisValue = append(xAxisValue, now.Format("15:04")) + now = now.Add(time.Minute) + value, _ := rand.Int(rand.Reader, big.NewInt(100)) + values = append(values, float64(value.Int64())) + } + p, err := charts.LineRender( + [][]float64{ + values, + }, + charts.TitleTextOptionFunc("Line"), + charts.XAxisDataOptionFunc(xAxisValue, charts.FalseFlag()), + charts.LegendLabelsOptionFunc([]string{ + "Demo", + }, "50"), + func(opt *charts.ChartOption) { + opt.XAxis.FirstAxis = firstAxis + // 必须要比计算得来的最小值更大(每60分钟) + opt.XAxis.SplitNumber = 60 + opt.Legend.Padding = charts.Box{ + Top: 5, + Bottom: 10, + } + opt.SymbolShow = charts.FalseFlag() + opt.LineStrokeWidth = 1 + opt.ValueFormatter = func(f float64) string { + return fmt.Sprintf("%.0f", f) + } + }, + ) + + if err != nil { + panic(err) + } + + buf, err := p.Bytes() + if err != nil { + panic(err) + } + err = writeFile(buf) + if err != nil { + panic(err) + } +} diff --git a/painter.go b/painter.go index 8f43940..18496fd 100644 --- a/painter.go +++ b/painter.go @@ -59,6 +59,8 @@ type PainterOptions struct { type PainterOption func(*Painter) type TicksOption struct { + // the first tick + First int Length int Orient string Count int @@ -74,6 +76,8 @@ type MultiTextOption struct { // The text rotation of label TextRotation float64 Offset Box + // The first text index + First int } type GridOption struct { @@ -616,6 +620,7 @@ func (p *Painter) Ticks(opt TicksOption) *Painter { return p } count := opt.Count + first := opt.First width := p.Width() height := p.Height() unit := 1 @@ -630,7 +635,10 @@ func (p *Painter) Ticks(opt TicksOption) *Painter { values = autoDivide(width, count) } for index, value := range values { - if index%unit != 0 { + if index < first { + continue + } + if (index-first)%unit != 0 { continue } if isVertical { @@ -688,7 +696,10 @@ func (p *Painter) MultiText(opt MultiTextOption) *Painter { isTextRotation := opt.TextRotation != 0 offset := opt.Offset for index, text := range opt.TextList { - if opt.Unit != 0 && index%opt.Unit != showIndex { + if index < opt.First { + continue + } + if opt.Unit != 0 && (index-opt.First)%opt.Unit != showIndex { continue } if isTextRotation { diff --git a/xaxis.go b/xaxis.go index 95578ff..61698d7 100644 --- a/xaxis.go +++ b/xaxis.go @@ -50,6 +50,8 @@ type XAxisOption struct { FontColor Color // The text rotation of label TextRotation float64 + // The first axis + FirstAxis int // The offset of label LabelOffset Box isValueAxis bool @@ -87,6 +89,7 @@ func (opt *XAxisOption) ToAxisOption() AxisOption { SplitLineColor: opt.Theme.GetAxisSplitLineColor(), TextRotation: opt.TextRotation, LabelOffset: opt.LabelOffset, + FirstAxis: opt.FirstAxis, } if opt.isValueAxis { axisOpt.SplitLineShow = true From e7a49c2c212d022df6cda4739b8c3e391fd3594a Mon Sep 17 00:00:00 2001 From: Mike Jensen Date: Thu, 4 May 2023 12:52:28 -0600 Subject: [PATCH 51/73] Improvements to how the X Axis is rendered This provides two improvements to how the X Axis is rendered: * The calculation for where a tick should exist has been improved. It now will ensure a tick is always at both the start of the axis and the end of the axis. This makes it clear exactly what data span is captured in the graph. * The second improvement is how the label on the last tick is written. It used to often get partially cut off, and with the change to ensure a tick is always at the end this could be seen more easily. Now the last tick has it's label written to the left so that it can be fully displayed. --- painter.go | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/painter.go b/painter.go index 18496fd..450afdf 100644 --- a/painter.go +++ b/painter.go @@ -615,6 +615,17 @@ func (p *Painter) TextFit(body string, x, y, width int, textAligns ...string) ch return output } +func isTick(totalRange int, numTicks int, index int) bool { + step := float64(totalRange-1) / float64(numTicks-1) + for i := 0; i < numTicks; i++ { + value := float64(i) * step + if int(value + 0.5) == index { + return true + } + } + return false +} + func (p *Painter) Ticks(opt TicksOption) *Painter { if opt.Count <= 0 || opt.Length <= 0 { return p @@ -638,7 +649,7 @@ func (p *Painter) Ticks(opt TicksOption) *Painter { if index < first { continue } - if (index-first)%unit != 0 { + if ! isTick(len(values), unit, index) { continue } if isVertical { @@ -674,15 +685,13 @@ func (p *Painter) MultiText(opt MultiTextOption) *Painter { } count := len(opt.TextList) positionCenter := true - showIndex := opt.Unit / 2 + tickLimit := true if containsString([]string{ PositionLeft, PositionTop, }, opt.Position) { positionCenter = false count-- - // 非居中 - showIndex = 0 } width := p.Width() height := p.Height() @@ -690,6 +699,7 @@ func (p *Painter) MultiText(opt MultiTextOption) *Painter { isVertical := opt.Orient == OrientVertical if isVertical { values = autoDivide(height, count) + tickLimit = false } else { values = autoDivide(width, count) } @@ -699,7 +709,7 @@ func (p *Painter) MultiText(opt MultiTextOption) *Painter { if index < opt.First { continue } - if opt.Unit != 0 && (index-opt.First)%opt.Unit != showIndex { + if opt.Unit != 0 && tickLimit && ! isTick(len(opt.TextList)-opt.First, opt.Unit, index-opt.First) { continue } if isTextRotation { @@ -724,7 +734,11 @@ func (p *Painter) MultiText(opt MultiTextOption) *Painter { x = 0 } } else { - x = start - box.Width()>>1 + if index == len(opt.TextList) - 1 { + x = start - box.Width() + } else { + x = start - box.Width()>>1 + } } x += offset.Left y += offset.Top @@ -749,7 +763,6 @@ func (p *Painter) Grid(opt GridOption) *Painter { x1 := 0 y1 := 0 if isVertical { - x0 = v x1 = v y1 = height From 19173dfd37f737b4a5a681556b0a7fe661d749db Mon Sep 17 00:00:00 2001 From: Mike Jensen Date: Thu, 4 May 2023 17:59:11 -0600 Subject: [PATCH 52/73] painter.go: Optimize isTick function This reduces the loop frequency to one or two iterations in all cases. I have been unable to find any single line equation that can produce this same behavior, but one likely exists. --- painter.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/painter.go b/painter.go index 450afdf..175b2f2 100644 --- a/painter.go +++ b/painter.go @@ -617,10 +617,12 @@ func (p *Painter) TextFit(body string, x, y, width int, textAligns ...string) ch func isTick(totalRange int, numTicks int, index int) bool { step := float64(totalRange-1) / float64(numTicks-1) - for i := 0; i < numTicks; i++ { - value := float64(i) * step - if int(value + 0.5) == index { + for i := int(float64(index) / step); i < numTicks; i++ { + value := int((float64(i) * step) + 0.5) + if value == index { return true + } else if value > index { + break } } return false From c810369730585ed5692c3e5e13c36a4c785568c1 Mon Sep 17 00:00:00 2001 From: Mike Jensen Date: Fri, 5 May 2023 09:44:09 -0600 Subject: [PATCH 53/73] Change ticks to avoid values impacting each other The recently introduced logic has an incorrect understanding of the `unit` parameter. This would result in too many ticks being outputted, particularly as datasets got larger. This fixes it by re-calculating the tick count using the `unit` param as originally intended. --- painter.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/painter.go b/painter.go index 175b2f2..d74b80d 100644 --- a/painter.go +++ b/painter.go @@ -615,7 +615,8 @@ func (p *Painter) TextFit(body string, x, y, width int, textAligns ...string) ch return output } -func isTick(totalRange int, numTicks int, index int) bool { +func isTick(totalRange int, unit int, index int) bool { + numTicks := (totalRange / unit) + 1 step := float64(totalRange-1) / float64(numTicks-1) for i := int(float64(index) / step); i < numTicks; i++ { value := int((float64(i) * step) + 0.5) @@ -737,7 +738,7 @@ func (p *Painter) MultiText(opt MultiTextOption) *Painter { } } else { if index == len(opt.TextList) - 1 { - x = start - box.Width() + x = start - box.Width() + 10 } else { x = start - box.Width()>>1 } From a158191faf90eb0baa08d2cbe8692afd5b09b58c Mon Sep 17 00:00:00 2001 From: Mike Jensen Date: Fri, 5 May 2023 09:55:55 -0600 Subject: [PATCH 54/73] Add `Unit` to XAxis as a publicly visible parameter In some cases the XAxis may have a single long title. This can result in very few increments being shown. In order to be more flexible for those cases this allows the XAxis Tick frequency to be able to be directly controlled. --- axis.go | 1 - xaxis.go | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/axis.go b/axis.go index 762a6a2..af104a8 100644 --- a/axis.go +++ b/axis.go @@ -176,7 +176,6 @@ func (a *axisPainter) Render() (Box, error) { unit := opt.Unit if unit <= 0 { - unit = ceilFloatToInt(float64(dataCount) / float64(fitTextCount)) unit = chart.MaxInt(unit, opt.SplitNumber) // 偶数 diff --git a/xaxis.go b/xaxis.go index 61698d7..5557015 100644 --- a/xaxis.go +++ b/xaxis.go @@ -40,7 +40,7 @@ type XAxisOption struct { FontSize float64 // The flag for show axis, set this to *false will hide axis Show *bool - // Number of segments that the axis is split into. Note that this number serves only as a recommendation. + // Number of segments that the axis is split into. Note that this number serves only as a recommendation to avoid writing overlap. SplitNumber int // The position of axis, it can be 'top' or 'bottom' Position string @@ -55,6 +55,8 @@ type XAxisOption struct { // The offset of label LabelOffset Box isValueAxis bool + // This value overrides SplitNumber, specifying directly the frequency at which the axis is split into, higher numbers result in less ticks + Unit int } const defaultXAxisHeight = 30 @@ -90,6 +92,7 @@ func (opt *XAxisOption) ToAxisOption() AxisOption { TextRotation: opt.TextRotation, LabelOffset: opt.LabelOffset, FirstAxis: opt.FirstAxis, + Unit: opt.Unit, } if opt.isValueAxis { axisOpt.SplitLineShow = true From 687baad0af8ff907e1e317f2671e3d76b91746c1 Mon Sep 17 00:00:00 2001 From: Mike Jensen Date: Fri, 5 May 2023 10:19:01 -0600 Subject: [PATCH 55/73] Unit test fixes Unit tests updated for new tick positions and in a couple cases additional one X axis sample. --- axis_test.go | 6 +++--- bar_chart_test.go | 2 +- chart_option_test.go | 6 +++--- echarts_test.go | 2 +- horizontal_bar_chart_test.go | 2 +- line_chart_test.go | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/axis_test.go b/axis_test.go index d0cff41..a04024d 100644 --- a/axis_test.go +++ b/axis_test.go @@ -53,7 +53,7 @@ func TestAxis(t *testing.T) { }).Render() return p.Bytes() }, - result: "\\nMonTueWedThuFriSatSun", + result: "\\nMonTueWedThuFriSatSun", }, // 底部x轴文本居左 { @@ -72,7 +72,7 @@ func TestAxis(t *testing.T) { }).Render() return p.Bytes() }, - result: "\\nMonTueWedThuFriSatSun", + result: "\\nMonTueWedThuFriSatSun", }, // 左侧y轴 { @@ -155,7 +155,7 @@ func TestAxis(t *testing.T) { }).Render() return p.Bytes() }, - result: "\\nMon --Tue --Wed --Thu --Fri --Sat --Sun --", + result: "\\nMon --Tue --Wed --Thu --Fri --Sat --Sun --", }, } diff --git a/bar_chart_test.go b/bar_chart_test.go index e1522d6..aec6428 100644 --- a/bar_chart_test.go +++ b/bar_chart_test.go @@ -102,7 +102,7 @@ func TestBarChart(t *testing.T) { } return p.Bytes() }, - result: "\\n24020016012080400FebMayAugNov24.9723.225.676.7135.6162.232.6206.43.32.65.9926.428.770.7175.6182.248.718.862.3", + result: "\\n24020016012080400JanAprJulSepDec24.9723.225.676.7135.6162.232.6206.43.32.65.9926.428.770.7175.6182.248.718.862.3", }, } diff --git a/chart_option_test.go b/chart_option_test.go index ff17750..b7f4e93 100644 --- a/chart_option_test.go +++ b/chart_option_test.go @@ -204,7 +204,7 @@ func TestLineRender(t *testing.T) { assert.Nil(err) data, err := p.Bytes() assert.Nil(err) - assert.Equal("\\nEmailUnion AdsVideo AdsDirectSearch EngineLine1.44k1.2k9607204802400MonTueWedThuFriSatSun", string(data)) + assert.Equal("\\nEmailUnion AdsVideo AdsDirectSearch EngineLine1.44k1.2k9607204802400MonTueWedThuFriSatSun", string(data)) } func TestBarRender(t *testing.T) { @@ -277,7 +277,7 @@ func TestBarRender(t *testing.T) { assert.Nil(err) data, err := p.Bytes() assert.Nil(err) - assert.Equal("\\nRainfallEvaporation24020016012080400FebMayAugNov162.22182.22.341.6248.07", string(data)) + assert.Equal("\\nRainfallEvaporation24020016012080400JanAprJulSepDec162.22182.22.341.6248.07", string(data)) } func TestHorizontalBarRender(t *testing.T) { @@ -326,7 +326,7 @@ func TestHorizontalBarRender(t *testing.T) { assert.Nil(err) data, err := p.Bytes() assert.Nil(err) - assert.Equal("\\n20112012World PopulationWorldChinaIndiaUSAIndonesiaBrazil0122.28k244.56k366.84k489.12k611.4k733.68k", string(data)) + assert.Equal("\\n20112012World PopulationWorldChinaIndiaUSAIndonesiaBrazil0122.28k244.56k366.84k489.12k611.4k733.68k", string(data)) } func TestPieRender(t *testing.T) { diff --git a/echarts_test.go b/echarts_test.go index 2ce1715..dd7562f 100644 --- a/echarts_test.go +++ b/echarts_test.go @@ -578,5 +578,5 @@ func TestRenderEChartsToSVG(t *testing.T) { ] }`) assert.Nil(err) - assert.Equal("\\nRainfallEvaporationRainfall vs EvaporationFake Data24020016012080400FebMayAugNov162.22182.22.341.6248.07", string(data)) + assert.Equal("\\nRainfallEvaporationRainfall vs EvaporationFake Data24020016012080400JanAprJulSepDec162.22182.22.341.6248.07", string(data)) } diff --git a/horizontal_bar_chart_test.go b/horizontal_bar_chart_test.go index e078c4a..78f3e69 100644 --- a/horizontal_bar_chart_test.go +++ b/horizontal_bar_chart_test.go @@ -83,7 +83,7 @@ func TestHorizontalBarChart(t *testing.T) { } return p.Bytes() }, - result: "\\n20112012World PopulationWorldChinaIndiaUSAIndonesiaBrazil0122.28k244.56k366.84k489.12k611.4k733.68k", + result: "\\n20112012World PopulationWorldChinaIndiaUSAIndonesiaBrazil0122.28k244.56k366.84k489.12k611.4k733.68k", }, } for _, tt := range tests { diff --git a/line_chart_test.go b/line_chart_test.go index e169f90..e8bc1d7 100644 --- a/line_chart_test.go +++ b/line_chart_test.go @@ -117,7 +117,7 @@ func TestLineChart(t *testing.T) { } return p.Bytes() }, - result: "\\nEmailUnion AdsVideo AdsDirectSearch EngineLine1.44k1.2k9607204802400MonTueWedThuFriSatSun", + result: "\\nEmailUnion AdsVideo AdsDirectSearch EngineLine1.44k1.2k9607204802400MonTueWedThuFriSatSun", }, { render: func(p *Painter) ([]byte, error) { @@ -201,7 +201,7 @@ func TestLineChart(t *testing.T) { } return p.Bytes() }, - result: "\\nEmailUnion AdsVideo AdsDirectSearch EngineLine1.44k1.2k9607204802400MonTueWedThuFriSatSun", + result: "\\nEmailUnion AdsVideo AdsDirectSearch EngineLine1.44k1.2k9607204802400MonTueWedThuFriSatSun", }, } From 0ddb9e4ef1d089a08fdf712377e6d3d7d7396cc4 Mon Sep 17 00:00:00 2001 From: vicanso Date: Fri, 12 May 2023 20:31:42 +0800 Subject: [PATCH 56/73] chore: update modules --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e265627..d8a492c 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.17 require ( github.com/dustin/go-humanize v1.0.1 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 - github.com/stretchr/testify v1.8.1 + github.com/stretchr/testify v1.8.2 github.com/wcharczuk/go-chart/v2 v2.1.0 ) diff --git a/go.sum b/go.sum index ef2a000..ac1d9f7 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,8 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/wcharczuk/go-chart/v2 v2.1.0 h1:tY2slqVQ6bN+yHSnDYwZebLQFkphK4WNrVwnt7CJZ2I= github.com/wcharczuk/go-chart/v2 v2.1.0/go.mod h1:yx7MvAVNcP/kN9lKXM/NTce4au4DFN99j6i1OwDclNA= golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 h1:QelT11PB4FXiDEXucrfNckHoFxwt8USGY1ajP1ZF5lM= From 8bcb584abac4fe128bd83103a9b3a04a01db0346 Mon Sep 17 00:00:00 2001 From: Tree Xie Date: Wed, 27 Dec 2023 18:20:55 +0800 Subject: [PATCH 57/73] Revert "Improvements to how the X Axis is rendered" --- axis.go | 1 + axis_test.go | 6 +++--- bar_chart_test.go | 2 +- chart_option_test.go | 6 +++--- echarts_test.go | 2 +- horizontal_bar_chart_test.go | 2 +- line_chart_test.go | 4 ++-- painter.go | 30 +++++++----------------------- xaxis.go | 5 +---- 9 files changed, 20 insertions(+), 38 deletions(-) diff --git a/axis.go b/axis.go index af104a8..762a6a2 100644 --- a/axis.go +++ b/axis.go @@ -176,6 +176,7 @@ func (a *axisPainter) Render() (Box, error) { unit := opt.Unit if unit <= 0 { + unit = ceilFloatToInt(float64(dataCount) / float64(fitTextCount)) unit = chart.MaxInt(unit, opt.SplitNumber) // 偶数 diff --git a/axis_test.go b/axis_test.go index a04024d..d0cff41 100644 --- a/axis_test.go +++ b/axis_test.go @@ -53,7 +53,7 @@ func TestAxis(t *testing.T) { }).Render() return p.Bytes() }, - result: "\\nMonTueWedThuFriSatSun", + result: "\\nMonTueWedThuFriSatSun", }, // 底部x轴文本居左 { @@ -72,7 +72,7 @@ func TestAxis(t *testing.T) { }).Render() return p.Bytes() }, - result: "\\nMonTueWedThuFriSatSun", + result: "\\nMonTueWedThuFriSatSun", }, // 左侧y轴 { @@ -155,7 +155,7 @@ func TestAxis(t *testing.T) { }).Render() return p.Bytes() }, - result: "\\nMon --Tue --Wed --Thu --Fri --Sat --Sun --", + result: "\\nMon --Tue --Wed --Thu --Fri --Sat --Sun --", }, } diff --git a/bar_chart_test.go b/bar_chart_test.go index aec6428..e1522d6 100644 --- a/bar_chart_test.go +++ b/bar_chart_test.go @@ -102,7 +102,7 @@ func TestBarChart(t *testing.T) { } return p.Bytes() }, - result: "\\n24020016012080400JanAprJulSepDec24.9723.225.676.7135.6162.232.6206.43.32.65.9926.428.770.7175.6182.248.718.862.3", + result: "\\n24020016012080400FebMayAugNov24.9723.225.676.7135.6162.232.6206.43.32.65.9926.428.770.7175.6182.248.718.862.3", }, } diff --git a/chart_option_test.go b/chart_option_test.go index b7f4e93..ff17750 100644 --- a/chart_option_test.go +++ b/chart_option_test.go @@ -204,7 +204,7 @@ func TestLineRender(t *testing.T) { assert.Nil(err) data, err := p.Bytes() assert.Nil(err) - assert.Equal("\\nEmailUnion AdsVideo AdsDirectSearch EngineLine1.44k1.2k9607204802400MonTueWedThuFriSatSun", string(data)) + assert.Equal("\\nEmailUnion AdsVideo AdsDirectSearch EngineLine1.44k1.2k9607204802400MonTueWedThuFriSatSun", string(data)) } func TestBarRender(t *testing.T) { @@ -277,7 +277,7 @@ func TestBarRender(t *testing.T) { assert.Nil(err) data, err := p.Bytes() assert.Nil(err) - assert.Equal("\\nRainfallEvaporation24020016012080400JanAprJulSepDec162.22182.22.341.6248.07", string(data)) + assert.Equal("\\nRainfallEvaporation24020016012080400FebMayAugNov162.22182.22.341.6248.07", string(data)) } func TestHorizontalBarRender(t *testing.T) { @@ -326,7 +326,7 @@ func TestHorizontalBarRender(t *testing.T) { assert.Nil(err) data, err := p.Bytes() assert.Nil(err) - assert.Equal("\\n20112012World PopulationWorldChinaIndiaUSAIndonesiaBrazil0122.28k244.56k366.84k489.12k611.4k733.68k", string(data)) + assert.Equal("\\n20112012World PopulationWorldChinaIndiaUSAIndonesiaBrazil0122.28k244.56k366.84k489.12k611.4k733.68k", string(data)) } func TestPieRender(t *testing.T) { diff --git a/echarts_test.go b/echarts_test.go index dd7562f..2ce1715 100644 --- a/echarts_test.go +++ b/echarts_test.go @@ -578,5 +578,5 @@ func TestRenderEChartsToSVG(t *testing.T) { ] }`) assert.Nil(err) - assert.Equal("\\nRainfallEvaporationRainfall vs EvaporationFake Data24020016012080400JanAprJulSepDec162.22182.22.341.6248.07", string(data)) + assert.Equal("\\nRainfallEvaporationRainfall vs EvaporationFake Data24020016012080400FebMayAugNov162.22182.22.341.6248.07", string(data)) } diff --git a/horizontal_bar_chart_test.go b/horizontal_bar_chart_test.go index 78f3e69..e078c4a 100644 --- a/horizontal_bar_chart_test.go +++ b/horizontal_bar_chart_test.go @@ -83,7 +83,7 @@ func TestHorizontalBarChart(t *testing.T) { } return p.Bytes() }, - result: "\\n20112012World PopulationWorldChinaIndiaUSAIndonesiaBrazil0122.28k244.56k366.84k489.12k611.4k733.68k", + result: "\\n20112012World PopulationWorldChinaIndiaUSAIndonesiaBrazil0122.28k244.56k366.84k489.12k611.4k733.68k", }, } for _, tt := range tests { diff --git a/line_chart_test.go b/line_chart_test.go index e8bc1d7..e169f90 100644 --- a/line_chart_test.go +++ b/line_chart_test.go @@ -117,7 +117,7 @@ func TestLineChart(t *testing.T) { } return p.Bytes() }, - result: "\\nEmailUnion AdsVideo AdsDirectSearch EngineLine1.44k1.2k9607204802400MonTueWedThuFriSatSun", + result: "\\nEmailUnion AdsVideo AdsDirectSearch EngineLine1.44k1.2k9607204802400MonTueWedThuFriSatSun", }, { render: func(p *Painter) ([]byte, error) { @@ -201,7 +201,7 @@ func TestLineChart(t *testing.T) { } return p.Bytes() }, - result: "\\nEmailUnion AdsVideo AdsDirectSearch EngineLine1.44k1.2k9607204802400MonTueWedThuFriSatSun", + result: "\\nEmailUnion AdsVideo AdsDirectSearch EngineLine1.44k1.2k9607204802400MonTueWedThuFriSatSun", }, } diff --git a/painter.go b/painter.go index d74b80d..18496fd 100644 --- a/painter.go +++ b/painter.go @@ -615,20 +615,6 @@ func (p *Painter) TextFit(body string, x, y, width int, textAligns ...string) ch return output } -func isTick(totalRange int, unit int, index int) bool { - numTicks := (totalRange / unit) + 1 - step := float64(totalRange-1) / float64(numTicks-1) - for i := int(float64(index) / step); i < numTicks; i++ { - value := int((float64(i) * step) + 0.5) - if value == index { - return true - } else if value > index { - break - } - } - return false -} - func (p *Painter) Ticks(opt TicksOption) *Painter { if opt.Count <= 0 || opt.Length <= 0 { return p @@ -652,7 +638,7 @@ func (p *Painter) Ticks(opt TicksOption) *Painter { if index < first { continue } - if ! isTick(len(values), unit, index) { + if (index-first)%unit != 0 { continue } if isVertical { @@ -688,13 +674,15 @@ func (p *Painter) MultiText(opt MultiTextOption) *Painter { } count := len(opt.TextList) positionCenter := true - tickLimit := true + showIndex := opt.Unit / 2 if containsString([]string{ PositionLeft, PositionTop, }, opt.Position) { positionCenter = false count-- + // 非居中 + showIndex = 0 } width := p.Width() height := p.Height() @@ -702,7 +690,6 @@ func (p *Painter) MultiText(opt MultiTextOption) *Painter { isVertical := opt.Orient == OrientVertical if isVertical { values = autoDivide(height, count) - tickLimit = false } else { values = autoDivide(width, count) } @@ -712,7 +699,7 @@ func (p *Painter) MultiText(opt MultiTextOption) *Painter { if index < opt.First { continue } - if opt.Unit != 0 && tickLimit && ! isTick(len(opt.TextList)-opt.First, opt.Unit, index-opt.First) { + if opt.Unit != 0 && (index-opt.First)%opt.Unit != showIndex { continue } if isTextRotation { @@ -737,11 +724,7 @@ func (p *Painter) MultiText(opt MultiTextOption) *Painter { x = 0 } } else { - if index == len(opt.TextList) - 1 { - x = start - box.Width() + 10 - } else { - x = start - box.Width()>>1 - } + x = start - box.Width()>>1 } x += offset.Left y += offset.Top @@ -766,6 +749,7 @@ func (p *Painter) Grid(opt GridOption) *Painter { x1 := 0 y1 := 0 if isVertical { + x0 = v x1 = v y1 = height diff --git a/xaxis.go b/xaxis.go index 5557015..61698d7 100644 --- a/xaxis.go +++ b/xaxis.go @@ -40,7 +40,7 @@ type XAxisOption struct { FontSize float64 // The flag for show axis, set this to *false will hide axis Show *bool - // Number of segments that the axis is split into. Note that this number serves only as a recommendation to avoid writing overlap. + // Number of segments that the axis is split into. Note that this number serves only as a recommendation. SplitNumber int // The position of axis, it can be 'top' or 'bottom' Position string @@ -55,8 +55,6 @@ type XAxisOption struct { // The offset of label LabelOffset Box isValueAxis bool - // This value overrides SplitNumber, specifying directly the frequency at which the axis is split into, higher numbers result in less ticks - Unit int } const defaultXAxisHeight = 30 @@ -92,7 +90,6 @@ func (opt *XAxisOption) ToAxisOption() AxisOption { TextRotation: opt.TextRotation, LabelOffset: opt.LabelOffset, FirstAxis: opt.FirstAxis, - Unit: opt.Unit, } if opt.isValueAxis { axisOpt.SplitLineShow = true From 98af9866a47cd7ac3665588501889601f7900ef4 Mon Sep 17 00:00:00 2001 From: vicanso Date: Wed, 27 Dec 2023 20:33:12 +0800 Subject: [PATCH 58/73] refactor: support label show for radar chart, #62 --- echarts.go | 5 +++++ radar_chart.go | 9 ++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/echarts.go b/echarts.go index fbe9a36..5a0e5a0 100644 --- a/echarts.go +++ b/echarts.go @@ -344,6 +344,11 @@ func (esList EChartsSeriesList) ToSeriesList() SeriesList { Data: NewSeriesDataFromValues(dataItem.Value.values), Max: item.Max, Min: item.Min, + Label: SeriesLabel{ + Color: parseColor(item.Label.Color), + Show: item.Label.Show, + Distance: item.Label.Distance, + }, }) } continue diff --git a/radar_chart.go b/radar_chart.go index 429850d..f3d63b9 100644 --- a/radar_chart.go +++ b/radar_chart.go @@ -25,6 +25,7 @@ package charts import ( "errors" + "github.com/dustin/go-humanize" "github.com/golang/freetype/truetype" "github.com/wcharczuk/go-chart/v2" "github.com/wcharczuk/go-chart/v2/drawing" @@ -230,9 +231,15 @@ func (r *radarChart) render(result *defaultRenderResult, seriesList SeriesList) StrokeColor: color, FillColor: dotFillColor, }) - for _, point := range linePoints { + for index, point := range linePoints { seriesPainter.Circle(dotWith, point.X, point.Y) seriesPainter.FillStroke() + if series.Label.Show && index < len(series.Data) { + value := humanize.FtoaWithDigits(series.Data[index].Value, 2) + b := seriesPainter.MeasureText(value) + seriesPainter.Text(value, point.X-b.Width()/2, point.Y) + } + } } From c2f709a74299db5a1c0290f5d6c07606f0b9f124 Mon Sep 17 00:00:00 2001 From: vicanso Date: Wed, 27 Dec 2023 20:34:05 +0800 Subject: [PATCH 59/73] chore: update modules --- .github/workflows/test.yml | 1 + go.mod | 6 +++--- go.sum | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f591a3a..8d6a027 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,6 +14,7 @@ jobs: strategy: matrix: go: + - '1.21' - '1.20' - '1.19' - '1.18' diff --git a/go.mod b/go.mod index d8a492c..b46ff02 100644 --- a/go.mod +++ b/go.mod @@ -5,13 +5,13 @@ go 1.17 require ( github.com/dustin/go-humanize v1.0.1 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 - github.com/stretchr/testify v1.8.2 - github.com/wcharczuk/go-chart/v2 v2.1.0 + github.com/stretchr/testify v1.8.4 + github.com/wcharczuk/go-chart/v2 v2.1.1 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 // indirect + golang.org/x/image v0.14.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index ac1d9f7..e518b63 100644 --- a/go.sum +++ b/go.sum @@ -14,11 +14,48 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/wcharczuk/go-chart/v2 v2.1.0 h1:tY2slqVQ6bN+yHSnDYwZebLQFkphK4WNrVwnt7CJZ2I= github.com/wcharczuk/go-chart/v2 v2.1.0/go.mod h1:yx7MvAVNcP/kN9lKXM/NTce4au4DFN99j6i1OwDclNA= +github.com/wcharczuk/go-chart/v2 v2.1.1 h1:2u7na789qiD5WzccZsFz4MJWOJP72G+2kUuJoSNqWnE= +github.com/wcharczuk/go-chart/v2 v2.1.1/go.mod h1:CyCAUt2oqvfhCl6Q5ZvAZwItgpQKZOkCJGb+VGv6l14= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 h1:QelT11PB4FXiDEXucrfNckHoFxwt8USGY1ajP1ZF5lM= golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8= +golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4= +golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From e09ab2c3c7a5a338be739f503361bc17779733dd Mon Sep 17 00:00:00 2001 From: vicanso Date: Wed, 27 Dec 2023 20:37:18 +0800 Subject: [PATCH 60/73] Revert "chore: update modules" This reverts commit c2f709a74299db5a1c0290f5d6c07606f0b9f124. --- .github/workflows/test.yml | 1 - go.mod | 6 +++--- go.sum | 37 ------------------------------------- 3 files changed, 3 insertions(+), 41 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8d6a027..f591a3a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,7 +14,6 @@ jobs: strategy: matrix: go: - - '1.21' - '1.20' - '1.19' - '1.18' diff --git a/go.mod b/go.mod index b46ff02..d8a492c 100644 --- a/go.mod +++ b/go.mod @@ -5,13 +5,13 @@ go 1.17 require ( github.com/dustin/go-humanize v1.0.1 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 - github.com/stretchr/testify v1.8.4 - github.com/wcharczuk/go-chart/v2 v2.1.1 + github.com/stretchr/testify v1.8.2 + github.com/wcharczuk/go-chart/v2 v2.1.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/image v0.14.0 // indirect + golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index e518b63..ac1d9f7 100644 --- a/go.sum +++ b/go.sum @@ -14,48 +14,11 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/wcharczuk/go-chart/v2 v2.1.0 h1:tY2slqVQ6bN+yHSnDYwZebLQFkphK4WNrVwnt7CJZ2I= github.com/wcharczuk/go-chart/v2 v2.1.0/go.mod h1:yx7MvAVNcP/kN9lKXM/NTce4au4DFN99j6i1OwDclNA= -github.com/wcharczuk/go-chart/v2 v2.1.1 h1:2u7na789qiD5WzccZsFz4MJWOJP72G+2kUuJoSNqWnE= -github.com/wcharczuk/go-chart/v2 v2.1.1/go.mod h1:CyCAUt2oqvfhCl6Q5ZvAZwItgpQKZOkCJGb+VGv6l14= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 h1:QelT11PB4FXiDEXucrfNckHoFxwt8USGY1ajP1ZF5lM= golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8= -golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4= -golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 310800a5f01eaa29226b7595cdcc0384d2d9f55b Mon Sep 17 00:00:00 2001 From: "xuejinwei.1112" Date: Tue, 2 Jan 2024 12:32:56 +0800 Subject: [PATCH 61/73] support dash line for line chart --- line_chart.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/line_chart.go b/line_chart.go index bdbd38e..363cd36 100644 --- a/line_chart.go +++ b/line_chart.go @@ -115,6 +115,9 @@ func (l *lineChart) render(result *defaultRenderResult, seriesList SeriesList) ( StrokeColor: seriesColor, StrokeWidth: strokeWidth, } + if len(series.Style.StrokeDashArray) > 0 { + drawingStyle.StrokeDashArray = series.Style.StrokeDashArray + } yRange := result.axisRanges[series.AxisIndex] points := make([]Point, 0) From f1a231ff4b0e660609709babc1a9336c89c0233a Mon Sep 17 00:00:00 2001 From: vicanso Date: Sun, 11 Feb 2024 12:36:26 +0800 Subject: [PATCH 62/73] feat: support split line show option for charts, #69 --- .github/workflows/test.yml | 2 ++ examples/area_line_chart/main.go | 3 +-- examples/bar_chart/main.go | 3 +-- examples/chinese/main.go | 2 +- examples/funnel_chart/main.go | 3 +-- examples/horizontal_bar_chart/main.go | 3 +-- examples/line_chart/main.go | 8 ++++++-- examples/painter/main.go | 3 +-- examples/pie_chart/main.go | 3 +-- examples/radar_chart/main.go | 3 +-- examples/table/main.go | 3 +-- examples/time_line_chart/main.go | 3 +-- yaxis.go | 5 +++++ 13 files changed, 23 insertions(+), 21 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f591a3a..5544970 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,6 +14,8 @@ jobs: strategy: matrix: go: + - '1.22' + - '1.21' - '1.20' - '1.19' - '1.18' diff --git a/examples/area_line_chart/main.go b/examples/area_line_chart/main.go index 7a84df0..ea8f1c2 100644 --- a/examples/area_line_chart/main.go +++ b/examples/area_line_chart/main.go @@ -1,7 +1,6 @@ package main import ( - "io/ioutil" "os" "path/filepath" @@ -16,7 +15,7 @@ func writeFile(buf []byte) error { } file := filepath.Join(tmpPath, "area-line-chart.png") - err = ioutil.WriteFile(file, buf, 0600) + err = os.WriteFile(file, buf, 0600) if err != nil { return err } diff --git a/examples/bar_chart/main.go b/examples/bar_chart/main.go index c559a76..feea66e 100644 --- a/examples/bar_chart/main.go +++ b/examples/bar_chart/main.go @@ -1,7 +1,6 @@ package main import ( - "io/ioutil" "os" "path/filepath" @@ -16,7 +15,7 @@ func writeFile(buf []byte) error { } file := filepath.Join(tmpPath, "bar-chart.png") - err = ioutil.WriteFile(file, buf, 0600) + err = os.WriteFile(file, buf, 0600) if err != nil { return err } diff --git a/examples/chinese/main.go b/examples/chinese/main.go index d77216a..2d96b58 100644 --- a/examples/chinese/main.go +++ b/examples/chinese/main.go @@ -16,7 +16,7 @@ func writeFile(buf []byte) error { } file := filepath.Join(tmpPath, "chinese-line-chart.png") - err = ioutil.WriteFile(file, buf, 0600) + err = os.WriteFile(file, buf, 0600) if err != nil { return err } diff --git a/examples/funnel_chart/main.go b/examples/funnel_chart/main.go index 24f8afe..f29ccf9 100644 --- a/examples/funnel_chart/main.go +++ b/examples/funnel_chart/main.go @@ -1,7 +1,6 @@ package main import ( - "io/ioutil" "os" "path/filepath" @@ -16,7 +15,7 @@ func writeFile(buf []byte) error { } file := filepath.Join(tmpPath, "funnel-chart.png") - err = ioutil.WriteFile(file, buf, 0600) + err = os.WriteFile(file, buf, 0600) if err != nil { return err } diff --git a/examples/horizontal_bar_chart/main.go b/examples/horizontal_bar_chart/main.go index a1c50a7..f2cabe8 100644 --- a/examples/horizontal_bar_chart/main.go +++ b/examples/horizontal_bar_chart/main.go @@ -1,7 +1,6 @@ package main import ( - "io/ioutil" "os" "path/filepath" @@ -16,7 +15,7 @@ func writeFile(buf []byte) error { } file := filepath.Join(tmpPath, "horizontal-bar-chart.png") - err = ioutil.WriteFile(file, buf, 0600) + err = os.WriteFile(file, buf, 0600) if err != nil { return err } diff --git a/examples/line_chart/main.go b/examples/line_chart/main.go index c1478a6..4e6448f 100644 --- a/examples/line_chart/main.go +++ b/examples/line_chart/main.go @@ -2,7 +2,6 @@ package main import ( "fmt" - "io/ioutil" "os" "path/filepath" @@ -17,7 +16,7 @@ func writeFile(buf []byte) error { } file := filepath.Join(tmpPath, "line-chart.png") - err = ioutil.WriteFile(file, buf, 0600) + err = os.WriteFile(file, buf, 0600) if err != nil { return err } @@ -97,6 +96,11 @@ func main() { Top: 5, Bottom: 10, } + opt.YAxisOptions = []charts.YAxisOption{ + { + SplitLineShow: charts.FalseFlag(), + }, + } opt.SymbolShow = charts.FalseFlag() opt.LineStrokeWidth = 1 opt.ValueFormatter = func(f float64) string { diff --git a/examples/painter/main.go b/examples/painter/main.go index 3c31ce4..b7a5832 100644 --- a/examples/painter/main.go +++ b/examples/painter/main.go @@ -1,7 +1,6 @@ package main import ( - "io/ioutil" "os" "path/filepath" @@ -17,7 +16,7 @@ func writeFile(buf []byte) error { } file := filepath.Join(tmpPath, "painter.png") - err = ioutil.WriteFile(file, buf, 0600) + err = os.WriteFile(file, buf, 0600) if err != nil { return err } diff --git a/examples/pie_chart/main.go b/examples/pie_chart/main.go index 3721ed1..38488d2 100644 --- a/examples/pie_chart/main.go +++ b/examples/pie_chart/main.go @@ -1,7 +1,6 @@ package main import ( - "io/ioutil" "os" "path/filepath" @@ -16,7 +15,7 @@ func writeFile(buf []byte) error { } file := filepath.Join(tmpPath, "pie-chart.png") - err = ioutil.WriteFile(file, buf, 0600) + err = os.WriteFile(file, buf, 0600) if err != nil { return err } diff --git a/examples/radar_chart/main.go b/examples/radar_chart/main.go index 51f7409..e8095ae 100644 --- a/examples/radar_chart/main.go +++ b/examples/radar_chart/main.go @@ -1,7 +1,6 @@ package main import ( - "io/ioutil" "os" "path/filepath" @@ -16,7 +15,7 @@ func writeFile(buf []byte) error { } file := filepath.Join(tmpPath, "radar-chart.png") - err = ioutil.WriteFile(file, buf, 0600) + err = os.WriteFile(file, buf, 0600) if err != nil { return err } diff --git a/examples/table/main.go b/examples/table/main.go index 2701ec1..0210ecf 100644 --- a/examples/table/main.go +++ b/examples/table/main.go @@ -1,7 +1,6 @@ package main import ( - "io/ioutil" "os" "path/filepath" "strconv" @@ -19,7 +18,7 @@ func writeFile(buf []byte, filename string) error { } file := filepath.Join(tmpPath, filename) - err = ioutil.WriteFile(file, buf, 0600) + err = os.WriteFile(file, buf, 0600) if err != nil { return err } diff --git a/examples/time_line_chart/main.go b/examples/time_line_chart/main.go index 10932cd..6cb3f3d 100644 --- a/examples/time_line_chart/main.go +++ b/examples/time_line_chart/main.go @@ -3,7 +3,6 @@ package main import ( "crypto/rand" "fmt" - "io/ioutil" "math/big" "os" "path/filepath" @@ -20,7 +19,7 @@ func writeFile(buf []byte) error { } file := filepath.Join(tmpPath, "time-line-chart.png") - err = ioutil.WriteFile(file, buf, 0600) + err = os.WriteFile(file, buf, 0600) if err != nil { return err } diff --git a/yaxis.go b/yaxis.go index bece2cc..e58b7a6 100644 --- a/yaxis.go +++ b/yaxis.go @@ -50,6 +50,8 @@ type YAxisOption struct { DivideCount int Unit int isCategoryAxis bool + // The flag for show axis split line, set this to true will show axis split line + SplitLineShow *bool } // NewYAxisOptions returns a y axis option @@ -100,6 +102,9 @@ func (opt *YAxisOption) ToAxisOption(p *Painter) AxisOption { axisOpt.StrokeWidth = 1 axisOpt.SplitLineShow = false } + if opt.SplitLineShow != nil { + axisOpt.SplitLineShow = *opt.SplitLineShow + } return axisOpt } From 06fe1006d5b2f1a79f7f68f5a986de4bd1c7b9d2 Mon Sep 17 00:00:00 2001 From: vicanso Date: Sun, 11 Feb 2024 12:39:39 +0800 Subject: [PATCH 63/73] chore: update test go version --- .github/workflows/test.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5544970..ce56fe7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,10 +20,6 @@ jobs: - '1.19' - '1.18' - '1.17' - - '1.16' - - '1.15' - - '1.14' - - '1.13' steps: - name: Go ${{ matrix.go }} test From 19a4d783fdfb2f6dedfbad90da9117d389443ffe Mon Sep 17 00:00:00 2001 From: Alexander Heidrich Date: Fri, 8 Mar 2024 20:24:13 +0100 Subject: [PATCH 64/73] fix: Label position of the pie chart --- pie_chart.go | 223 ++++++++++++++++++++++++----------- pie_chart_test.go | 294 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 447 insertions(+), 70 deletions(-) diff --git a/pie_chart.go b/pie_chart.go index b4714ac..bbf7814 100644 --- a/pie_chart.go +++ b/pie_chart.go @@ -63,6 +63,96 @@ func NewPieChart(p *Painter, opt PieChartOption) *pieChart { } } +type sector struct { + value float64 + percent float64 + cx int + cy int + rx float64 + ry float64 + start float64 + delta float64 + offset int + quadrant int + lineStartX int + lineStartY int + lineBranchX int + lineBranchY int + lineEndX int + lineEndY int + showLabel bool + label string + series Series + color Color +} + +func NewSector(cx int, cy int, radius float64, labelRadius float64, value float64, currentValue float64, totalValue float64, labelLineLength int, label string, series Series, color Color) sector { + s := sector{} + s.value = value + s.percent = value / totalValue + s.cx = cx + s.cy = cy + s.rx = radius + s.ry = radius + p := currentValue / totalValue + if p < 0.25 { + s.quadrant = 1 + } else if p < 0.5 { + s.quadrant = 4 + } else if p < 0.75 { + s.quadrant = 3 + } else { + s.quadrant = 2 + } + s.start = chart.PercentToRadians(currentValue/totalValue) - math.Pi/2 // Bogenmaß + s.delta = chart.PercentToRadians(value / totalValue) + angle := s.start + s.delta/2 + s.lineStartX = cx + int(radius*math.Cos(angle)) + s.lineStartY = cy + int(radius*math.Sin(angle)) + s.lineBranchX = cx + int(labelRadius*math.Cos(angle)) + s.lineBranchY = cy + int(labelRadius*math.Sin(angle)) + s.offset = labelLineLength + if s.lineBranchX <= cx { + s.offset *= -1 + } + s.lineEndX = s.lineBranchX + s.offset + s.lineEndY = s.lineBranchY + s.series = series + s.color = color + s.showLabel = series.Label.Show + s.label = NewPieLabelFormatter([]string{label}, series.Label.Formatter)(0, s.value, s.percent) + return s +} + +func (s *sector) calculateY(prevY int) int { + for i := 0; i <= s.cy; i++ { + if s.quadrant <= 2 { + if (prevY - s.lineBranchY) > labelFontSize+5 { + break + } + s.lineBranchY -= 1 + } else { + if (s.lineBranchY - prevY) > labelFontSize+5 { + break + } + s.lineBranchY += 1 + } + } + s.lineEndY = s.lineBranchY + return s.lineBranchY +} + +func (s *sector) calculateTextXY(textBox Box) (x int, y int) { + textMargin := 3 + x = s.lineEndX + textMargin + y = s.lineEndY + textBox.Height()>>1 - 1 + if s.offset < 0 { + textWidth := textBox.Width() + x = s.lineEndX - textWidth - textMargin + } + return +} + func (p *pieChart) render(result *defaultRenderResult, seriesList SeriesList) (Box, error) { opt := p.opt values := make([]float64, len(seriesList)) @@ -101,98 +191,91 @@ func (p *pieChart) render(result *defaultRenderResult, seriesList SeriesList) (B theme := opt.Theme currentValue := float64(0) - prevPoints := make([]Point, 0) - isOverride := func(x, y int) bool { - for _, p := range prevPoints { - if math.Abs(float64(p.Y-y)) > labelFontSize { - continue - } - // label可能较多内容,不好计算横向占用空间 - // 因此x的位置需要中间位置两侧,否则认为override - if (p.X <= cx && x <= cx) || - (p.X > cx && x > cx) { - return true + var quadrant1, quadrant2, quadrant3, quadrant4 []sector + for index, v := range values { + series := seriesList[index] + color := theme.GetSeriesColor(index) + if index == len(values)-1 { + if color == theme.GetSeriesColor(0) { + color = theme.GetSeriesColor(1) } } - return false + s := NewSector(cx, cy, radius, labelRadius, v, currentValue, total, labelLineWidth, seriesNames[index], series, color) + switch quadrant := s.quadrant; quadrant { + case 1: + quadrant1 = append([]sector{s}, quadrant1...) + case 2: + quadrant2 = append(quadrant2, s) + case 3: + quadrant3 = append([]sector{s}, quadrant3...) + case 4: + quadrant4 = append(quadrant4, s) + } + currentValue += v } + sectors := append(quadrant1, quadrant4...) + sectors = append(sectors, quadrant3...) + sectors = append(sectors, quadrant2...) - for index, v := range values { + currentQuadrant := 0 + prevY := 0 + maxY := 0 + minY := 0 + for _, s := range sectors { seriesPainter.OverrideDrawingStyle(Style{ StrokeWidth: 1, - StrokeColor: theme.GetSeriesColor(index), - FillColor: theme.GetSeriesColor(index), + StrokeColor: s.color, + FillColor: s.color, }) - seriesPainter.MoveTo(cx, cy) - start := chart.PercentToRadians(currentValue/total) - math.Pi/2 - currentValue += v - percent := (v / total) - delta := chart.PercentToRadians(percent) - seriesPainter.ArcTo(cx, cy, radius, radius, start, delta). - LineTo(cx, cy). - Close(). - FillStroke() - - series := seriesList[index] - // 是否显示label - showLabel := series.Label.Show - if !showLabel { + seriesPainter.MoveTo(s.cx, s.cy) + seriesPainter.ArcTo(s.cx, s.cy, s.rx, s.ry, s.start, s.delta).LineTo(s.cx, s.cy).Close().FillStroke() + if !s.showLabel { continue } - - // label的角度为饼块中间 - angle := start + delta/2 - startx := cx + int(radius*math.Cos(angle)) - starty := cy + int(radius*math.Sin(angle)) - - endx := cx + int(labelRadius*math.Cos(angle)) - endy := cy + int(labelRadius*math.Sin(angle)) - // 计算是否有重叠,如果有则调整y坐标位置 - // 最多只尝试5次 - for i := 0; i < 5; i++ { - if !isOverride(endx, endy) { - break + if currentQuadrant != s.quadrant { + currentQuadrant = s.quadrant + if s.quadrant == 1 { + minY = cy * 2 + maxY = 0 + prevY = cy * 2 + } + if s.quadrant == 2 { + prevY = minY + } + if s.quadrant == 3 { + minY = cy * 2 + maxY = 0 + prevY = 0 + } + if s.quadrant == 4 { + prevY = maxY } - endy -= (labelFontSize << 1) } - prevPoints = append(prevPoints, Point{ - X: endx, - Y: endy, - }) - - seriesPainter.MoveTo(startx, starty) - seriesPainter.LineTo(endx, endy) - offset := labelLineWidth - if endx < cx { - offset *= -1 + prevY = s.calculateY(prevY) + if prevY > maxY { + maxY = prevY } - seriesPainter.MoveTo(endx, endy) - endx += offset - seriesPainter.LineTo(endx, endy) + if prevY < minY { + minY = prevY + } + seriesPainter.MoveTo(s.lineStartX, s.lineStartY) + seriesPainter.LineTo(s.lineBranchX, s.lineBranchY) + seriesPainter.MoveTo(s.lineBranchX, s.lineBranchY) + seriesPainter.LineTo(s.lineEndX, s.lineEndY) seriesPainter.Stroke() - textStyle := Style{ FontColor: theme.GetTextColor(), FontSize: labelFontSize, Font: opt.Font, } - if !series.Label.Color.IsZero() { - textStyle.FontColor = series.Label.Color + if !s.series.Label.Color.IsZero() { + textStyle.FontColor = s.series.Label.Color } seriesPainter.OverrideTextStyle(textStyle) - text := NewPieLabelFormatter(seriesNames, series.Label.Formatter)(index, v, percent) - textBox := seriesPainter.MeasureText(text) - textMargin := 3 - x := endx + textMargin - y := endy + textBox.Height()>>1 - 1 - if offset < 0 { - textWidth := textBox.Width() - x = endx - textWidth - textMargin - } - seriesPainter.Text(text, x, y) + x, y := s.calculateTextXY(seriesPainter.MeasureText(s.label)) + seriesPainter.Text(s.label, x, y) } - return p.p.box, nil } diff --git a/pie_chart_test.go b/pie_chart_test.go index c373a7e..0b8f798 100644 --- a/pie_chart_test.go +++ b/pie_chart_test.go @@ -23,6 +23,7 @@ package charts import ( + "strconv" "testing" "github.com/stretchr/testify/assert" @@ -98,3 +99,296 @@ func TestPieChart(t *testing.T) { assert.Equal(tt.result, string(data)) } } + +func TestPieChartWithLabelsValuesSortedDescending(t *testing.T) { + assert := assert.New(t) + + tests := []struct { + render func(*Painter) ([]byte, error) + result string + }{ + { + render: func(p *Painter) ([]byte, error) { + values := []float64{ + 84358845, + 68070697, + 58850717, + 48059777, + 36753736, + 19051562, + 17947406, + 11754004, + 10827529, + 10521556, + 10467366, + 10394055, + 9597085, + 9104772, + 6447710, + 5932654, + 5563970, + 5428792, + 5194336, + 3850894, + 2857279, + 2116792, + 1883008, + 1373101, + 920701, + 660809, + 542051, + } + _, err := NewPieChart(p, PieChartOption{ + SeriesList: NewPieSeriesList(values, PieSeriesOption{ + Label: SeriesLabel{ + Show: true, + Formatter: "{b} ({c} ≅ {d})", + }, + Radius: "200", + }), + Title: TitleOption{ + Text: "European Union member states by population", + Left: PositionRight, + }, + Padding: Box{ + Top: 20, + Right: 20, + Bottom: 20, + Left: 20, + }, + Legend: LegendOption{ + Data: []string{ + "Germany", + "France", + "Italy", + "Spain", + "Poland", + "Romania", + "Netherlands", + "Belgium", + "Czech Republic", + "Sweden", + "Portugal", + "Greece", + "Hungary", + "Austria", + "Bulgaria", + "Denmark", + "Finland", + "Slovakia", + "Ireland", + "Croatia", + "Lithuania", + "Slovenia", + "Latvia", + "Estonia", + "Cyprus", + "Luxembourg", + "Malta", + }, + Show: FalseFlag(), + }, + }).Render() + if err != nil { + return nil, err + } + return p.Bytes() + }, + result: "\\nEuropean Union member states by populationFrance (68070697 ≅ 15.17%)Germany (84358845 ≅ 18.8%)Italy (58850717 ≅ 13.12%)Spain (48059777 ≅ 10.71%)Belgium (11754004 ≅ 2.62%)Netherlands (17947406 ≅ 4%)Romania (19051562 ≅ 4.24%)Poland (36753736 ≅ 8.19%)Czech Republic (10827529 ≅ 2.41%)Sweden (10521556 ≅ 2.34%)Portugal (10467366 ≅ 2.33%)Greece (10394055 ≅ 2.31%)Hungary (9597085 ≅ 2.13%)Austria (9104772 ≅ 2.02%)Bulgaria (6447710 ≅ 1.43%)Denmark (5932654 ≅ 1.32%)Finland (5563970 ≅ 1.24%)Slovakia (5428792 ≅ 1.21%)Ireland (5194336 ≅ 1.15%)Croatia (3850894 ≅ 0.85%)Lithuania (2857279 ≅ 0.63%)Slovenia (2116792 ≅ 0.47%)Latvia (1883008 ≅ 0.41%)Estonia (1373101 ≅ 0.3%)Cyprus (920701 ≅ 0.2%)Luxembourg (660809 ≅ 0.14%)Malta (542051 ≅ 0.12%)", + }, + } + for _, tt := range tests { + p, err := NewPainter(PainterOptions{ + Type: ChartOutputSVG, + Width: 1000, + Height: 800, + }, PainterThemeOption(defaultTheme)) + assert.Nil(err) + data, err := tt.render(p.Child(PainterPaddingOption(Box{ + Left: 20, + Top: 20, + Right: 20, + Bottom: 20, + }))) + assert.Nil(err) + assert.Equal(tt.result, string(data)) + } +} + +func TestPieChartWithLabelsValuesUnsorted(t *testing.T) { + assert := assert.New(t) + + tests := []struct { + render func(*Painter) ([]byte, error) + result string + }{ + { + render: func(p *Painter) ([]byte, error) { + values := []float64{ + 9104772, + 11754004, + 6447710, + 3850894, + 920701, + 10827529, + 5932654, + 1373101, + 5563970, + 68070697, + 84358845, + 10394055, + 9597085, + 5194336, + 58850717, + 1883008, + 2857279, + 660809, + 542051, + 17947406, + 36753736, + 10467366, + 19051562, + 5428792, + 2116792, + 48059777, + 10521556, + } + _, err := NewPieChart(p, PieChartOption{ + SeriesList: NewPieSeriesList(values, PieSeriesOption{ + Label: SeriesLabel{ + Show: true, + Formatter: "{b} ({c} ≅ {d})", + }, + Radius: "200", + }), + Title: TitleOption{ + Text: "European Union member states by population", + Left: PositionRight, + }, + Padding: Box{ + Top: 20, + Right: 20, + Bottom: 20, + Left: 20, + }, + Legend: LegendOption{ + Data: []string{ + "Austria", + "Belgium", + "Bulgaria", + "Croatia", + "Cyprus", + "Czech Republic", + "Denmark", + "Estonia", + "Finland", + "France", + "Germany", + "Greece", + "Hungary", + "Ireland", + "Italy", + "Latvia", + "Lithuania", + "Luxembourg", + "Malta", + "Netherlands", + "Poland", + "Portugal", + "Romania", + "Slovakia", + "Slovenia", + "Spain", + "Sweden", + }, + Show: FalseFlag(), + }, + }).Render() + if err != nil { + return nil, err + } + return p.Bytes() + }, + result: "\\nEuropean Union member states by populationFrance (68070697 ≅ 15.17%)Finland (5563970 ≅ 1.24%)Estonia (1373101 ≅ 0.3%)Denmark (5932654 ≅ 1.32%)Czech Republic (10827529 ≅ 2.41%)Cyprus (920701 ≅ 0.2%)Croatia (3850894 ≅ 0.85%)Bulgaria (6447710 ≅ 1.43%)Belgium (11754004 ≅ 2.62%)Austria (9104772 ≅ 2.02%)Germany (84358845 ≅ 18.8%)Greece (10394055 ≅ 2.31%)Hungary (9597085 ≅ 2.13%)Poland (36753736 ≅ 8.19%)Netherlands (17947406 ≅ 4%)Malta (542051 ≅ 0.12%)Luxembourg (660809 ≅ 0.14%)Lithuania (2857279 ≅ 0.63%)Latvia (1883008 ≅ 0.41%)Italy (58850717 ≅ 13.12%)Ireland (5194336 ≅ 1.15%)Portugal (10467366 ≅ 2.33%)Romania (19051562 ≅ 4.24%)Slovakia (5428792 ≅ 1.21%)Slovenia (2116792 ≅ 0.47%)Spain (48059777 ≅ 10.71%)Sweden (10521556 ≅ 2.34%)", + }, + } + for _, tt := range tests { + p, err := NewPainter(PainterOptions{ + Type: ChartOutputSVG, + Width: 1000, + Height: 800, + }, PainterThemeOption(defaultTheme)) + assert.Nil(err) + data, err := tt.render(p.Child(PainterPaddingOption(Box{ + Left: 20, + Top: 20, + Right: 20, + Bottom: 20, + }))) + assert.Nil(err) + assert.Equal(tt.result, string(data)) + } +} + +func TestPieChartWith100Labels(t *testing.T) { + assert := assert.New(t) + + tests := []struct { + render func(*Painter) ([]byte, error) + result string + }{ + { + render: func(p *Painter) ([]byte, error) { + var values []float64 + var labels []string + for i := 1; i <= 100; i++ { + values = append(values, float64(1)) + labels = append(labels, "Label "+strconv.Itoa(i)) + } + _, err := NewPieChart(p, PieChartOption{ + SeriesList: NewPieSeriesList(values, PieSeriesOption{ + Label: SeriesLabel{ + Show: true, + }, + Radius: "200", + }), + Title: TitleOption{ + Text: "Test with 100 labels", + Left: PositionRight, + }, + Padding: Box{ + Top: 20, + Right: 20, + Bottom: 20, + Left: 20, + }, + Legend: LegendOption{ + Data: labels, + Show: FalseFlag(), + }, + }).Render() + if err != nil { + return nil, err + } + return p.Bytes() + }, + result: "\\nTest with 100 labelsLabel 25: 1%Label 24: 1%Label 23: 1%Label 22: 1%Label 21: 1%Label 20: 1%Label 19: 1%Label 18: 1%Label 17: 1%Label 16: 1%Label 15: 1%Label 14: 1%Label 13: 1%Label 12: 1%Label 11: 1%Label 10: 1%Label 9: 1%Label 8: 1%Label 7: 1%Label 6: 1%Label 5: 1%Label 4: 1%Label 3: 1%Label 2: 1%Label 1: 1%Label 26: 1%Label 27: 1%Label 28: 1%Label 29: 1%Label 30: 1%Label 31: 1%Label 32: 1%Label 33: 1%Label 34: 1%Label 35: 1%Label 36: 1%Label 37: 1%Label 38: 1%Label 39: 1%Label 40: 1%Label 41: 1%Label 42: 1%Label 43: 1%Label 44: 1%Label 45: 1%Label 46: 1%Label 47: 1%Label 48: 1%Label 49: 1%Label 50: 1%Label 75: 1%Label 74: 1%Label 73: 1%Label 72: 1%Label 71: 1%Label 70: 1%Label 69: 1%Label 68: 1%Label 67: 1%Label 66: 1%Label 65: 1%Label 64: 1%Label 63: 1%Label 62: 1%Label 61: 1%Label 60: 1%Label 59: 1%Label 58: 1%Label 57: 1%Label 56: 1%Label 55: 1%Label 54: 1%Label 53: 1%Label 52: 1%Label 51: 1%Label 76: 1%Label 77: 1%Label 78: 1%Label 79: 1%Label 80: 1%Label 81: 1%Label 82: 1%Label 83: 1%Label 84: 1%Label 85: 1%Label 86: 1%Label 87: 1%Label 88: 1%Label 89: 1%Label 90: 1%Label 91: 1%Label 92: 1%Label 93: 1%Label 94: 1%Label 95: 1%Label 96: 1%Label 97: 1%Label 98: 1%Label 99: 1%Label 100: 1%", + }, + } + for _, tt := range tests { + p, err := NewPainter(PainterOptions{ + Type: ChartOutputSVG, + Width: 1000, + Height: 900, + }, PainterThemeOption(defaultTheme)) + assert.Nil(err) + data, err := tt.render(p.Child(PainterPaddingOption(Box{ + Left: 20, + Top: 20, + Right: 20, + Bottom: 20, + }))) + assert.Nil(err) + assert.Equal(tt.result, string(data)) + } +} From 8c6c4e007c85b7c5fabf3340fff4952fc0810592 Mon Sep 17 00:00:00 2001 From: Alexander Heidrich Date: Fri, 22 Mar 2024 08:27:06 +0100 Subject: [PATCH 65/73] fix: Label position of the pie chart --- pie_chart.go | 4 +- pie_chart_test.go | 141 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 142 insertions(+), 3 deletions(-) diff --git a/pie_chart.go b/pie_chart.go index bbf7814..6cc48c4 100644 --- a/pie_chart.go +++ b/pie_chart.go @@ -94,7 +94,7 @@ func NewSector(cx int, cy int, radius float64, labelRadius float64, value float6 s.cy = cy s.rx = radius s.ry = radius - p := currentValue / totalValue + p := (currentValue + value/2) / totalValue if p < 0.25 { s.quadrant = 1 } else if p < 0.5 { @@ -104,7 +104,7 @@ func NewSector(cx int, cy int, radius float64, labelRadius float64, value float6 } else { s.quadrant = 2 } - s.start = chart.PercentToRadians(currentValue/totalValue) - math.Pi/2 // Bogenmaß + s.start = chart.PercentToRadians(currentValue/totalValue) - math.Pi/2 s.delta = chart.PercentToRadians(value / totalValue) angle := s.start + s.delta/2 s.lineStartX = cx + int(radius*math.Cos(angle)) diff --git a/pie_chart_test.go b/pie_chart_test.go index 0b8f798..3795d32 100644 --- a/pie_chart_test.go +++ b/pie_chart_test.go @@ -194,7 +194,7 @@ func TestPieChartWithLabelsValuesSortedDescending(t *testing.T) { } return p.Bytes() }, - result: "\\nEuropean Union member states by populationFrance (68070697 ≅ 15.17%)Germany (84358845 ≅ 18.8%)Italy (58850717 ≅ 13.12%)Spain (48059777 ≅ 10.71%)Belgium (11754004 ≅ 2.62%)Netherlands (17947406 ≅ 4%)Romania (19051562 ≅ 4.24%)Poland (36753736 ≅ 8.19%)Czech Republic (10827529 ≅ 2.41%)Sweden (10521556 ≅ 2.34%)Portugal (10467366 ≅ 2.33%)Greece (10394055 ≅ 2.31%)Hungary (9597085 ≅ 2.13%)Austria (9104772 ≅ 2.02%)Bulgaria (6447710 ≅ 1.43%)Denmark (5932654 ≅ 1.32%)Finland (5563970 ≅ 1.24%)Slovakia (5428792 ≅ 1.21%)Ireland (5194336 ≅ 1.15%)Croatia (3850894 ≅ 0.85%)Lithuania (2857279 ≅ 0.63%)Slovenia (2116792 ≅ 0.47%)Latvia (1883008 ≅ 0.41%)Estonia (1373101 ≅ 0.3%)Cyprus (920701 ≅ 0.2%)Luxembourg (660809 ≅ 0.14%)Malta (542051 ≅ 0.12%)", + result: "\\nEuropean Union member states by populationGermany (84358845 ≅ 18.8%)France (68070697 ≅ 15.17%)Italy (58850717 ≅ 13.12%)Netherlands (17947406 ≅ 4%)Romania (19051562 ≅ 4.24%)Poland (36753736 ≅ 8.19%)Spain (48059777 ≅ 10.71%)Belgium (11754004 ≅ 2.62%)Czech Republic (10827529 ≅ 2.41%)Sweden (10521556 ≅ 2.34%)Portugal (10467366 ≅ 2.33%)Greece (10394055 ≅ 2.31%)Hungary (9597085 ≅ 2.13%)Austria (9104772 ≅ 2.02%)Bulgaria (6447710 ≅ 1.43%)Denmark (5932654 ≅ 1.32%)Finland (5563970 ≅ 1.24%)Slovakia (5428792 ≅ 1.21%)Ireland (5194336 ≅ 1.15%)Croatia (3850894 ≅ 0.85%)Lithuania (2857279 ≅ 0.63%)Slovenia (2116792 ≅ 0.47%)Latvia (1883008 ≅ 0.41%)Estonia (1373101 ≅ 0.3%)Cyprus (920701 ≅ 0.2%)Luxembourg (660809 ≅ 0.14%)Malta (542051 ≅ 0.12%)", }, } for _, tt := range tests { @@ -392,3 +392,142 @@ func TestPieChartWith100Labels(t *testing.T) { assert.Equal(tt.result, string(data)) } } + +func TestPieChartFixLabelPos72586(t *testing.T) { + assert := assert.New(t) + + tests := []struct { + render func(*Painter) ([]byte, error) + result string + }{ + { + render: func(p *Painter) ([]byte, error) { + values := []float64{ + 397594, + 185596, + 149086, + 144258, + 120194, + 117514, + 99412, + 91135, + 87282, + 76790, + 72586, + 58818, + 58270, + 56306, + 55486, + 54792, + 53746, + 51460, + 41242, + 39476, + 37414, + 36644, + 33784, + 32788, + 32566, + 29608, + 29558, + 29384, + 28166, + 26998, + 26948, + 26054, + 25804, + 25730, + 24438, + 23782, + 22896, + 21404, + 428978, + } + _, err := NewPieChart(p, PieChartOption{ + SeriesList: NewPieSeriesList(values, PieSeriesOption{ + Label: SeriesLabel{ + Show: true, + Formatter: "{b} ({c} ≅ {d})", + }, + Radius: "150", + }), + Title: TitleOption{ + Text: "Fix label K (72586)", + Left: PositionRight, + }, + Padding: Box{ + Top: 20, + Right: 20, + Bottom: 20, + Left: 20, + }, + Legend: LegendOption{ + Data: []string{ + "A", + "B", + "C", + "D", + "E", + "F", + "G", + "H", + "I", + "J", + "K", + "L", + "M", + "N", + "O", + "P", + "Q", + "R", + "S", + "T", + "U", + "V", + "W", + "X", + "Y", + "Z", + "AA", + "AB", + "AC", + "AD", + "AE", + "AF", + "AG", + "AH", + "AI", + "AJ", + "AK", + "AL", + "AM", + }, + Show: FalseFlag(), + }, + }).Render() + if err != nil { + return nil, err + } + return p.Bytes() + }, + result: "\\nFix label K (72586)C (149086 ≅ 5.04%)B (185596 ≅ 6.28%)A (397594 ≅ 13.45%)D (144258 ≅ 4.88%)E (120194 ≅ 4.06%)F (117514 ≅ 3.97%)G (99412 ≅ 3.36%)H (91135 ≅ 3.08%)I (87282 ≅ 2.95%)J (76790 ≅ 2.59%)Z (29608 ≅ 1%)Y (32566 ≅ 1.1%)X (32788 ≅ 1.1%)W (33784 ≅ 1.14%)V (36644 ≅ 1.24%)U (37414 ≅ 1.26%)T (39476 ≅ 1.33%)S (41242 ≅ 1.39%)R (51460 ≅ 1.74%)Q (53746 ≅ 1.81%)P (54792 ≅ 1.85%)O (55486 ≅ 1.87%)N (56306 ≅ 1.9%)M (58270 ≅ 1.97%)L (58818 ≅ 1.99%)K (72586 ≅ 2.45%)AA (29558 ≅ 1%)AB (29384 ≅ 0.99%)AC (28166 ≅ 0.95%)AD (26998 ≅ 0.91%)AE (26948 ≅ 0.91%)AF (26054 ≅ 0.88%)AG (25804 ≅ 0.87%)AH (25730 ≅ 0.87%)AI (24438 ≅ 0.82%)AJ (23782 ≅ 0.8%)AK (22896 ≅ 0.77%)AL (21404 ≅ 0.72%)AM (428978 ≅ 14.52%)", + }, + } + for _, tt := range tests { + p, err := NewPainter(PainterOptions{ + Type: ChartOutputSVG, + Width: 1150, + Height: 550, + }, PainterThemeOption(defaultTheme)) + assert.Nil(err) + data, err := tt.render(p.Child(PainterPaddingOption(Box{ + Left: 20, + Top: 20, + Right: 20, + Bottom: 20, + }))) + assert.Nil(err) + assert.Equal(tt.result, string(data)) + } +} From 9b7634c2c230a0a1907c6e99be6bdea11f811a9e Mon Sep 17 00:00:00 2001 From: vicanso Date: Thu, 16 May 2024 20:02:24 +0800 Subject: [PATCH 66/73] feat: support rounded rect for bar chart --- bar_chart.go | 27 ++++++++++++------ bar_chart_test.go | 70 +++++++++++++++++++++++++++++++++++++++++++++++ painter.go | 42 ++++++++++++++++++++++++++++ painter_test.go | 23 ++++++++++++++++ series.go | 2 ++ 5 files changed, 156 insertions(+), 8 deletions(-) diff --git a/bar_chart.go b/bar_chart.go index efeb465..508c63e 100644 --- a/bar_chart.go +++ b/bar_chart.go @@ -142,14 +142,25 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B } top := barMaxHeight - h - seriesPainter.OverrideDrawingStyle(Style{ - FillColor: fillColor, - }).Rect(chart.Box{ - Top: top, - Left: x, - Right: x + barWidth, - Bottom: barMaxHeight - 1, - }) + if series.RoundRadius <= 0 { + seriesPainter.OverrideDrawingStyle(Style{ + FillColor: fillColor, + }).Rect(chart.Box{ + Top: top, + Left: x, + Right: x + barWidth, + Bottom: barMaxHeight - 1, + }) + } else { + seriesPainter.OverrideDrawingStyle(Style{ + FillColor: fillColor, + }).RoundedRect(chart.Box{ + Top: top, + Left: x, + Right: x + barWidth, + Bottom: barMaxHeight - 1, + }, series.RoundRadius) + } // 用于生成marker point points[j] = Point{ // 居中的位置 diff --git a/bar_chart_test.go b/bar_chart_test.go index e1522d6..654c320 100644 --- a/bar_chart_test.go +++ b/bar_chart_test.go @@ -104,6 +104,76 @@ func TestBarChart(t *testing.T) { }, result: "\\n24020016012080400FebMayAugNov24.9723.225.676.7135.6162.232.6206.43.32.65.9926.428.770.7175.6182.248.718.862.3", }, + { + render: func(p *Painter) ([]byte, error) { + seriesList := NewSeriesListDataFromValues([][]float64{ + { + 2.0, + 4.9, + 7.0, + 23.2, + 25.6, + 76.7, + 135.6, + 162.2, + 32.6, + 20.0, + 6.4, + 3.3, + }, + { + 2.6, + 5.9, + 9.0, + 26.4, + 28.7, + 70.7, + 175.6, + 182.2, + 48.7, + 18.8, + 6.0, + 2.3, + }, + }) + for index := range seriesList { + seriesList[index].Label.Show = true + seriesList[index].RoundRadius = 5 + } + _, err := NewBarChart(p, BarChartOption{ + Padding: Box{ + Left: 10, + Top: 10, + Right: 10, + Bottom: 10, + }, + SeriesList: seriesList, + XAxis: NewXAxisOption([]string{ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + }), + YAxisOptions: NewYAxisOptions([]string{ + "Rainfall", + "Evaporation", + }), + }).Render() + if err != nil { + return nil, err + } + return p.Bytes() + }, + result: "\\n24020016012080400FebMayAugNov24.9723.225.676.7135.6162.232.6206.43.32.65.9926.428.770.7175.6182.248.718.862.3", + }, } for _, tt := range tests { diff --git a/painter.go b/painter.go index 18496fd..bc13418 100644 --- a/painter.go +++ b/painter.go @@ -803,6 +803,48 @@ func (p *Painter) Rect(box Box) *Painter { return p } +func (p *Painter) RoundedRect(box Box, radius int) *Painter { + r := (box.Right - box.Left) / 2 + if radius > r { + radius = r + } + rx := float64(radius) + ry := float64(radius) + p.MoveTo(box.Left+radius, box.Top) + p.LineTo(box.Right-radius, box.Top) + + cx := box.Right - radius + cy := box.Top + radius + // right top + p.ArcTo(cx, cy, rx, ry, -math.Pi/2, math.Pi/2) + + p.LineTo(box.Right, box.Bottom-radius) + + // right bottom + cx = box.Right - radius + cy = box.Bottom - radius + p.ArcTo(cx, cy, rx, ry, 0.0, math.Pi/2) + + p.LineTo(box.Left+radius, box.Bottom) + + // left bottom + cx = box.Left + radius + cy = box.Bottom - radius + p.ArcTo(cx, cy, rx, ry, math.Pi/2, math.Pi/2) + + p.LineTo(box.Left, box.Top+radius) + + // left top + cx = box.Left + radius + cy = box.Top + radius + p.ArcTo(cx, cy, rx, ry, math.Pi, math.Pi/2) + + p.Close() + p.FillStroke() + p.Fill() + return p +} + func (p *Painter) LegendLineDot(box Box) *Painter { width := box.Width() height := box.Height() diff --git a/painter_test.go b/painter_test.go index 2392d5b..b159328 100644 --- a/painter_test.go +++ b/painter_test.go @@ -343,6 +343,29 @@ func TestPainter(t *testing.T) { } } +func TestRoundedRect(t *testing.T) { + assert := assert.New(t) + p, err := NewPainter(PainterOptions{ + Width: 400, + Height: 300, + Type: ChartOutputSVG, + }) + assert.Nil(err) + p.OverrideDrawingStyle(Style{ + FillColor: drawing.ColorWhite, + StrokeWidth: 1, + StrokeColor: drawing.ColorWhite, + }).RoundedRect(Box{ + Left: 10, + Right: 30, + Bottom: 150, + Top: 10, + }, 5) + buf, err := p.Bytes() + assert.Nil(err) + assert.Equal("\\n", string(buf)) +} + func TestPainterTextFit(t *testing.T) { assert := assert.New(t) p, err := NewPainter(PainterOptions{ diff --git a/series.go b/series.go index f28bfa9..0ad135f 100644 --- a/series.go +++ b/series.go @@ -126,6 +126,8 @@ type Series struct { Name string // Radius for Pie chart, e.g.: 40%, default is "40%" Radius string + // Round for bar chart + RoundRadius int // Mark point for series MarkPoint SeriesMarkPoint // Make line for series From 96148357237a4698910b58e780f6a2fe095a4cfe Mon Sep 17 00:00:00 2001 From: vicanso Date: Tue, 21 May 2024 20:26:40 +0800 Subject: [PATCH 67/73] feat: support rounded rect for horizontal bar chart --- examples/horizontal_bar_chart/main.go | 3 +++ horizontal_bar_chart.go | 28 +++++++++++++++++++-------- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/examples/horizontal_bar_chart/main.go b/examples/horizontal_bar_chart/main.go index f2cabe8..a7daa8c 100644 --- a/examples/horizontal_bar_chart/main.go +++ b/examples/horizontal_bar_chart/main.go @@ -65,6 +65,9 @@ func main() { "China", "World", }), + func(opt *charts.ChartOption) { + opt.SeriesList[0].RoundRadius = 5 + }, ) if err != nil { panic(err) diff --git a/horizontal_bar_chart.go b/horizontal_bar_chart.go index 2ab4c03..ca91242 100644 --- a/horizontal_bar_chart.go +++ b/horizontal_bar_chart.go @@ -136,14 +136,26 @@ func (h *horizontalBarChart) render(result *defaultRenderResult, seriesList Seri fillColor = item.Style.FillColor } right := w - seriesPainter.OverrideDrawingStyle(Style{ - FillColor: fillColor, - }).Rect(chart.Box{ - Top: y, - Left: 0, - Right: right, - Bottom: y + barHeight, - }) + if series.RoundRadius <= 0 { + seriesPainter.OverrideDrawingStyle(Style{ + FillColor: fillColor, + }).Rect(chart.Box{ + Top: y, + Left: 0, + Right: right, + Bottom: y + barHeight, + }) + } else { + seriesPainter.OverrideDrawingStyle(Style{ + FillColor: fillColor, + }).RoundedRect(chart.Box{ + Top: y, + Left: 0, + Right: right, + Bottom: y + barHeight, + }, series.RoundRadius) + } + // 如果label不需要展示,则返回 if labelPainter == nil { continue From 32e6dd52d093ff3411bab31f2bed1357ddc29f48 Mon Sep 17 00:00:00 2001 From: vicanso Date: Wed, 5 Jun 2024 21:13:03 +0800 Subject: [PATCH 68/73] refactor: export `GetRenderer` function to get chart renderer --- painter.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/painter.go b/painter.go index bc13418..2bbbe2e 100644 --- a/painter.go +++ b/painter.go @@ -860,3 +860,7 @@ func (p *Painter) LegendLineDot(box Box) *Painter { p.FillStroke() return p } + +func (p *Painter) GetRenderer() chart.Renderer { + return p.render +} From e7dc4189d5b4bae6e8cf1b10851f6766cb3d4f39 Mon Sep 17 00:00:00 2001 From: vicanso Date: Fri, 7 Jun 2024 20:35:03 +0800 Subject: [PATCH 69/73] feat: support bar margin --- bar_chart.go | 7 +++++++ chart_option.go | 2 ++ charts.go | 10 ++++++---- horizontal_bar_chart.go | 5 +++++ 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/bar_chart.go b/bar_chart.go index 508c63e..1bebc88 100644 --- a/bar_chart.go +++ b/bar_chart.go @@ -23,6 +23,7 @@ package charts import ( + "fmt" "math" "github.com/golang/freetype/truetype" @@ -63,6 +64,8 @@ type BarChartOption struct { // The legend option Legend LegendOption BarWidth int + // Margin of bar + BarMargin int } func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (Box, error) { @@ -88,6 +91,10 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B margin = 5 barMargin = 3 } + if opt.BarMargin > 0 { + barMargin = opt.BarMargin + } + fmt.Println(barMargin) seriesCount := len(seriesList) // 总的宽度-两个margin-(总数-1)的barMargin barWidth := (width - 2*margin - barMargin*(seriesCount-1)) / seriesCount diff --git a/chart_option.go b/chart_option.go index 5311d50..d80a383 100644 --- a/chart_option.go +++ b/chart_option.go @@ -67,6 +67,8 @@ type ChartOption struct { LineStrokeWidth float64 // The bar with of bar chart BarWidth int + // The margin of each bar + BarMargin int // The bar height of horizontal bar chart BarHeight int // Fill the area of line chart diff --git a/charts.go b/charts.go index 74db733..91a0048 100644 --- a/charts.go +++ b/charts.go @@ -375,10 +375,11 @@ func Render(opt ChartOption, opts ...OptionFunc) (*Painter, error) { if len(barSeriesList) != 0 { handler.Add(func() error { _, err := NewBarChart(p, BarChartOption{ - Theme: opt.theme, - Font: opt.font, - XAxis: opt.XAxis, - BarWidth: opt.BarWidth, + Theme: opt.theme, + Font: opt.font, + XAxis: opt.XAxis, + BarWidth: opt.BarWidth, + BarMargin: opt.BarMargin, }).render(renderResult, barSeriesList) return err }) @@ -391,6 +392,7 @@ func Render(opt ChartOption, opts ...OptionFunc) (*Painter, error) { Theme: opt.theme, Font: opt.font, BarHeight: opt.BarHeight, + BarMargin: opt.BarMargin, YAxisOptions: opt.YAxisOptions, }).render(renderResult, horizontalBarSeriesList) return err diff --git a/horizontal_bar_chart.go b/horizontal_bar_chart.go index ca91242..2ea97bd 100644 --- a/horizontal_bar_chart.go +++ b/horizontal_bar_chart.go @@ -50,6 +50,8 @@ type HorizontalBarChartOption struct { // The legend option Legend LegendOption BarHeight int + // Margin of bar + BarMargin int } // NewHorizontalBarChart returns a horizontal bar chart renderer @@ -81,6 +83,9 @@ func (h *horizontalBarChart) render(result *defaultRenderResult, seriesList Seri margin = 5 barMargin = 3 } + if opt.BarMargin > 0 { + barMargin = opt.BarMargin + } seriesCount := len(seriesList) // 总的高度-两个margin-(总数-1)的barMargin barHeight := (height - 2*margin - barMargin*(seriesCount-1)) / seriesCount From 5842c71b1d23147fad578d8c11e0f587c464ef71 Mon Sep 17 00:00:00 2001 From: vicanso Date: Thu, 1 Aug 2024 21:44:52 +0800 Subject: [PATCH 70/73] refactor: remove unused code --- bar_chart.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/bar_chart.go b/bar_chart.go index 1bebc88..2d702f2 100644 --- a/bar_chart.go +++ b/bar_chart.go @@ -23,7 +23,6 @@ package charts import ( - "fmt" "math" "github.com/golang/freetype/truetype" @@ -94,7 +93,6 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B if opt.BarMargin > 0 { barMargin = opt.BarMargin } - fmt.Println(barMargin) seriesCount := len(seriesList) // 总的宽度-两个margin-(总数-1)的barMargin barWidth := (width - 2*margin - barMargin*(seriesCount-1)) / seriesCount From d25a827706ac3d9c23b2676085c7d6fb845e7782 Mon Sep 17 00:00:00 2001 From: vicanso Date: Thu, 15 Aug 2024 20:37:07 +0800 Subject: [PATCH 71/73] fix: fix label position of pie, #86 --- pie_chart.go | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/pie_chart.go b/pie_chart.go index 6cc48c4..9e036c6 100644 --- a/pie_chart.go +++ b/pie_chart.go @@ -234,23 +234,35 @@ func (p *pieChart) render(result *defaultRenderResult, seriesList SeriesList) (B continue } if currentQuadrant != s.quadrant { - currentQuadrant = s.quadrant if s.quadrant == 1 { minY = cy * 2 maxY = 0 prevY = cy * 2 } if s.quadrant == 2 { - prevY = minY + if currentQuadrant != 3 { + prevY = s.lineEndY + } else { + prevY = minY + } } if s.quadrant == 3 { - minY = cy * 2 - maxY = 0 - prevY = 0 + if currentQuadrant != 4 { + prevY = s.lineEndY + } else { + minY = cy * 2 + maxY = 0 + prevY = 0 + } } if s.quadrant == 4 { - prevY = maxY + if currentQuadrant != 1 { + prevY = s.lineEndY + } else { + prevY = maxY + } } + currentQuadrant = s.quadrant } prevY = s.calculateY(prevY) if prevY > maxY { From 0eacc8e394512d513cd7fa53b8c2fe525b9b2f3c Mon Sep 17 00:00:00 2001 From: Zeni Kim Date: Tue, 13 May 2025 21:46:02 -0500 Subject: [PATCH 72/73] start migration to our packages --- README.md | 2 ++ alias.go | 4 ++-- axis.go | 2 +- axis_test.go | 2 +- bar_chart.go | 2 +- chart_option_test.go | 2 +- charts.go | 2 +- charts_test.go | 2 +- echarts.go | 2 +- echarts_test.go | 2 +- examples/painter/main.go | 2 +- examples/table/main.go | 2 +- font.go | 2 +- font_test.go | 2 +- go.mod | 12 +++++++----- go.sum | 18 ++++++++---------- grid_test.go | 2 +- horizontal_bar_chart.go | 2 +- line_chart.go | 2 +- mark_line_test.go | 2 +- mark_point_test.go | 2 +- painter.go | 2 +- painter_test.go | 4 ++-- pie_chart.go | 2 +- radar_chart.go | 4 ++-- series.go | 2 +- series_label.go | 2 +- table.go | 4 ++-- theme.go | 2 +- util.go | 4 ++-- util_test.go | 4 ++-- 31 files changed, 51 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index 1e4ea8b..4cfb004 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # go-charts +Clone from https://github.com/vicanso/go-charts + [![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/vicanso/go-charts/blob/master/LICENSE) [![Build Status](https://github.com/vicanso/go-charts/workflows/Test/badge.svg)](https://github.com/vicanso/go-charts/actions) diff --git a/alias.go b/alias.go index a96f50b..edf0dec 100644 --- a/alias.go +++ b/alias.go @@ -23,8 +23,8 @@ package charts import ( - "github.com/wcharczuk/go-chart/v2" - "github.com/wcharczuk/go-chart/v2/drawing" + "git.smarteching.com/zeni/go-chart/v2" + "git.smarteching.com/zeni/go-chart/v2/drawing" ) type Box = chart.Box diff --git a/axis.go b/axis.go index 762a6a2..55fa219 100644 --- a/axis.go +++ b/axis.go @@ -26,7 +26,7 @@ import ( "strings" "github.com/golang/freetype/truetype" - "github.com/wcharczuk/go-chart/v2" + "git.smarteching.com/zeni/go-chart/v2" ) type axisPainter struct { diff --git a/axis_test.go b/axis_test.go index d0cff41..85e18ca 100644 --- a/axis_test.go +++ b/axis_test.go @@ -26,7 +26,7 @@ import ( "testing" "github.com/stretchr/testify/assert" - "github.com/wcharczuk/go-chart/v2/drawing" + "git.smarteching.com/zeni/go-chart/v2/drawing" ) func TestAxis(t *testing.T) { diff --git a/bar_chart.go b/bar_chart.go index 2d702f2..043e044 100644 --- a/bar_chart.go +++ b/bar_chart.go @@ -26,7 +26,7 @@ import ( "math" "github.com/golang/freetype/truetype" - "github.com/wcharczuk/go-chart/v2" + "git.smarteching.com/zeni/go-chart/v2" ) type barChart struct { diff --git a/chart_option_test.go b/chart_option_test.go index ff17750..c354b26 100644 --- a/chart_option_test.go +++ b/chart_option_test.go @@ -26,7 +26,7 @@ import ( "testing" "github.com/stretchr/testify/assert" - "github.com/wcharczuk/go-chart/v2/drawing" + "git.smarteching.com/zeni/go-chart/v2/drawing" ) func TestChartOption(t *testing.T) { diff --git a/charts.go b/charts.go index 91a0048..31df11c 100644 --- a/charts.go +++ b/charts.go @@ -27,7 +27,7 @@ import ( "math" "sort" - "github.com/wcharczuk/go-chart/v2" + "git.smarteching.com/zeni/go-chart/v2" ) const labelFontSize = 10 diff --git a/charts_test.go b/charts_test.go index da75ee5..bd581e9 100644 --- a/charts_test.go +++ b/charts_test.go @@ -26,7 +26,7 @@ import ( "errors" "testing" - "github.com/wcharczuk/go-chart/v2" + "git.smarteching.com/zeni/go-chart/v2" ) func BenchmarkMultiChartPNGRender(b *testing.B) { diff --git a/echarts.go b/echarts.go index 5a0e5a0..aaef1f1 100644 --- a/echarts.go +++ b/echarts.go @@ -29,7 +29,7 @@ import ( "regexp" "strconv" - "github.com/wcharczuk/go-chart/v2" + "git.smarteching.com/zeni/go-chart/v2" ) func convertToArray(data []byte) []byte { diff --git a/echarts_test.go b/echarts_test.go index 2ce1715..2077278 100644 --- a/echarts_test.go +++ b/echarts_test.go @@ -27,7 +27,7 @@ import ( "testing" "github.com/stretchr/testify/assert" - "github.com/wcharczuk/go-chart/v2/drawing" + "git.smarteching.com/zeni/go-chart/v2/drawing" ) func TestConvertToArray(t *testing.T) { diff --git a/examples/painter/main.go b/examples/painter/main.go index b7a5832..193eb7c 100644 --- a/examples/painter/main.go +++ b/examples/painter/main.go @@ -5,7 +5,7 @@ import ( "path/filepath" charts "github.com/vicanso/go-charts/v2" - "github.com/wcharczuk/go-chart/v2/drawing" + "git.smarteching.com/zeni/go-chart/v2/drawing" ) func writeFile(buf []byte) error { diff --git a/examples/table/main.go b/examples/table/main.go index 0210ecf..036dc90 100644 --- a/examples/table/main.go +++ b/examples/table/main.go @@ -7,7 +7,7 @@ import ( "strings" "github.com/vicanso/go-charts/v2" - "github.com/wcharczuk/go-chart/v2/drawing" + "git.smarteching.com/zeni/go-chart/v2/drawing" ) func writeFile(buf []byte, filename string) error { diff --git a/font.go b/font.go index dae5141..828654e 100644 --- a/font.go +++ b/font.go @@ -27,7 +27,7 @@ import ( "sync" "github.com/golang/freetype/truetype" - "github.com/wcharczuk/go-chart/v2/roboto" + "git.smarteching.com/zeni/go-chart/v2/roboto" ) var fonts = sync.Map{} diff --git a/font_test.go b/font_test.go index 9dc731c..e0c56b2 100644 --- a/font_test.go +++ b/font_test.go @@ -26,7 +26,7 @@ import ( "testing" "github.com/stretchr/testify/assert" - "github.com/wcharczuk/go-chart/v2/roboto" + "git.smarteching.com/zeni/go-chart/v2/roboto" ) func TestInstallFont(t *testing.T) { diff --git a/go.mod b/go.mod index d8a492c..b984e59 100644 --- a/go.mod +++ b/go.mod @@ -1,17 +1,19 @@ -module github.com/vicanso/go-charts/v2 +module git.smarteching.com/zeni/go-charts/v2 -go 1.17 +go 1.24.1 require ( + git.smarteching.com/zeni/go-chart/v2 v2.1.4 github.com/dustin/go-humanize v1.0.1 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 - github.com/stretchr/testify v1.8.2 - github.com/wcharczuk/go-chart/v2 v2.1.0 + github.com/stretchr/testify v1.10.0 + github.com/vicanso/go-charts/v2 v2.6.10 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 // indirect + github.com/wcharczuk/go-chart/v2 v2.1.0 // indirect + golang.org/x/image v0.21.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index ac1d9f7..e7a75a8 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,5 @@ -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +git.smarteching.com/zeni/go-chart/v2 v2.1.4 h1:pF06+F6eqJLIG8uMiTVPR5TygPGMjM/FHMzTxmu5V/Q= +git.smarteching.com/zeni/go-chart/v2 v2.1.4/go.mod h1:b3ueW9h3pGGXyhkormZAvilHaG4+mQti+bMNPdQBeOQ= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= @@ -7,20 +8,17 @@ github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF0 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/vicanso/go-charts/v2 v2.6.10 h1:Nb2YBekEbUBPbvohnUO1oYMy31v75brUPk6n/fq+JXw= +github.com/vicanso/go-charts/v2 v2.6.10/go.mod h1:Ii2KDI3udTG1wPtiTnntzjlUBJVJTqNscMzh3oYHzUk= github.com/wcharczuk/go-chart/v2 v2.1.0 h1:tY2slqVQ6bN+yHSnDYwZebLQFkphK4WNrVwnt7CJZ2I= github.com/wcharczuk/go-chart/v2 v2.1.0/go.mod h1:yx7MvAVNcP/kN9lKXM/NTce4au4DFN99j6i1OwDclNA= -golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 h1:QelT11PB4FXiDEXucrfNckHoFxwt8USGY1ajP1ZF5lM= golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s= +golang.org/x/image v0.21.0/go.mod h1:vUbsLavqK/W303ZroQQVKQ+Af3Yl6Uz1Ppu5J/cLz78= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/grid_test.go b/grid_test.go index 3110a2b..fa9c3a6 100644 --- a/grid_test.go +++ b/grid_test.go @@ -26,7 +26,7 @@ import ( "testing" "github.com/stretchr/testify/assert" - "github.com/wcharczuk/go-chart/v2/drawing" + "git.smarteching.com/zeni/go-chart/v2/drawing" ) func TestGrid(t *testing.T) { diff --git a/horizontal_bar_chart.go b/horizontal_bar_chart.go index 2ea97bd..ed091c9 100644 --- a/horizontal_bar_chart.go +++ b/horizontal_bar_chart.go @@ -24,7 +24,7 @@ package charts import ( "github.com/golang/freetype/truetype" - "github.com/wcharczuk/go-chart/v2" + "git.smarteching.com/zeni/go-chart/v2" ) type horizontalBarChart struct { diff --git a/line_chart.go b/line_chart.go index 363cd36..fb1d16a 100644 --- a/line_chart.go +++ b/line_chart.go @@ -26,7 +26,7 @@ import ( "math" "github.com/golang/freetype/truetype" - "github.com/wcharczuk/go-chart/v2/drawing" + "git.smarteching.com/zeni/go-chart/v2/drawing" ) type lineChart struct { diff --git a/mark_line_test.go b/mark_line_test.go index 00d19ef..0448cda 100644 --- a/mark_line_test.go +++ b/mark_line_test.go @@ -26,7 +26,7 @@ import ( "testing" "github.com/stretchr/testify/assert" - "github.com/wcharczuk/go-chart/v2/drawing" + "git.smarteching.com/zeni/go-chart/v2/drawing" ) func TestMarkLine(t *testing.T) { diff --git a/mark_point_test.go b/mark_point_test.go index ffa01a7..298345b 100644 --- a/mark_point_test.go +++ b/mark_point_test.go @@ -26,7 +26,7 @@ import ( "testing" "github.com/stretchr/testify/assert" - "github.com/wcharczuk/go-chart/v2/drawing" + "git.smarteching.com/zeni/go-chart/v2/drawing" ) func TestMarkPoint(t *testing.T) { diff --git a/painter.go b/painter.go index 2bbbe2e..bee646f 100644 --- a/painter.go +++ b/painter.go @@ -28,7 +28,7 @@ import ( "math" "github.com/golang/freetype/truetype" - "github.com/wcharczuk/go-chart/v2" + "git.smarteching.com/zeni/go-chart/v2" ) type ValueFormatter func(float64) string diff --git a/painter_test.go b/painter_test.go index b159328..07c4113 100644 --- a/painter_test.go +++ b/painter_test.go @@ -28,8 +28,8 @@ import ( "github.com/golang/freetype/truetype" "github.com/stretchr/testify/assert" - "github.com/wcharczuk/go-chart/v2" - "github.com/wcharczuk/go-chart/v2/drawing" + "git.smarteching.com/zeni/go-chart/v2" + "git.smarteching.com/zeni/go-chart/v2/drawing" ) func TestPainterOption(t *testing.T) { diff --git a/pie_chart.go b/pie_chart.go index 9e036c6..5c04ed8 100644 --- a/pie_chart.go +++ b/pie_chart.go @@ -27,7 +27,7 @@ import ( "math" "github.com/golang/freetype/truetype" - "github.com/wcharczuk/go-chart/v2" + "git.smarteching.com/zeni/go-chart/v2" ) type pieChart struct { diff --git a/radar_chart.go b/radar_chart.go index f3d63b9..cf18135 100644 --- a/radar_chart.go +++ b/radar_chart.go @@ -27,8 +27,8 @@ import ( "github.com/dustin/go-humanize" "github.com/golang/freetype/truetype" - "github.com/wcharczuk/go-chart/v2" - "github.com/wcharczuk/go-chart/v2/drawing" + "git.smarteching.com/zeni/go-chart/v2" + "git.smarteching.com/zeni/go-chart/v2/drawing" ) type radarChart struct { diff --git a/series.go b/series.go index 0ad135f..da50e64 100644 --- a/series.go +++ b/series.go @@ -26,7 +26,7 @@ import ( "strings" "github.com/dustin/go-humanize" - "github.com/wcharczuk/go-chart/v2" + "git.smarteching.com/zeni/go-chart/v2" ) type SeriesData struct { diff --git a/series_label.go b/series_label.go index 10fd148..af873fc 100644 --- a/series_label.go +++ b/series_label.go @@ -24,7 +24,7 @@ package charts import ( "github.com/golang/freetype/truetype" - "github.com/wcharczuk/go-chart/v2" + "git.smarteching.com/zeni/go-chart/v2" ) type labelRenderValue struct { diff --git a/table.go b/table.go index 86ef569..3e6f273 100644 --- a/table.go +++ b/table.go @@ -26,8 +26,8 @@ import ( "errors" "github.com/golang/freetype/truetype" - "github.com/wcharczuk/go-chart/v2" - "github.com/wcharczuk/go-chart/v2/drawing" + "git.smarteching.com/zeni/go-chart/v2" + "git.smarteching.com/zeni/go-chart/v2/drawing" ) type tableChart struct { diff --git a/theme.go b/theme.go index a6d624f..85016a5 100644 --- a/theme.go +++ b/theme.go @@ -24,7 +24,7 @@ package charts import ( "github.com/golang/freetype/truetype" - "github.com/wcharczuk/go-chart/v2/drawing" + "git.smarteching.com/zeni/go-chart/v2/drawing" ) const ThemeDark = "dark" diff --git a/util.go b/util.go index b333e6d..87ff31c 100644 --- a/util.go +++ b/util.go @@ -29,8 +29,8 @@ import ( "strings" "github.com/dustin/go-humanize" - "github.com/wcharczuk/go-chart/v2" - "github.com/wcharczuk/go-chart/v2/drawing" + "git.smarteching.com/zeni/go-chart/v2" + "git.smarteching.com/zeni/go-chart/v2/drawing" ) func TrueFlag() *bool { diff --git a/util_test.go b/util_test.go index 62fd08d..5770776 100644 --- a/util_test.go +++ b/util_test.go @@ -26,8 +26,8 @@ import ( "testing" "github.com/stretchr/testify/assert" - "github.com/wcharczuk/go-chart/v2" - "github.com/wcharczuk/go-chart/v2/drawing" + "git.smarteching.com/zeni/go-chart/v2" + "git.smarteching.com/zeni/go-chart/v2/drawing" ) func TestGetDefaultInt(t *testing.T) { From 958172a1f1206cc6b676c0c02da928ce7f21cf7f Mon Sep 17 00:00:00 2001 From: Zeni Kim Date: Tue, 13 May 2025 21:53:31 -0500 Subject: [PATCH 73/73] update URL in examples --- README.md | 16 ++++++++-------- README_zh.md | 16 ++++++++-------- examples/area_line_chart/main.go | 2 +- examples/bar_chart/main.go | 2 +- examples/charts/main.go | 2 +- examples/chinese/main.go | 2 +- examples/funnel_chart/main.go | 2 +- examples/horizontal_bar_chart/main.go | 2 +- examples/line_chart/main.go | 2 +- examples/painter/main.go | 2 +- examples/pie_chart/main.go | 2 +- examples/radar_chart/main.go | 2 +- examples/table/main.go | 2 +- examples/time_line_chart/main.go | 2 +- go.mod | 2 -- go.sum | 6 ------ 16 files changed, 28 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 4cfb004..0650395 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ More examples can be found in the [./examples/](./examples/) directory. package main import ( - charts "github.com/vicanso/go-charts/v2" + charts "git.smarteching.com/zeni/go-charts/v2" ) func main() { @@ -101,7 +101,7 @@ func main() { package main import ( - "github.com/vicanso/go-charts/v2" + "git.smarteching.com/zeni/go-charts/v2" ) func main() { @@ -176,7 +176,7 @@ func main() { package main import ( - "github.com/vicanso/go-charts/v2" + "git.smarteching.com/zeni/go-charts/v2" ) func main() { @@ -233,7 +233,7 @@ func main() { package main import ( - "github.com/vicanso/go-charts/v2" + "git.smarteching.com/zeni/go-charts/v2" ) func main() { @@ -288,7 +288,7 @@ func main() { package main import ( - "github.com/vicanso/go-charts/v2" + "git.smarteching.com/zeni/go-charts/v2" ) func main() { @@ -346,7 +346,7 @@ func main() { package main import ( - "github.com/vicanso/go-charts/v2" + "git.smarteching.com/zeni/go-charts/v2" ) func main() { @@ -386,7 +386,7 @@ func main() { package main import ( - "github.com/vicanso/go-charts/v2" + "git.smarteching.com/zeni/go-charts/v2" ) func main() { @@ -451,7 +451,7 @@ func main() { package main import ( - "github.com/vicanso/go-charts/v2" + "git.smarteching.com/zeni/go-charts/v2" ) func main() { diff --git a/README_zh.md b/README_zh.md index c31cf77..3f35b97 100644 --- a/README_zh.md +++ b/README_zh.md @@ -32,7 +32,7 @@ package main import ( - charts "github.com/vicanso/go-charts/v2" + charts "git.smarteching.com/zeni/go-charts/v2" ) func main() { @@ -98,7 +98,7 @@ func main() { package main import ( - "github.com/vicanso/go-charts/v2" + "git.smarteching.com/zeni/go-charts/v2" ) func main() { @@ -173,7 +173,7 @@ func main() { package main import ( - "github.com/vicanso/go-charts/v2" + "git.smarteching.com/zeni/go-charts/v2" ) func main() { @@ -230,7 +230,7 @@ func main() { package main import ( - "github.com/vicanso/go-charts/v2" + "git.smarteching.com/zeni/go-charts/v2" ) func main() { @@ -285,7 +285,7 @@ func main() { package main import ( - "github.com/vicanso/go-charts/v2" + "git.smarteching.com/zeni/go-charts/v2" ) func main() { @@ -343,7 +343,7 @@ func main() { package main import ( - "github.com/vicanso/go-charts/v2" + "git.smarteching.com/zeni/go-charts/v2" ) func main() { @@ -383,7 +383,7 @@ func main() { package main import ( - "github.com/vicanso/go-charts/v2" + "git.smarteching.com/zeni/go-charts/v2" ) func main() { @@ -447,7 +447,7 @@ func main() { package main import ( - "github.com/vicanso/go-charts/v2" + "git.smarteching.com/zeni/go-charts/v2" ) func main() { diff --git a/examples/area_line_chart/main.go b/examples/area_line_chart/main.go index ea8f1c2..57ca1e9 100644 --- a/examples/area_line_chart/main.go +++ b/examples/area_line_chart/main.go @@ -4,7 +4,7 @@ import ( "os" "path/filepath" - "github.com/vicanso/go-charts/v2" + "git.smarteching.com/zeni/go-charts/v2" ) func writeFile(buf []byte) error { diff --git a/examples/bar_chart/main.go b/examples/bar_chart/main.go index feea66e..91c9f81 100644 --- a/examples/bar_chart/main.go +++ b/examples/bar_chart/main.go @@ -4,7 +4,7 @@ import ( "os" "path/filepath" - "github.com/vicanso/go-charts/v2" + "git.smarteching.com/zeni/go-charts/v2" ) func writeFile(buf []byte) error { diff --git a/examples/charts/main.go b/examples/charts/main.go index 76aa42c..81bc4f2 100644 --- a/examples/charts/main.go +++ b/examples/charts/main.go @@ -6,7 +6,7 @@ import ( "net/http" "strconv" - charts "github.com/vicanso/go-charts/v2" + charts "git.smarteching.com/zeni/go-charts/v2" ) var html = ` diff --git a/examples/chinese/main.go b/examples/chinese/main.go index 2d96b58..601f54e 100644 --- a/examples/chinese/main.go +++ b/examples/chinese/main.go @@ -5,7 +5,7 @@ import ( "os" "path/filepath" - "github.com/vicanso/go-charts/v2" + "git.smarteching.com/zeni/go-charts/v2" ) func writeFile(buf []byte) error { diff --git a/examples/funnel_chart/main.go b/examples/funnel_chart/main.go index f29ccf9..653f834 100644 --- a/examples/funnel_chart/main.go +++ b/examples/funnel_chart/main.go @@ -4,7 +4,7 @@ import ( "os" "path/filepath" - "github.com/vicanso/go-charts/v2" + "git.smarteching.com/zeni/go-charts/v2" ) func writeFile(buf []byte) error { diff --git a/examples/horizontal_bar_chart/main.go b/examples/horizontal_bar_chart/main.go index a7daa8c..f5d8497 100644 --- a/examples/horizontal_bar_chart/main.go +++ b/examples/horizontal_bar_chart/main.go @@ -4,7 +4,7 @@ import ( "os" "path/filepath" - "github.com/vicanso/go-charts/v2" + "git.smarteching.com/zeni/go-charts/v2" ) func writeFile(buf []byte) error { diff --git a/examples/line_chart/main.go b/examples/line_chart/main.go index 4e6448f..baee8a3 100644 --- a/examples/line_chart/main.go +++ b/examples/line_chart/main.go @@ -5,7 +5,7 @@ import ( "os" "path/filepath" - "github.com/vicanso/go-charts/v2" + "git.smarteching.com/zeni/go-charts/v2" ) func writeFile(buf []byte) error { diff --git a/examples/painter/main.go b/examples/painter/main.go index 193eb7c..1b842b3 100644 --- a/examples/painter/main.go +++ b/examples/painter/main.go @@ -4,7 +4,7 @@ import ( "os" "path/filepath" - charts "github.com/vicanso/go-charts/v2" + charts "git.smarteching.com/zeni/go-charts/v2" "git.smarteching.com/zeni/go-chart/v2/drawing" ) diff --git a/examples/pie_chart/main.go b/examples/pie_chart/main.go index 38488d2..5d70438 100644 --- a/examples/pie_chart/main.go +++ b/examples/pie_chart/main.go @@ -4,7 +4,7 @@ import ( "os" "path/filepath" - "github.com/vicanso/go-charts/v2" + "git.smarteching.com/zeni/go-charts/v2" ) func writeFile(buf []byte) error { diff --git a/examples/radar_chart/main.go b/examples/radar_chart/main.go index e8095ae..e7053af 100644 --- a/examples/radar_chart/main.go +++ b/examples/radar_chart/main.go @@ -4,7 +4,7 @@ import ( "os" "path/filepath" - "github.com/vicanso/go-charts/v2" + "git.smarteching.com/zeni/go-charts/v2" ) func writeFile(buf []byte) error { diff --git a/examples/table/main.go b/examples/table/main.go index 036dc90..de994eb 100644 --- a/examples/table/main.go +++ b/examples/table/main.go @@ -6,7 +6,7 @@ import ( "strconv" "strings" - "github.com/vicanso/go-charts/v2" + "git.smarteching.com/zeni/go-charts/v2" "git.smarteching.com/zeni/go-chart/v2/drawing" ) diff --git a/examples/time_line_chart/main.go b/examples/time_line_chart/main.go index 6cb3f3d..c6c93bf 100644 --- a/examples/time_line_chart/main.go +++ b/examples/time_line_chart/main.go @@ -8,7 +8,7 @@ import ( "path/filepath" "time" - "github.com/vicanso/go-charts/v2" + "git.smarteching.com/zeni/go-charts/v2" ) func writeFile(buf []byte) error { diff --git a/go.mod b/go.mod index b984e59..76a47b6 100644 --- a/go.mod +++ b/go.mod @@ -7,13 +7,11 @@ require ( github.com/dustin/go-humanize v1.0.1 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 github.com/stretchr/testify v1.10.0 - github.com/vicanso/go-charts/v2 v2.6.10 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/wcharczuk/go-chart/v2 v2.1.0 // indirect golang.org/x/image v0.21.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index e7a75a8..3e1a48a 100644 --- a/go.sum +++ b/go.sum @@ -10,14 +10,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/vicanso/go-charts/v2 v2.6.10 h1:Nb2YBekEbUBPbvohnUO1oYMy31v75brUPk6n/fq+JXw= -github.com/vicanso/go-charts/v2 v2.6.10/go.mod h1:Ii2KDI3udTG1wPtiTnntzjlUBJVJTqNscMzh3oYHzUk= -github.com/wcharczuk/go-chart/v2 v2.1.0 h1:tY2slqVQ6bN+yHSnDYwZebLQFkphK4WNrVwnt7CJZ2I= -github.com/wcharczuk/go-chart/v2 v2.1.0/go.mod h1:yx7MvAVNcP/kN9lKXM/NTce4au4DFN99j6i1OwDclNA= -golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s= golang.org/x/image v0.21.0/go.mod h1:vUbsLavqK/W303ZroQQVKQ+Af3Yl6Uz1Ppu5J/cLz78= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=