diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ce56fe7..22e77a8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,12 +14,12 @@ jobs: strategy: matrix: go: - - '1.22' - - '1.21' - - '1.20' - - '1.19' - '1.18' - '1.17' + - '1.16' + - '1.15' + - '1.14' + - '1.13' steps: - name: Go ${{ matrix.go }} test diff --git a/.gitignore b/.gitignore index 57206ee..2e33342 100644 --- a/.gitignore +++ b/.gitignore @@ -16,5 +16,3 @@ *.png *.svg tmp -NotoSansSC.ttf -.vscode \ No newline at end of file diff --git a/README.md b/README.md index 0650395..1e4ea8b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ # 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) @@ -35,7 +33,7 @@ More examples can be found in the [./examples/](./examples/) directory. package main import ( - charts "git.smarteching.com/zeni/go-charts/v2" + charts "github.com/vicanso/go-charts/v2" ) func main() { @@ -101,7 +99,7 @@ func main() { package main import ( - "git.smarteching.com/zeni/go-charts/v2" + "github.com/vicanso/go-charts/v2" ) func main() { @@ -176,7 +174,7 @@ func main() { package main import ( - "git.smarteching.com/zeni/go-charts/v2" + "github.com/vicanso/go-charts/v2" ) func main() { @@ -233,7 +231,7 @@ func main() { package main import ( - "git.smarteching.com/zeni/go-charts/v2" + "github.com/vicanso/go-charts/v2" ) func main() { @@ -288,7 +286,7 @@ func main() { package main import ( - "git.smarteching.com/zeni/go-charts/v2" + "github.com/vicanso/go-charts/v2" ) func main() { @@ -346,7 +344,7 @@ func main() { package main import ( - "git.smarteching.com/zeni/go-charts/v2" + "github.com/vicanso/go-charts/v2" ) func main() { @@ -386,7 +384,7 @@ func main() { package main import ( - "git.smarteching.com/zeni/go-charts/v2" + "github.com/vicanso/go-charts/v2" ) func main() { @@ -451,7 +449,7 @@ func main() { package main import ( - "git.smarteching.com/zeni/go-charts/v2" + "github.com/vicanso/go-charts/v2" ) func main() { diff --git a/README_zh.md b/README_zh.md index 3f35b97..87c42fa 100644 --- a/README_zh.md +++ b/README_zh.md @@ -32,7 +32,7 @@ package main import ( - charts "git.smarteching.com/zeni/go-charts/v2" + charts "github.com/vicanso/go-charts/v2" ) func main() { @@ -98,7 +98,7 @@ func main() { package main import ( - "git.smarteching.com/zeni/go-charts/v2" + "github.com/vicanso/go-charts/v2" ) func main() { @@ -173,7 +173,7 @@ func main() { package main import ( - "git.smarteching.com/zeni/go-charts/v2" + "github.com/vicanso/go-charts/v2" ) func main() { @@ -230,7 +230,7 @@ func main() { package main import ( - "git.smarteching.com/zeni/go-charts/v2" + "github.com/vicanso/go-charts/v2" ) func main() { @@ -285,7 +285,7 @@ func main() { package main import ( - "git.smarteching.com/zeni/go-charts/v2" + "github.com/vicanso/go-charts/v2" ) func main() { @@ -343,7 +343,7 @@ func main() { package main import ( - "git.smarteching.com/zeni/go-charts/v2" + "github.com/vicanso/go-charts/v2" ) func main() { @@ -383,7 +383,7 @@ func main() { package main import ( - "git.smarteching.com/zeni/go-charts/v2" + "github.com/vicanso/go-charts/v2" ) func main() { @@ -447,7 +447,7 @@ func main() { package main import ( - "git.smarteching.com/zeni/go-charts/v2" + "github.com/vicanso/go-charts/v2" ) func main() { @@ -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` 格式可能会加载失败,字体尽量选择Bold类型,否则生成的图片会有点模糊。 +字体文件可以在[中文字库noto-cjk](https://github.com/googlefonts/noto-cjk)下载,注意下载时选择字体格式为 `ttf` 格式,如果选用 `otf` 格式可能会加载失败。 示例见 [examples/chinese/main.go](examples/chinese/main.go) diff --git a/alias.go b/alias.go index edf0dec..a96f50b 100644 --- a/alias.go +++ b/alias.go @@ -23,8 +23,8 @@ package charts import ( - "git.smarteching.com/zeni/go-chart/v2" - "git.smarteching.com/zeni/go-chart/v2/drawing" + "github.com/wcharczuk/go-chart/v2" + "github.com/wcharczuk/go-chart/v2/drawing" ) type Box = chart.Box diff --git a/axis.go b/axis.go index 55fa219..ebc6782 100644 --- a/axis.go +++ b/axis.go @@ -26,7 +26,7 @@ import ( "strings" "github.com/golang/freetype/truetype" - "git.smarteching.com/zeni/go-chart/v2" + "github.com/wcharczuk/go-chart/v2" ) type axisPainter struct { @@ -63,8 +63,6 @@ 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 @@ -77,11 +75,6 @@ type AxisOption struct { SplitLineShow bool // The color of split line SplitLineColor Color - // The text rotation of label - TextRotation float64 - // The offset of label - LabelOffset Box - Unit int } func (a *axisPainter) Render() (Box, error) { @@ -159,30 +152,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) - // 根据文本宽度计算较为符合的展示项 - fitTextCount := ceilFloatToInt(float64(top.Width()) / textFillWidth) - - 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++ - } + 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 @@ -257,7 +235,6 @@ func (a *axisPainter) Render() (Box, error) { Length: tickLength, Unit: unit, Orient: orient, - First: opt.FirstAxis, }) p.LineStroke([]Point{ { @@ -276,19 +253,15 @@ func (a *axisPainter) Render() (Box, error) { Top: labelPaddingTop, Right: labelPaddingRight, })).MultiText(MultiTextOption{ - First: opt.FirstAxis, - Align: textAlign, - TextList: data, - Orient: orient, - Unit: unit, - Position: labelPosition, - TextRotation: opt.TextRotation, - Offset: opt.LabelOffset, + Align: textAlign, + TextList: data, + Orient: orient, + Unit: unit, + Position: labelPosition, }) // 显示辅助线 if opt.SplitLineShow { style.StrokeColor = opt.SplitLineColor - style.StrokeWidth = 1 top.OverrideDrawingStyle(style) if isVertical { x0 := p.Width() @@ -297,9 +270,7 @@ func (a *axisPainter) Render() (Box, error) { x0 = 0 x1 = top.Width() - p.Width() } - yValues := autoDivide(height, tickCount) - yValues = yValues[0 : len(yValues)-1] - for _, y := range yValues { + for _, y := range autoDivide(height, tickCount) { top.LineStroke([]Point{ { X: x0, diff --git a/axis_test.go b/axis_test.go index 85e18ca..17fe8d6 100644 --- a/axis_test.go +++ b/axis_test.go @@ -26,7 +26,7 @@ import ( "testing" "github.com/stretchr/testify/assert" - "git.smarteching.com/zeni/go-chart/v2/drawing" + "github.com/wcharczuk/go-chart/v2/drawing" ) func TestAxis(t *testing.T) { @@ -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.go b/bar_chart.go index 043e044..26f8da5 100644 --- a/bar_chart.go +++ b/bar_chart.go @@ -23,10 +23,8 @@ package charts import ( - "math" - "github.com/golang/freetype/truetype" - "git.smarteching.com/zeni/go-chart/v2" + "github.com/wcharczuk/go-chart/v2" ) type barChart struct { @@ -61,10 +59,14 @@ type BarChartOption struct { // The option of title Title TitleOption // The legend option - Legend LegendOption - BarWidth int - // Margin of bar - BarMargin int + Legend LegendOption +} + +type barChartLabelRenderOption struct { + Text string + Style Style + X int + Y int } func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (Box, error) { @@ -73,7 +75,6 @@ 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(), }) @@ -90,17 +91,9 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B margin = 5 barMargin = 3 } - if opt.BarMargin > 0 { - barMargin = opt.BarMargin - } seriesCount := len(seriesList) // 总的宽度-两个margin-(总数-1)的barMargin - barWidth := (width - 2*margin - barMargin*(seriesCount-1)) / seriesCount - if opt.BarWidth > 0 && opt.BarWidth < barWidth { - barWidth = opt.BarWidth - // 重新计算margin - margin = (width - seriesCount*barWidth - barMargin*(seriesCount-1)) / 2 - } + barWidth := (width - 2*margin - barMargin*(seriesCount-1)) / len(seriesList) barMaxHeight := seriesPainter.Height() theme := opt.Theme seriesNames := seriesList.Names() @@ -111,6 +104,7 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B markPointPainter, markLinePainter, } + labelRenderOptions := make([]barChartLabelRenderOption, 0) for index := range seriesList { series := seriesList[index] yRange := result.axisRanges[series.AxisIndex] @@ -118,18 +112,6 @@ 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 @@ -147,25 +129,14 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B } top := barMaxHeight - h - 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) - } + seriesPainter.OverrideDrawingStyle(Style{ + FillColor: fillColor, + }).Rect(chart.Box{ + Top: top, + Left: x, + Right: x + barWidth, + Bottom: barMaxHeight - 1, + }) // 用于生成marker point points[j] = Point{ // 居中的位置 @@ -179,33 +150,30 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B Y: top, } // 如果label不需要展示,则返回 - if labelPainter == nil { + if !series.Label.Show { continue } - y := barMaxHeight - h - radians := float64(0) - fontColor := series.Label.Color - if series.Label.Position == PositionBottom { - y = barMaxHeight - radians = -math.Pi / 2 - if fontColor.IsZero() { - if isLightColor(fillColor) { - fontColor = defaultLightFontColor - } else { - fontColor = defaultDarkFontColor - } - } + distance := series.Label.Distance + if distance == 0 { + distance = 5 } - labelPainter.Add(LabelValue{ - Index: index, - Value: item.Value, - X: x + barWidth>>1, - Y: y, - // 旋转 - Radians: radians, - FontColor: fontColor, - Offset: series.Label.Offset, - FontSize: series.Label.FontSize, + 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) + + labelRenderOptions = append(labelRenderOptions, barChartLabelRenderOption{ + Text: text, + Style: labelStyle, + X: x + (barWidth-textBox.Width())>>1, + Y: barMaxHeight - h - distance, }) } @@ -224,6 +192,10 @@ 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/bar_chart_test.go b/bar_chart_test.go index 654c320..f1bd688 100644 --- a/bar_chart_test.go +++ b/bar_chart_test.go @@ -102,77 +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", - }, - { - 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", + 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.go b/chart_option.go index d80a383..39de686 100644 --- a/chart_option.go +++ b/chart_option.go @@ -26,6 +26,7 @@ import ( "sort" "github.com/golang/freetype/truetype" + "github.com/wcharczuk/go-chart/v2" ) type ChartOption struct { @@ -61,24 +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 stroke width of line chart - 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 - FillArea bool - // background fill (alpha) opacity - Opacity uint8 // The child charts Children []ChartOption - // The value formatter - ValueFormatter ValueFormatter } // OptionFunc option function @@ -123,12 +108,9 @@ func TitleOptionFunc(title TitleOption) OptionFunc { } // TitleTextOptionFunc set title text of chart -func TitleTextOptionFunc(text string, subtext ...string) OptionFunc { +func TitleTextOptionFunc(text string) OptionFunc { return func(opt *ChartOption) { opt.Title.Text = text - if len(subtext) != 0 { - opt.Title.Subtext = subtext[0] - } } } @@ -273,10 +255,7 @@ func (o *ChartOption) fillDefault() { o.font, _ = GetFont(o.FontFamily) if o.font == nil { - o.font, _ = GetDefaultFont() - } else { - // 如果指定了字体,则设置主题的字体 - t.SetFont(o.font) + o.font, _ = chart.GetDefaultFont() } if o.BackgroundColor.IsZero() { o.BackgroundColor = t.GetBackgroundColor() @@ -387,11 +366,8 @@ 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, _ = GetDefaultFont() + opt.Font, _ = chart.GetDefaultFont() } p, err := NewPainter(PainterOptions{ diff --git a/chart_option_test.go b/chart_option_test.go index c354b26..1238422 100644 --- a/chart_option_test.go +++ b/chart_option_test.go @@ -26,7 +26,7 @@ import ( "testing" "github.com/stretchr/testify/assert" - "git.smarteching.com/zeni/go-chart/v2/drawing" + "github.com/wcharczuk/go-chart/v2/drawing" ) func TestChartOption(t *testing.T) { @@ -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 31df11c..36bb17e 100644 --- a/charts.go +++ b/charts.go @@ -24,10 +24,9 @@ package charts import ( "errors" - "math" "sort" - "git.smarteching.com/zeni/go-chart/v2" + "github.com/wcharczuk/go-chart/v2" ) const labelFontSize = 10 @@ -52,18 +51,6 @@ 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) } @@ -186,26 +173,21 @@ 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) + if yAxisOption.Min != nil { + min = *yAxisOption.Min + } + if yAxisOption.Max != nil { + max = *yAxisOption.Max + } r := NewRange(AxisRangeOption{ - Painter: p, - Min: min, - Max: max, + Min: min, + Max: max, // 高度需要减去x轴的高度 Size: rangeHeight, // 分隔数量 - DivideCount: divideCount, + 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 { @@ -215,16 +197,7 @@ func defaultRender(p *Painter, opt defaultRenderOption) (*defaultRenderResult, e yAxisOption.Data = r.Values() } else { yAxisOption.isCategoryAxis = true - // 由于x轴为value部分,因此计算其label单独处理 - opt.XAxis.Data = NewRange(AxisRangeOption{ - Painter: p, - Min: min, - Max: max, - // 高度需要减去x轴的高度 - Size: rangeHeight, - // 分隔数量 - DivideCount: defaultAxisDivideCount, - }).Values() + opt.XAxis.Data = r.Values() opt.XAxis.isValueAxis = true } reverseStringSlice(yAxisOption.Data) @@ -301,9 +274,6 @@ 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)) } @@ -346,8 +316,9 @@ func Render(opt ChartOption, opts ...OptionFunc) (*Painter, error) { TitleOption: opt.Title, LegendOption: opt.Legend, axisReversed: axisReversed, - // 前置已设置背景色 - backgroundIsFilled: true, + } + if isChild { + renderOpt.backgroundIsFilled = true } if len(pieSeriesList) != 0 || len(radarSeriesList) != 0 || @@ -359,10 +330,6 @@ 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 { @@ -375,11 +342,9 @@ 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, - BarMargin: opt.BarMargin, + Theme: opt.theme, + Font: opt.font, + XAxis: opt.XAxis, }).render(renderResult, barSeriesList) return err }) @@ -391,8 +356,6 @@ func Render(opt ChartOption, opts ...OptionFunc) (*Painter, error) { _, err := NewHorizontalBarChart(p, HorizontalBarChartOption{ Theme: opt.theme, Font: opt.font, - BarHeight: opt.BarHeight, - BarMargin: opt.BarMargin, YAxisOptions: opt.YAxisOptions, }).render(renderResult, horizontalBarSeriesList) return err @@ -414,13 +377,9 @@ 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, - StrokeWidth: opt.LineStrokeWidth, - FillArea: opt.FillArea, - Opacity: opt.Opacity, + Theme: opt.theme, + Font: opt.font, + XAxis: opt.XAxis, }).render(renderResult, lineSeriesList) return err }) diff --git a/charts_test.go b/charts_test.go index bd581e9..da75ee5 100644 --- a/charts_test.go +++ b/charts_test.go @@ -26,7 +26,7 @@ import ( "errors" "testing" - "git.smarteching.com/zeni/go-chart/v2" + "github.com/wcharczuk/go-chart/v2" ) func BenchmarkMultiChartPNGRender(b *testing.B) { diff --git a/echarts.go b/echarts.go index aaef1f1..fbe9a36 100644 --- a/echarts.go +++ b/echarts.go @@ -29,7 +29,7 @@ import ( "regexp" "strconv" - "git.smarteching.com/zeni/go-chart/v2" + "github.com/wcharczuk/go-chart/v2" ) func convertToArray(data []byte) []byte { @@ -344,11 +344,6 @@ 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/echarts_test.go b/echarts_test.go index 2077278..8deda2d 100644 --- a/echarts_test.go +++ b/echarts_test.go @@ -27,7 +27,7 @@ import ( "testing" "github.com/stretchr/testify/assert" - "git.smarteching.com/zeni/go-chart/v2/drawing" + "github.com/wcharczuk/go-chart/v2/drawing" ) func TestConvertToArray(t *testing.T) { @@ -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/examples/area_line_chart/main.go b/examples/area_line_chart/main.go deleted file mode 100644 index 57ca1e9..0000000 --- a/examples/area_line_chart/main.go +++ /dev/null @@ -1,73 +0,0 @@ -package main - -import ( - "os" - "path/filepath" - - "git.smarteching.com/zeni/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 = os.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/bar_chart/main.go b/examples/bar_chart/main.go index 91c9f81..c559a76 100644 --- a/examples/bar_chart/main.go +++ b/examples/bar_chart/main.go @@ -1,10 +1,11 @@ package main import ( + "io/ioutil" "os" "path/filepath" - "git.smarteching.com/zeni/go-charts/v2" + "github.com/vicanso/go-charts/v2" ) func writeFile(buf []byte) error { @@ -15,7 +16,7 @@ func writeFile(buf []byte) error { } file := filepath.Join(tmpPath, "bar-chart.png") - err = os.WriteFile(file, buf, 0600) + err = ioutil.WriteFile(file, buf, 0600) if err != nil { return err } diff --git a/examples/charts/main.go b/examples/charts/main.go index 81bc4f2..7b14919 100644 --- a/examples/charts/main.go +++ b/examples/charts/main.go @@ -2,11 +2,10 @@ package main import ( "bytes" - "fmt" "net/http" "strconv" - charts "git.smarteching.com/zeni/go-charts/v2" + charts "github.com/vicanso/go-charts/v2" ) var html = ` @@ -262,35 +261,6 @@ 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{ @@ -355,10 +325,6 @@ func indexHandler(w http.ResponseWriter, req *http.Request) { Value: 180, }, }, - Label: charts.SeriesLabel{ - Show: true, - Position: charts.PositionBottom, - }, }, }, }, @@ -1969,6 +1935,5 @@ 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/examples/chinese/main.go b/examples/chinese/main.go index 601f54e..bb7cc00 100644 --- a/examples/chinese/main.go +++ b/examples/chinese/main.go @@ -5,7 +5,7 @@ import ( "os" "path/filepath" - "git.smarteching.com/zeni/go-charts/v2" + "github.com/vicanso/go-charts/v2" ) func writeFile(buf []byte) error { @@ -16,7 +16,7 @@ func writeFile(buf []byte) error { } file := filepath.Join(tmpPath, "chinese-line-chart.png") - err = os.WriteFile(file, buf, 0600) + err = ioutil.WriteFile(file, buf, 0600) if err != nil { return err } @@ -25,8 +25,7 @@ func writeFile(buf []byte) error { func main() { // 字体文件需要自行下载 - // https://github.com/googlefonts/noto-cjk - buf, err := ioutil.ReadFile("./NotoSansSC.ttf") + buf, err := ioutil.ReadFile("../NotoSansSC.ttf") if err != nil { panic(err) } @@ -34,8 +33,6 @@ func main() { if err != nil { panic(err) } - font, _ := charts.GetFont("noto") - charts.SetDefaultFont(font) values := [][]float64{ { @@ -86,7 +83,8 @@ func main() { } p, err := charts.LineRender( values, - charts.TitleTextOptionFunc("测试"), + charts.TitleTextOptionFunc("Line"), + charts.FontFamilyOptionFunc("noto"), charts.XAxisDataOptionFunc([]string{ "星期一", "星期二", diff --git a/examples/funnel_chart/main.go b/examples/funnel_chart/main.go index 653f834..8f21db6 100644 --- a/examples/funnel_chart/main.go +++ b/examples/funnel_chart/main.go @@ -1,10 +1,11 @@ package main import ( + "io/ioutil" "os" "path/filepath" - "git.smarteching.com/zeni/go-charts/v2" + "github.com/vicanso/go-charts/v2" ) func writeFile(buf []byte) error { @@ -15,7 +16,7 @@ func writeFile(buf []byte) error { } file := filepath.Join(tmpPath, "funnel-chart.png") - err = os.WriteFile(file, buf, 0600) + err = ioutil.WriteFile(file, buf, 0600) if err != nil { return err } @@ -29,8 +30,6 @@ func main() { 60, 40, 20, - 10, - 0, } p, err := charts.FunnelRender( values, @@ -41,8 +40,6 @@ func main() { "Visit", "Inquiry", "Order", - "Pay", - "Cancel", }), ) if err != nil { diff --git a/examples/horizontal_bar_chart/main.go b/examples/horizontal_bar_chart/main.go index f5d8497..8b996b6 100644 --- a/examples/horizontal_bar_chart/main.go +++ b/examples/horizontal_bar_chart/main.go @@ -1,10 +1,11 @@ package main import ( + "io/ioutil" "os" "path/filepath" - "git.smarteching.com/zeni/go-charts/v2" + "github.com/vicanso/go-charts/v2" ) func writeFile(buf []byte) error { @@ -15,7 +16,7 @@ func writeFile(buf []byte) error { } file := filepath.Join(tmpPath, "horizontal-bar-chart.png") - err = os.WriteFile(file, buf, 0600) + err = ioutil.WriteFile(file, buf, 0600) if err != nil { return err } @@ -25,22 +26,20 @@ func writeFile(buf []byte) error { func main() { values := [][]float64{ { - 10, - 30, - 50, - 70, - 90, - 110, - 130, + 18203, + 23489, + 29034, + 104970, + 131744, + 630230, }, { - 20, - 40, - 60, - 80, - 100, - 120, - 140, + 19325, + 23438, + 31000, + 121594, + 134141, + 681807, }, } p, err := charts.HorizontalBarRender( @@ -57,7 +56,6 @@ func main() { "2012", }), charts.YAxisDataOptionFunc([]string{ - "UN", "Brazil", "Indonesia", "USA", @@ -65,9 +63,6 @@ func main() { "China", "World", }), - func(opt *charts.ChartOption) { - opt.SeriesList[0].RoundRadius = 5 - }, ) if err != nil { panic(err) diff --git a/examples/line_chart/main.go b/examples/line_chart/main.go index baee8a3..a941bca 100644 --- a/examples/line_chart/main.go +++ b/examples/line_chart/main.go @@ -1,11 +1,11 @@ package main import ( - "fmt" + "io/ioutil" "os" "path/filepath" - "git.smarteching.com/zeni/go-charts/v2" + "github.com/vicanso/go-charts/v2" ) func writeFile(buf []byte) error { @@ -16,7 +16,7 @@ func writeFile(buf []byte) error { } file := filepath.Join(tmpPath, "line-chart.png") - err = os.WriteFile(file, buf, 0600) + err = ioutil.WriteFile(file, buf, 0600) if err != nil { return err } @@ -29,8 +29,7 @@ func main() { 120, 132, 101, - // 134, - charts.GetNullValue(), + 134, 90, 230, 210, @@ -96,16 +95,6 @@ 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 { - return fmt.Sprintf("%.0f", f) - } }, ) diff --git a/examples/painter/main.go b/examples/painter/main.go index 1b842b3..3c31ce4 100644 --- a/examples/painter/main.go +++ b/examples/painter/main.go @@ -1,11 +1,12 @@ package main import ( + "io/ioutil" "os" "path/filepath" - charts "git.smarteching.com/zeni/go-charts/v2" - "git.smarteching.com/zeni/go-chart/v2/drawing" + charts "github.com/vicanso/go-charts/v2" + "github.com/wcharczuk/go-chart/v2/drawing" ) func writeFile(buf []byte) error { @@ -16,7 +17,7 @@ func writeFile(buf []byte) error { } file := filepath.Join(tmpPath, "painter.png") - err = os.WriteFile(file, buf, 0600) + err = ioutil.WriteFile(file, buf, 0600) if err != nil { return err } diff --git a/examples/pie_chart/main.go b/examples/pie_chart/main.go index 5d70438..3721ed1 100644 --- a/examples/pie_chart/main.go +++ b/examples/pie_chart/main.go @@ -1,10 +1,11 @@ package main import ( + "io/ioutil" "os" "path/filepath" - "git.smarteching.com/zeni/go-charts/v2" + "github.com/vicanso/go-charts/v2" ) func writeFile(buf []byte) error { @@ -15,7 +16,7 @@ func writeFile(buf []byte) error { } file := filepath.Join(tmpPath, "pie-chart.png") - err = os.WriteFile(file, buf, 0600) + err = ioutil.WriteFile(file, buf, 0600) if err != nil { return err } diff --git a/examples/radar_chart/main.go b/examples/radar_chart/main.go index e7053af..51f7409 100644 --- a/examples/radar_chart/main.go +++ b/examples/radar_chart/main.go @@ -1,10 +1,11 @@ package main import ( + "io/ioutil" "os" "path/filepath" - "git.smarteching.com/zeni/go-charts/v2" + "github.com/vicanso/go-charts/v2" ) func writeFile(buf []byte) error { @@ -15,7 +16,7 @@ func writeFile(buf []byte) error { } file := filepath.Join(tmpPath, "radar-chart.png") - err = os.WriteFile(file, buf, 0600) + err = ioutil.WriteFile(file, buf, 0600) if err != nil { return err } diff --git a/examples/table/main.go b/examples/table/main.go index de994eb..2701ec1 100644 --- a/examples/table/main.go +++ b/examples/table/main.go @@ -1,13 +1,14 @@ package main import ( + "io/ioutil" "os" "path/filepath" "strconv" "strings" - "git.smarteching.com/zeni/go-charts/v2" - "git.smarteching.com/zeni/go-chart/v2/drawing" + "github.com/vicanso/go-charts/v2" + "github.com/wcharczuk/go-chart/v2/drawing" ) func writeFile(buf []byte, filename string) error { @@ -18,7 +19,7 @@ func writeFile(buf []byte, filename string) error { } file := filepath.Join(tmpPath, filename) - err = os.WriteFile(file, buf, 0600) + err = ioutil.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 deleted file mode 100644 index c6c93bf..0000000 --- a/examples/time_line_chart/main.go +++ /dev/null @@ -1,81 +0,0 @@ -package main - -import ( - "crypto/rand" - "fmt" - "math/big" - "os" - "path/filepath" - "time" - - "git.smarteching.com/zeni/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 = os.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/font.go b/font.go index 828654e..c40b51e 100644 --- a/font.go +++ b/font.go @@ -27,18 +27,14 @@ import ( "sync" "github.com/golang/freetype/truetype" - "git.smarteching.com/zeni/go-chart/v2/roboto" + "github.com/wcharczuk/go-chart/v2/roboto" ) var fonts = sync.Map{} var ErrFontNotExists = errors.New("font is not exists") -var defaultFontFamily = "defaultFontFamily" func init() { - name := "roboto" - _ = InstallFont(name, roboto.Roboto) - font, _ := GetFont(name) - SetDefaultFont(font) + _ = InstallFont("roboto", roboto.Roboto) } // InstallFont installs the font for charts @@ -51,19 +47,6 @@ 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/font_test.go b/font_test.go index e0c56b2..9dc731c 100644 --- a/font_test.go +++ b/font_test.go @@ -26,7 +26,7 @@ import ( "testing" "github.com/stretchr/testify/assert" - "git.smarteching.com/zeni/go-chart/v2/roboto" + "github.com/wcharczuk/go-chart/v2/roboto" ) func TestInstallFont(t *testing.T) { diff --git a/funnel_chart.go b/funnel_chart.go index d4a8bdd..719853a 100644 --- a/funnel_chart.go +++ b/funnel_chart.go @@ -23,6 +23,9 @@ package charts import ( + "fmt" + + "github.com/dustin/go-humanize" "github.com/golang/freetype/truetype" ) @@ -92,23 +95,13 @@ func (f *funnelChart) render(result *defaultRenderResult, seriesList SeriesList) y := 0 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 - // 最大最小值一致则为100% - widthPercent := 100.0 - if offset != 0 { - widthPercent = (value - min) / offset - } + widthPercent := (value - min) / (max - min) w := int(widthPercent * float64(width)) widthList[index] = w - // 如果最大值为0,则占比100% - percent := 1.0 - if max != 0 { - percent = value / max - } - textList[index] = NewFunnelLabelFormatter(seriesNames, item.Label.Formatter)(index, value, percent) + p := humanize.CommafWithDigits(value/max*100, 2) + "%" + textList[index] = fmt.Sprintf("%s(%s)", item.Name, p) } for index, w := range widthList { diff --git a/go.mod b/go.mod index 76a47b6..66145c7 100644 --- a/go.mod +++ b/go.mod @@ -1,17 +1,17 @@ -module git.smarteching.com/zeni/go-charts/v2 +module github.com/vicanso/go-charts/v2 -go 1.24.1 +go 1.17 require ( - git.smarteching.com/zeni/go-chart/v2 v2.1.4 - github.com/dustin/go-humanize v1.0.1 + github.com/dustin/go-humanize v1.0.0 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 - github.com/stretchr/testify v1.10.0 + github.com/stretchr/testify v1.7.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.21.0 // indirect + golang.org/x/image v0.0.0-20220617043117-41969df76e82 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 3e1a48a..5f953b0 100644 --- a/go.sum +++ b/go.sum @@ -1,17 +1,23 @@ -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.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.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= -github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +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/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/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -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= +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/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/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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/grid_test.go b/grid_test.go index fa9c3a6..3110a2b 100644 --- a/grid_test.go +++ b/grid_test.go @@ -26,7 +26,7 @@ import ( "testing" "github.com/stretchr/testify/assert" - "git.smarteching.com/zeni/go-chart/v2/drawing" + "github.com/wcharczuk/go-chart/v2/drawing" ) func TestGrid(t *testing.T) { diff --git a/horizontal_bar_chart.go b/horizontal_bar_chart.go index ed091c9..30a9b7d 100644 --- a/horizontal_bar_chart.go +++ b/horizontal_bar_chart.go @@ -24,7 +24,7 @@ package charts import ( "github.com/golang/freetype/truetype" - "git.smarteching.com/zeni/go-chart/v2" + "github.com/wcharczuk/go-chart/v2" ) type horizontalBarChart struct { @@ -48,10 +48,7 @@ type HorizontalBarChartOption struct { // The option of title Title TitleOption // The legend option - Legend LegendOption - BarHeight int - // Margin of bar - BarMargin int + Legend LegendOption } // NewHorizontalBarChart returns a horizontal bar chart renderer @@ -83,46 +80,24 @@ 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 - if opt.BarHeight > 0 && opt.BarHeight < barHeight { - barHeight = opt.BarHeight - margin = (height - seriesCount*barHeight - barMargin*(seriesCount-1)) / 2 - } + barHeight := (height - 2*margin - barMargin*(seriesCount-1)) / len(seriesList) theme := opt.Theme max, min := seriesList.GetMaxMin(0) xRange := NewRange(AxisRangeOption{ - Painter: p, Min: min, Max: max, 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 @@ -141,57 +116,16 @@ func (h *horizontalBarChart) render(result *defaultRenderResult, seriesList Seri fillColor = item.Style.FillColor } right := w - 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 - } - labelValue := LabelValue{ - Orient: OrientHorizontal, - Index: index, - Value: item.Value, - X: right, - Y: y + barHeight>>1, - Offset: series.Label.Offset, - FontColor: series.Label.Color, - FontSize: series.Label.FontSize, - } - if series.Label.Position == PositionLeft { - labelValue.X = 0 - if labelValue.FontColor.IsZero() { - if isLightColor(fillColor) { - labelValue.FontColor = defaultLightFontColor - } else { - labelValue.FontColor = defaultDarkFontColor - } - } - } - labelPainter.Add(labelValue) + seriesPainter.OverrideDrawingStyle(Style{ + FillColor: fillColor, + }).Rect(chart.Box{ + Top: y, + Left: 0, + Right: right, + Bottom: y + barHeight, + }) } } - err := doRender(rendererList...) - if err != nil { - return BoxZero, err - } return p.box, nil } diff --git a/horizontal_bar_chart_test.go b/horizontal_bar_chart_test.go index e078c4a..5555df6 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/legend.go b/legend.go index 035642c..8f21afb 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(y0, x0) + x0 = drawIcon(0, x0) } if opt.Orient == OrientVertical { y0 += offset diff --git a/line_chart.go b/line_chart.go index fb1d16a..0770447 100644 --- a/line_chart.go +++ b/line_chart.go @@ -23,10 +23,8 @@ package charts import ( - "math" - "github.com/golang/freetype/truetype" - "git.smarteching.com/zeni/go-chart/v2/drawing" + "github.com/wcharczuk/go-chart/v2/drawing" ) type lineChart struct { @@ -62,16 +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 - // The stroke width of line - StrokeWidth float64 - // Fill the area of line - FillArea bool // background is filled backgroundIsFilled bool - // background fill (alpha) opacity - Opacity uint8 } func (l *lineChart) render(result *defaultRenderResult, seriesList SeriesList) (Box, error) { @@ -103,82 +93,25 @@ func (l *lineChart) render(result *defaultRenderResult, seriesList SeriesList) ( markPointPainter, markLinePainter, } - strokeWidth := opt.StrokeWidth - if strokeWidth == 0 { - strokeWidth = defaultStrokeWidth - } - seriesNames := seriesList.Names() for index := range seriesList { series := seriesList[index] seriesColor := opt.Theme.GetSeriesColor(series.index) drawingStyle := Style{ StrokeColor: seriesColor, - StrokeWidth: strokeWidth, - } - if len(series.Style.StrokeDashArray) > 0 { - drawingStyle.StrokeDashArray = series.Style.StrokeDashArray + StrokeWidth: defaultStrokeWidth, } + seriesPainter.SetDrawingStyle(drawingStyle) 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 { - h = int(math.MaxInt32) - } p := Point{ X: xValues[i], Y: h, } points = append(points, p) - - // 如果label不需要展示,则返回 - if labelPainter == nil { - continue - } - labelPainter.Add(LabelValue{ - Index: index, - Value: item.Value, - X: p.X, - Y: p.Y, - // 字体大小 - FontSize: series.Label.FontSize, - }) } - // 如果需要填充区域 - if opt.FillArea { - 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, - }, Point{ - X: areaPoints[0].X, - Y: bottomY, - }, areaPoints[0]) - seriesPainter.SetDrawingStyle(Style{ - FillColor: seriesColor.WithAlpha(opacity), - }) - seriesPainter.FillArea(areaPoints) - } - seriesPainter.SetDrawingStyle(drawingStyle) - // 画线 seriesPainter.LineStroke(points) @@ -190,9 +123,7 @@ func (l *lineChart) render(result *defaultRenderResult, seriesList SeriesList) ( } drawingStyle.StrokeWidth = 1 seriesPainter.SetDrawingStyle(drawingStyle) - if !isFalse(opt.SymbolShow) { - seriesPainter.Dots(points) - } + seriesPainter.Dots(points) markPointPainter.Add(markPointRenderOption{ FillColor: seriesColor, Font: opt.Font, diff --git a/line_chart_test.go b/line_chart_test.go index e169f90..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/mark_line.go b/mark_line.go index bc850bb..af1062d 100644 --- a/mark_line.go +++ b/mark_line.go @@ -24,6 +24,7 @@ package charts import ( "github.com/golang/freetype/truetype" + "github.com/wcharczuk/go-chart/v2" ) // NewMarkLine returns a series mark line @@ -74,7 +75,7 @@ func (m *markLinePainter) Render() (Box, error) { } font := opt.Font if font == nil { - font, _ = GetDefaultFont() + font, _ = chart.GetDefaultFont() } summary := s.Summary() for _, markLine := range s.MarkLine.Data { diff --git a/mark_line_test.go b/mark_line_test.go index 0448cda..84152ce 100644 --- a/mark_line_test.go +++ b/mark_line_test.go @@ -26,7 +26,7 @@ import ( "testing" "github.com/stretchr/testify/assert" - "git.smarteching.com/zeni/go-chart/v2/drawing" + "github.com/wcharczuk/go-chart/v2/drawing" ) func TestMarkLine(t *testing.T) { @@ -55,7 +55,6 @@ func TestMarkLine(t *testing.T) { StrokeColor: drawing.ColorBlack, Series: series, Range: NewRange(AxisRangeOption{ - Painter: p, Min: 0, Max: 5, Size: p.Height(), @@ -68,7 +67,7 @@ func TestMarkLine(t *testing.T) { } return p.Bytes() }, - result: "\\n321", + result: "\\n321", }, } for _, tt := range tests { diff --git a/mark_point.go b/mark_point.go index fd8a88b..f6c93f3 100644 --- a/mark_point.go +++ b/mark_point.go @@ -24,6 +24,7 @@ package charts import ( "github.com/golang/freetype/truetype" + "github.com/wcharczuk/go-chart/v2/drawing" ) // NewMarkPoint returns a series mark point @@ -77,15 +78,16 @@ 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/mark_point_test.go b/mark_point_test.go index 298345b..ffa01a7 100644 --- a/mark_point_test.go +++ b/mark_point_test.go @@ -26,7 +26,7 @@ import ( "testing" "github.com/stretchr/testify/assert" - "git.smarteching.com/zeni/go-chart/v2/drawing" + "github.com/wcharczuk/go-chart/v2/drawing" ) func TestMarkPoint(t *testing.T) { diff --git a/painter.go b/painter.go index bee646f..1a954e2 100644 --- a/painter.go +++ b/painter.go @@ -28,11 +28,9 @@ import ( "math" "github.com/golang/freetype/truetype" - "git.smarteching.com/zeni/go-chart/v2" + "github.com/wcharczuk/go-chart/v2" ) -type ValueFormatter func(float64) string - type Painter struct { render chart.Renderer box Box @@ -41,8 +39,7 @@ type Painter struct { style Style theme ColorPalette // 类型 - outputType string - valueFormatter ValueFormatter + outputType string } type PainterOptions struct { @@ -59,8 +56,6 @@ type PainterOptions struct { type PainterOption func(*Painter) type TicksOption struct { - // the first tick - First int Length int Orient string Count int @@ -73,11 +68,6 @@ type MultiTextOption struct { Unit int Position string Align string - // The text rotation of label - TextRotation float64 - Offset Box - // The first text index - First int } type GridOption struct { @@ -156,7 +146,7 @@ func NewPainter(opts PainterOptions, opt ...PainterOption) (*Painter, error) { } font := opts.Font if font == nil { - f, err := GetDefaultFont() + f, err := chart.GetDefaultFont() if err != nil { return nil, err } @@ -198,9 +188,6 @@ 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, @@ -451,18 +438,11 @@ 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 y == int(math.MaxInt32) { - p.Stroke() - shouldMoveTo = true - continue - } - if shouldMoveTo || index == 0 { + if index == 0 { p.MoveTo(x, y) - shouldMoveTo = false } else { p.LineTo(x, y) } @@ -565,19 +545,6 @@ 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+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() -} - func (p *Painter) TextFit(body string, x, y, width int, textAligns ...string) chart.Box { style := p.style textWarp := style.TextWrap @@ -620,7 +587,6 @@ func (p *Painter) Ticks(opt TicksOption) *Painter { return p } count := opt.Count - first := opt.First width := p.Width() height := p.Height() unit := 1 @@ -635,10 +601,7 @@ func (p *Painter) Ticks(opt TicksOption) *Painter { values = autoDivide(width, count) } for index, value := range values { - if index < first { - continue - } - if (index-first)%unit != 0 { + if index%unit != 0 { continue } if isVertical { @@ -693,19 +656,10 @@ 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 index < opt.First { + if opt.Unit != 0 && index%opt.Unit != showIndex { continue } - if opt.Unit != 0 && (index-opt.First)%opt.Unit != showIndex { - continue - } - if isTextRotation { - p.ClearTextRotation() - p.SetTextRotation(opt.TextRotation) - } box := p.MeasureText(text) start := values[index] if positionCenter { @@ -726,13 +680,8 @@ func (p *Painter) MultiText(opt MultiTextOption) *Painter { } else { x = start - box.Width()>>1 } - x += offset.Left - y += offset.Top p.Text(text, x, y) } - if isTextRotation { - p.ClearTextRotation() - } return p } @@ -803,48 +752,6 @@ 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() @@ -860,7 +767,3 @@ func (p *Painter) LegendLineDot(box Box) *Painter { p.FillStroke() return p } - -func (p *Painter) GetRenderer() chart.Renderer { - return p.render -} diff --git a/painter_test.go b/painter_test.go index 07c4113..96e41ef 100644 --- a/painter_test.go +++ b/painter_test.go @@ -28,8 +28,8 @@ import ( "github.com/golang/freetype/truetype" "github.com/stretchr/testify/assert" - "git.smarteching.com/zeni/go-chart/v2" - "git.smarteching.com/zeni/go-chart/v2/drawing" + "github.com/wcharczuk/go-chart/v2" + "github.com/wcharczuk/go-chart/v2/drawing" ) func TestPainterOption(t *testing.T) { @@ -343,29 +343,6 @@ 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{ @@ -374,7 +351,7 @@ func TestPainterTextFit(t *testing.T) { Type: ChartOutputSVG, }) assert.Nil(err) - f, _ := GetDefaultFont() + f, _ := chart.GetDefaultFont() style := Style{ FontSize: 12, FontColor: chart.ColorBlack, diff --git a/pie_chart.go b/pie_chart.go index 5c04ed8..0075ffc 100644 --- a/pie_chart.go +++ b/pie_chart.go @@ -27,7 +27,7 @@ import ( "math" "github.com/golang/freetype/truetype" - "git.smarteching.com/zeni/go-chart/v2" + "github.com/wcharczuk/go-chart/v2" ) type pieChart struct { @@ -63,96 +63,6 @@ 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 + value/2) / 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 - 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)) @@ -191,103 +101,79 @@ func (p *pieChart) render(result *defaultRenderResult, seriesList SeriesList) (B theme := opt.Theme currentValue := float64(0) - - var quadrant1, quadrant2, quadrant3, quadrant4 []sector + prevEndX := 0 + prevEndY := 0 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) - } - } - 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...) - - currentQuadrant := 0 - prevY := 0 - maxY := 0 - minY := 0 - for _, s := range sectors { seriesPainter.OverrideDrawingStyle(Style{ StrokeWidth: 1, - StrokeColor: s.color, - FillColor: s.color, + StrokeColor: theme.GetSeriesColor(index), + FillColor: theme.GetSeriesColor(index), }) - 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 { + 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 { continue } - if currentQuadrant != s.quadrant { - if s.quadrant == 1 { - minY = cy * 2 - maxY = 0 - prevY = cy * 2 - } - if s.quadrant == 2 { - if currentQuadrant != 3 { - prevY = s.lineEndY - } else { - prevY = minY - } - } - if s.quadrant == 3 { - if currentQuadrant != 4 { - prevY = s.lineEndY - } else { - minY = cy * 2 - maxY = 0 - prevY = 0 - } - } - if s.quadrant == 4 { - if currentQuadrant != 1 { - prevY = s.lineEndY - } else { - prevY = maxY - } - } - currentQuadrant = s.quadrant + + // 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坐标位置 + if index != 0 && + math.Abs(float64(endx-prevEndX)) < labelFontSize && + math.Abs(float64(endy-prevEndY)) < labelFontSize { + endy -= (labelFontSize << 1) } - prevY = s.calculateY(prevY) - if prevY > maxY { - maxY = prevY + prevEndX = endx + prevEndY = endy + + seriesPainter.MoveTo(startx, starty) + seriesPainter.LineTo(endx, endy) + offset := labelLineWidth + if endx < cx { + offset *= -1 } - 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.MoveTo(endx, endy) + endx += offset + seriesPainter.LineTo(endx, endy) seriesPainter.Stroke() + textStyle := Style{ FontColor: theme.GetTextColor(), FontSize: labelFontSize, Font: opt.Font, } - if !s.series.Label.Color.IsZero() { - textStyle.FontColor = s.series.Label.Color + if !series.Label.Color.IsZero() { + textStyle.FontColor = series.Label.Color } seriesPainter.OverrideTextStyle(textStyle) - x, y := s.calculateTextXY(seriesPainter.MeasureText(s.label)) - seriesPainter.Text(s.label, x, y) + 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) } + return p.p.box, nil } diff --git a/pie_chart_test.go b/pie_chart_test.go index 3795d32..c373a7e 100644 --- a/pie_chart_test.go +++ b/pie_chart_test.go @@ -23,7 +23,6 @@ package charts import ( - "strconv" "testing" "github.com/stretchr/testify/assert" @@ -99,435 +98,3 @@ 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 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 { - 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)) - } -} - -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)) - } -} diff --git a/radar_chart.go b/radar_chart.go index cf18135..eab70d5 100644 --- a/radar_chart.go +++ b/radar_chart.go @@ -25,10 +25,9 @@ package charts import ( "errors" - "github.com/dustin/go-humanize" "github.com/golang/freetype/truetype" - "git.smarteching.com/zeni/go-chart/v2" - "git.smarteching.com/zeni/go-chart/v2/drawing" + "github.com/wcharczuk/go-chart/v2" + "github.com/wcharczuk/go-chart/v2/drawing" ) type radarChart struct { @@ -201,11 +200,7 @@ func (r *radarChart) render(result *defaultRenderResult, seriesList SeriesList) continue } indicator := indicators[j] - var percent float64 - offset := indicator.Max - indicator.Min - if offset > 0 { - percent = (item.Value - indicator.Min) / offset - } + percent := (item.Value - indicator.Min) / (indicator.Max - indicator.Min) r := percent * radius p := getPolygonPoint(center, r, angles[j]) linePoints = append(linePoints, p) @@ -231,15 +226,9 @@ func (r *radarChart) render(result *defaultRenderResult, seriesList SeriesList) StrokeColor: color, FillColor: dotFillColor, }) - for index, point := range linePoints { + for _, 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) - } - } } diff --git a/range.go b/range.go index ec64c2d..579a77f 100644 --- a/range.go +++ b/range.go @@ -29,7 +29,6 @@ import ( const defaultAxisDivideCount = 6 type axisRange struct { - p *Painter divideCount int min float64 max float64 @@ -38,7 +37,6 @@ type axisRange struct { } type AxisRangeOption struct { - Painter *Painter // The min value of axis Min float64 // The max value of axis @@ -62,10 +60,7 @@ func NewRange(opt AxisRangeOption) axisRange { r := math.Abs(max - min) // 最小单位计算 - unit := 1 - if r > 5 { - unit = 2 - } + unit := 2 if r > 10 { unit = 4 } @@ -90,12 +85,7 @@ func NewRange(opt AxisRangeOption) axisRange { } } max = min + float64(unit*divideCount) - expectMax := opt.Max * 2 - if max > expectMax { - max = float64(ceilFloatToInt(expectMax)) - } return axisRange{ - p: opt.Painter, divideCount: divideCount, min: min, max: max, @@ -108,22 +98,15 @@ 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 := formatter(v) + value := commafWithDigits(v) values = append(values, value) } return values } 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)) } diff --git a/series.go b/series.go index da50e64..ea71869 100644 --- a/series.go +++ b/series.go @@ -26,7 +26,7 @@ import ( "strings" "github.com/dustin/go-humanize" - "git.smarteching.com/zeni/go-chart/v2" + "github.com/wcharczuk/go-chart/v2" ) type SeriesData struct { @@ -79,12 +79,6 @@ type SeriesLabel struct { Show bool // Distance to the host graphic element. Distance int - // The position of label - Position string - // The offset of label's position - Offset Box - // The font size of label - FontSize float64 } const ( @@ -126,8 +120,6 @@ 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 @@ -173,10 +165,6 @@ 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 } @@ -281,14 +269,6 @@ 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 { diff --git a/series_label.go b/series_label.go deleted file mode 100644 index af873fc..0000000 --- a/series_label.go +++ /dev/null @@ -1,148 +0,0 @@ -// 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/golang/freetype/truetype" - "git.smarteching.com/zeni/go-chart/v2" -) - -type labelRenderValue struct { - Text string - Style Style - X int - Y int - // 旋转 - Radians float64 -} - -type LabelValue struct { - Index int - Value float64 - X int - Y int - // 旋转 - Radians float64 - // 字体颜色 - FontColor Color - // 字体大小 - FontSize float64 - Orient string - Offset Box -} - -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: params.P, - seriesNames: params.SeriesNames, - label: ¶ms.Label, - theme: params.Theme, - font: params.Font, - values: make([]labelRenderValue, 0), - } -} - -func (o *SeriesLabelPainter) Add(value LabelValue) { - 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 value.FontSize != 0 { - labelStyle.FontSize = value.FontSize - } - if !value.FontColor.IsZero() { - label.Color = value.FontColor - } - if !label.Color.IsZero() { - labelStyle.FontColor = label.Color - } - p := o.p - p.OverrideDrawingStyle(labelStyle) - rotated := value.Radians != 0 - if rotated { - p.SetTextRotation(value.Radians) - } - textBox := p.MeasureText(text) - renderValue := labelRenderValue{ - Text: text, - Style: labelStyle, - 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() - } else { - if textBox.Width()%2 != 0 { - renderValue.X++ - } - } - renderValue.X += value.Offset.Left - renderValue.Y += value.Offset.Top - o.values = append(o.values, renderValue) -} - -func (o *SeriesLabelPainter) Render() (Box, error) { - for _, item := range o.values { - o.p.OverrideTextStyle(item.Style) - 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 -} diff --git a/start_zh.md b/start_zh.md deleted file mode 100644 index ee8359c..0000000 --- a/start_zh.md +++ /dev/null @@ -1,254 +0,0 @@ -# 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, - }, - }, - } -}, -``` diff --git a/table.go b/table.go index 3e6f273..86ef569 100644 --- a/table.go +++ b/table.go @@ -26,8 +26,8 @@ import ( "errors" "github.com/golang/freetype/truetype" - "git.smarteching.com/zeni/go-chart/v2" - "git.smarteching.com/zeni/go-chart/v2/drawing" + "github.com/wcharczuk/go-chart/v2" + "github.com/wcharczuk/go-chart/v2/drawing" ) type tableChart struct { diff --git a/theme.go b/theme.go index 85016a5..31c3bf8 100644 --- a/theme.go +++ b/theme.go @@ -24,7 +24,8 @@ package charts import ( "github.com/golang/freetype/truetype" - "git.smarteching.com/zeni/go-chart/v2/drawing" + "github.com/wcharczuk/go-chart/v2" + "github.com/wcharczuk/go-chart/v2/drawing" ) const ThemeDark = "dark" @@ -35,19 +36,12 @@ 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 { @@ -70,25 +64,12 @@ type ThemeOption struct { SeriesColors []Color } -var palettes = map[string]*themeColorPalette{} +var palettes = map[string]ColorPalette{} 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"), @@ -260,8 +241,7 @@ func NewTheme(name string) ColorPalette { if !ok { p = palettes[ThemeLight] } - clone := *p - return &clone + return p } func (t *themeColorPalette) IsDark() bool { @@ -272,42 +252,23 @@ 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 @@ -315,18 +276,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 } - f, _ := GetDefaultFont() + f, _ := chart.GetDefaultFont() return f } - -func (t *themeColorPalette) SetFont(f *truetype.Font) { - t.font = f -} diff --git a/util.go b/util.go index 87ff31c..a33c6d2 100644 --- a/util.go +++ b/util.go @@ -29,8 +29,8 @@ import ( "strings" "github.com/dustin/go-humanize" - "git.smarteching.com/zeni/go-chart/v2" - "git.smarteching.com/zeni/go-chart/v2/drawing" + "github.com/wcharczuk/go-chart/v2" + "github.com/wcharczuk/go-chart/v2/drawing" ) func TrueFlag() *bool { @@ -160,25 +160,15 @@ 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 - if value >= T_VALUE { - return humanize.CommafWithDigits(value/T_VALUE, decimals) + "T" + m := float64(1000 * 1000) + if value >= m { + return humanize.CommafWithDigits(value/m, decimals) + "M" } - 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" + k := float64(1000) + if value >= k { + return humanize.CommafWithDigits(value/k, decimals) + "k" } return humanize.CommafWithDigits(value, decimals) } @@ -262,10 +252,3 @@ 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 5770776..7c2ab2f 100644 --- a/util_test.go +++ b/util_test.go @@ -26,8 +26,8 @@ import ( "testing" "github.com/stretchr/testify/assert" - "git.smarteching.com/zeni/go-chart/v2" - "git.smarteching.com/zeni/go-chart/v2/drawing" + "github.com/wcharczuk/go-chart/v2" + "github.com/wcharczuk/go-chart/v2/drawing" ) func TestGetDefaultInt(t *testing.T) { @@ -189,35 +189,3 @@ 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, - })) -} diff --git a/xaxis.go b/xaxis.go index 61698d7..00636a5 100644 --- a/xaxis.go +++ b/xaxis.go @@ -47,13 +47,7 @@ type XAxisOption struct { // The line color of axis StrokeColor Color // The color of label - FontColor Color - // The text rotation of label - TextRotation float64 - // The first axis - FirstAxis int - // The offset of label - LabelOffset Box + FontColor Color isValueAxis bool } @@ -87,9 +81,6 @@ func (opt *XAxisOption) ToAxisOption() AxisOption { FontColor: opt.FontColor, Show: opt.Show, SplitLineColor: opt.Theme.GetAxisSplitLineColor(), - TextRotation: opt.TextRotation, - LabelOffset: opt.LabelOffset, - FirstAxis: opt.FirstAxis, } if opt.isValueAxis { axisOpt.SplitLineShow = true diff --git a/yaxis.go b/yaxis.go index e58b7a6..eb9034c 100644 --- a/yaxis.go +++ b/yaxis.go @@ -47,11 +47,7 @@ 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 - // The flag for show axis split line, set this to true will show axis split line - SplitLineShow *bool } // NewYAxisOptions returns a y axis option @@ -91,7 +87,6 @@ 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 @@ -102,9 +97,6 @@ func (opt *YAxisOption) ToAxisOption(p *Painter) AxisOption { axisOpt.StrokeWidth = 1 axisOpt.SplitLineShow = false } - if opt.SplitLineShow != nil { - axisOpt.SplitLineShow = *opt.SplitLineShow - } return axisOpt }