Compare commits

..

No commits in common. "main" and "v2.1.1" have entirely different histories.
main ... v2.1.1

53 changed files with 289 additions and 1752 deletions

View file

@ -14,12 +14,12 @@ jobs:
strategy: strategy:
matrix: matrix:
go: go:
- '1.22'
- '1.21'
- '1.20'
- '1.19'
- '1.18' - '1.18'
- '1.17' - '1.17'
- '1.16'
- '1.15'
- '1.14'
- '1.13'
steps: steps:
- name: Go ${{ matrix.go }} test - name: Go ${{ matrix.go }} test

1
.gitignore vendored
View file

@ -17,4 +17,3 @@
*.svg *.svg
tmp tmp
NotoSansSC.ttf NotoSansSC.ttf
.vscode

View file

@ -1,7 +1,5 @@
# go-charts # 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) [![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) [![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 package main
import ( import (
charts "git.smarteching.com/zeni/go-charts/v2" charts "github.com/vicanso/go-charts/v2"
) )
func main() { func main() {
@ -101,7 +99,7 @@ func main() {
package main package main
import ( import (
"git.smarteching.com/zeni/go-charts/v2" "github.com/vicanso/go-charts/v2"
) )
func main() { func main() {
@ -176,7 +174,7 @@ func main() {
package main package main
import ( import (
"git.smarteching.com/zeni/go-charts/v2" "github.com/vicanso/go-charts/v2"
) )
func main() { func main() {
@ -233,7 +231,7 @@ func main() {
package main package main
import ( import (
"git.smarteching.com/zeni/go-charts/v2" "github.com/vicanso/go-charts/v2"
) )
func main() { func main() {
@ -288,7 +286,7 @@ func main() {
package main package main
import ( import (
"git.smarteching.com/zeni/go-charts/v2" "github.com/vicanso/go-charts/v2"
) )
func main() { func main() {
@ -346,7 +344,7 @@ func main() {
package main package main
import ( import (
"git.smarteching.com/zeni/go-charts/v2" "github.com/vicanso/go-charts/v2"
) )
func main() { func main() {
@ -386,7 +384,7 @@ func main() {
package main package main
import ( import (
"git.smarteching.com/zeni/go-charts/v2" "github.com/vicanso/go-charts/v2"
) )
func main() { func main() {
@ -451,7 +449,7 @@ func main() {
package main package main
import ( import (
"git.smarteching.com/zeni/go-charts/v2" "github.com/vicanso/go-charts/v2"
) )
func main() { func main() {

View file

@ -32,7 +32,7 @@
package main package main
import ( import (
charts "git.smarteching.com/zeni/go-charts/v2" charts "github.com/vicanso/go-charts/v2"
) )
func main() { func main() {
@ -98,7 +98,7 @@ func main() {
package main package main
import ( import (
"git.smarteching.com/zeni/go-charts/v2" "github.com/vicanso/go-charts/v2"
) )
func main() { func main() {
@ -173,7 +173,7 @@ func main() {
package main package main
import ( import (
"git.smarteching.com/zeni/go-charts/v2" "github.com/vicanso/go-charts/v2"
) )
func main() { func main() {
@ -230,7 +230,7 @@ func main() {
package main package main
import ( import (
"git.smarteching.com/zeni/go-charts/v2" "github.com/vicanso/go-charts/v2"
) )
func main() { func main() {
@ -285,7 +285,7 @@ func main() {
package main package main
import ( import (
"git.smarteching.com/zeni/go-charts/v2" "github.com/vicanso/go-charts/v2"
) )
func main() { func main() {
@ -343,7 +343,7 @@ func main() {
package main package main
import ( import (
"git.smarteching.com/zeni/go-charts/v2" "github.com/vicanso/go-charts/v2"
) )
func main() { func main() {
@ -383,7 +383,7 @@ func main() {
package main package main
import ( import (
"git.smarteching.com/zeni/go-charts/v2" "github.com/vicanso/go-charts/v2"
) )
func main() { func main() {
@ -447,7 +447,7 @@ func main() {
package main package main
import ( import (
"git.smarteching.com/zeni/go-charts/v2" "github.com/vicanso/go-charts/v2"
) )
func main() { func main() {
@ -569,7 +569,7 @@ BenchmarkMultiChartSVGRender-8 367 3356325 ns/op
默认使用的字符为`roboto`为英文字体库,因此如果需要显示中文字符需要增加中文字体库,`InstallFont`函数可添加对应的字体库,成功添加之后则指定`title.textStyle.fontFamily`即可。 默认使用的字符为`roboto`为英文字体库,因此如果需要显示中文字符需要增加中文字体库,`InstallFont`函数可添加对应的字体库,成功添加之后则指定`title.textStyle.fontFamily`即可。
在浏览器中使用`svg`时,如果指定的`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) 示例见 [examples/chinese/main.go](examples/chinese/main.go)

View file

@ -23,8 +23,8 @@
package charts package charts
import ( import (
"git.smarteching.com/zeni/go-chart/v2" "github.com/wcharczuk/go-chart/v2"
"git.smarteching.com/zeni/go-chart/v2/drawing" "github.com/wcharczuk/go-chart/v2/drawing"
) )
type Box = chart.Box type Box = chart.Box

27
axis.go
View file

@ -26,7 +26,7 @@ import (
"strings" "strings"
"github.com/golang/freetype/truetype" "github.com/golang/freetype/truetype"
"git.smarteching.com/zeni/go-chart/v2" "github.com/wcharczuk/go-chart/v2"
) )
type axisPainter struct { type axisPainter struct {
@ -63,8 +63,6 @@ type AxisOption struct {
StrokeWidth float64 StrokeWidth float64
// The length of the axis tick // The length of the axis tick
TickLength int TickLength int
// The first axis
FirstAxis int
// The margin value of label // The margin value of label
LabelMargin int LabelMargin int
// The font size of label // The font size of label
@ -77,11 +75,6 @@ type AxisOption struct {
SplitLineShow bool SplitLineShow bool
// The color of split line // The color of split line
SplitLineColor Color SplitLineColor Color
// The text rotation of label
TextRotation float64
// The offset of label
LabelOffset Box
Unit int
} }
func (a *axisPainter) Render() (Box, error) { func (a *axisPainter) Render() (Box, error) {
@ -159,31 +152,19 @@ func (a *axisPainter) Render() (Box, error) {
} }
top.SetDrawingStyle(style).OverrideTextStyle(style) top.SetDrawingStyle(style).OverrideTextStyle(style)
isTextRotation := opt.TextRotation != 0
if isTextRotation {
top.SetTextRotation(opt.TextRotation)
}
textMaxWidth, textMaxHeight := top.MeasureTextMaxWidthHeight(data) textMaxWidth, textMaxHeight := top.MeasureTextMaxWidthHeight(data)
if isTextRotation {
top.ClearTextRotation()
}
// 增加30px来计算文本展示区域 // 增加30px来计算文本展示区域
textFillWidth := float64(textMaxWidth + 20) textFillWidth := float64(textMaxWidth + 20)
// 根据文本宽度计算较为符合的展示项 // 根据文本宽度计算较为符合的展示项
fitTextCount := ceilFloatToInt(float64(top.Width()) / textFillWidth) fitTextCount := ceilFloatToInt(float64(top.Width()) / textFillWidth)
unit := opt.Unit unit := ceilFloatToInt(float64(dataCount) / float64(fitTextCount))
if unit <= 0 {
unit = ceilFloatToInt(float64(dataCount) / float64(fitTextCount))
unit = chart.MaxInt(unit, opt.SplitNumber) unit = chart.MaxInt(unit, opt.SplitNumber)
// 偶数 // 偶数
if unit%2 == 0 && dataCount%(unit+1) == 0 { if unit%2 == 0 && dataCount%(unit+1) == 0 {
unit++ unit++
} }
}
width := 0 width := 0
height := 0 height := 0
@ -257,7 +238,6 @@ func (a *axisPainter) Render() (Box, error) {
Length: tickLength, Length: tickLength,
Unit: unit, Unit: unit,
Orient: orient, Orient: orient,
First: opt.FirstAxis,
}) })
p.LineStroke([]Point{ p.LineStroke([]Point{
{ {
@ -276,14 +256,11 @@ func (a *axisPainter) Render() (Box, error) {
Top: labelPaddingTop, Top: labelPaddingTop,
Right: labelPaddingRight, Right: labelPaddingRight,
})).MultiText(MultiTextOption{ })).MultiText(MultiTextOption{
First: opt.FirstAxis,
Align: textAlign, Align: textAlign,
TextList: data, TextList: data,
Orient: orient, Orient: orient,
Unit: unit, Unit: unit,
Position: labelPosition, Position: labelPosition,
TextRotation: opt.TextRotation,
Offset: opt.LabelOffset,
}) })
// 显示辅助线 // 显示辅助线
if opt.SplitLineShow { if opt.SplitLineShow {

View file

@ -26,7 +26,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "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) { func TestAxis(t *testing.T) {

View file

@ -23,10 +23,8 @@
package charts package charts
import ( import (
"math"
"github.com/golang/freetype/truetype" "github.com/golang/freetype/truetype"
"git.smarteching.com/zeni/go-chart/v2" "github.com/wcharczuk/go-chart/v2"
) )
type barChart struct { type barChart struct {
@ -62,9 +60,13 @@ type BarChartOption struct {
Title TitleOption Title TitleOption
// The legend option // The legend option
Legend LegendOption Legend LegendOption
BarWidth int }
// Margin of bar
BarMargin int type barChartLabelRenderOption struct {
Text string
Style Style
X int
Y int
} }
func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (Box, error) { 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 seriesPainter := result.seriesPainter
xRange := NewRange(AxisRangeOption{ xRange := NewRange(AxisRangeOption{
Painter: b.p,
DivideCount: len(opt.XAxis.Data), DivideCount: len(opt.XAxis.Data),
Size: seriesPainter.Width(), Size: seriesPainter.Width(),
}) })
@ -90,17 +91,9 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B
margin = 5 margin = 5
barMargin = 3 barMargin = 3
} }
if opt.BarMargin > 0 {
barMargin = opt.BarMargin
}
seriesCount := len(seriesList) seriesCount := len(seriesList)
// 总的宽度-两个margin-(总数-1)的barMargin // 总的宽度-两个margin-(总数-1)的barMargin
barWidth := (width - 2*margin - barMargin*(seriesCount-1)) / seriesCount barWidth := (width - 2*margin - barMargin*(seriesCount-1)) / len(seriesList)
if opt.BarWidth > 0 && opt.BarWidth < barWidth {
barWidth = opt.BarWidth
// 重新计算margin
margin = (width - seriesCount*barWidth - barMargin*(seriesCount-1)) / 2
}
barMaxHeight := seriesPainter.Height() barMaxHeight := seriesPainter.Height()
theme := opt.Theme theme := opt.Theme
seriesNames := seriesList.Names() seriesNames := seriesList.Names()
@ -111,6 +104,7 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B
markPointPainter, markPointPainter,
markLinePainter, markLinePainter,
} }
labelRenderOptions := make([]barChartLabelRenderOption, 0)
for index := range seriesList { for index := range seriesList {
series := seriesList[index] series := seriesList[index]
yRange := result.axisRanges[series.AxisIndex] yRange := result.axisRanges[series.AxisIndex]
@ -118,18 +112,6 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B
divideValues := xRange.AutoDivide() divideValues := xRange.AutoDivide()
points := make([]Point, len(series.Data)) 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 { for j, item := range series.Data {
if j >= xRange.divideCount { if j >= xRange.divideCount {
continue continue
@ -147,7 +129,6 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B
} }
top := barMaxHeight - h top := barMaxHeight - h
if series.RoundRadius <= 0 {
seriesPainter.OverrideDrawingStyle(Style{ seriesPainter.OverrideDrawingStyle(Style{
FillColor: fillColor, FillColor: fillColor,
}).Rect(chart.Box{ }).Rect(chart.Box{
@ -156,16 +137,6 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B
Right: x + barWidth, Right: x + barWidth,
Bottom: barMaxHeight - 1, Bottom: barMaxHeight - 1,
}) })
} else {
seriesPainter.OverrideDrawingStyle(Style{
FillColor: fillColor,
}).RoundedRect(chart.Box{
Top: top,
Left: x,
Right: x + barWidth,
Bottom: barMaxHeight - 1,
}, series.RoundRadius)
}
// 用于生成marker point // 用于生成marker point
points[j] = Point{ points[j] = Point{
// 居中的位置 // 居中的位置
@ -179,33 +150,30 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B
Y: top, Y: top,
} }
// 如果label不需要展示则返回 // 如果label不需要展示则返回
if labelPainter == nil { if !series.Label.Show {
continue continue
} }
y := barMaxHeight - h distance := series.Label.Distance
radians := float64(0) if distance == 0 {
fontColor := series.Label.Color distance = 5
if series.Label.Position == PositionBottom {
y = barMaxHeight
radians = -math.Pi / 2
if fontColor.IsZero() {
if isLightColor(fillColor) {
fontColor = defaultLightFontColor
} else {
fontColor = defaultDarkFontColor
} }
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
} }
labelPainter.Add(LabelValue{
Index: index, textBox := seriesPainter.MeasureText(text)
Value: item.Value,
X: x + barWidth>>1, labelRenderOptions = append(labelRenderOptions, barChartLabelRenderOption{
Y: y, Text: text,
// 旋转 Style: labelStyle,
Radians: radians, X: x + (barWidth-textBox.Width())>>1,
FontColor: fontColor, Y: barMaxHeight - h - distance,
Offset: series.Label.Offset,
FontSize: series.Label.FontSize,
}) })
} }
@ -224,6 +192,10 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B
Range: yRange, Range: yRange,
}) })
} }
for _, labelOpt := range labelRenderOptions {
seriesPainter.OverrideTextStyle(labelOpt.Style)
seriesPainter.Text(labelOpt.Text, labelOpt.X, labelOpt.Y)
}
// 最大、最小的mark point // 最大、最小的mark point
err := doRender(rendererList...) err := doRender(rendererList...)
if err != nil { if err != nil {

File diff suppressed because one or more lines are too long

View file

@ -26,6 +26,7 @@ import (
"sort" "sort"
"github.com/golang/freetype/truetype" "github.com/golang/freetype/truetype"
"github.com/wcharczuk/go-chart/v2"
) )
type ChartOption struct { type ChartOption struct {
@ -65,20 +66,8 @@ type ChartOption struct {
SymbolShow *bool SymbolShow *bool
// The stroke width of line chart // The stroke width of line chart
LineStrokeWidth float64 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 // The child charts
Children []ChartOption Children []ChartOption
// The value formatter
ValueFormatter ValueFormatter
} }
// OptionFunc option function // OptionFunc option function
@ -273,7 +262,7 @@ func (o *ChartOption) fillDefault() {
o.font, _ = GetFont(o.FontFamily) o.font, _ = GetFont(o.FontFamily)
if o.font == nil { if o.font == nil {
o.font, _ = GetDefaultFont() o.font, _ = chart.GetDefaultFont()
} else { } else {
// 如果指定了字体,则设置主题的字体 // 如果指定了字体,则设置主题的字体
t.SetFont(o.font) t.SetFont(o.font)
@ -387,11 +376,8 @@ func TableOptionRender(opt TableChartOption) (*Painter, error) {
if opt.Width <= 0 { if opt.Width <= 0 {
opt.Width = defaultChartWidth opt.Width = defaultChartWidth
} }
if opt.FontFamily != "" {
opt.Font, _ = GetFont(opt.FontFamily)
}
if opt.Font == nil { if opt.Font == nil {
opt.Font, _ = GetDefaultFont() opt.Font, _ = chart.GetDefaultFont()
} }
p, err := NewPainter(PainterOptions{ p, err := NewPainter(PainterOptions{

File diff suppressed because one or more lines are too long

View file

@ -24,10 +24,9 @@ package charts
import ( import (
"errors" "errors"
"math"
"sort" "sort"
"git.smarteching.com/zeni/go-chart/v2" "github.com/wcharczuk/go-chart/v2"
) )
const labelFontSize = 10 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 { type Renderer interface {
Render() (Box, error) Render() (Box, error)
} }
@ -186,26 +173,21 @@ func defaultRender(p *Painter, opt defaultRenderOption) (*defaultRenderResult, e
if len(opt.YAxisOptions) > index { if len(opt.YAxisOptions) > index {
yAxisOption = opt.YAxisOptions[index] yAxisOption = opt.YAxisOptions[index]
} }
divideCount := yAxisOption.DivideCount
if divideCount <= 0 {
divideCount = defaultAxisDivideCount
}
max, min := opt.SeriesList.GetMaxMin(index) max, min := opt.SeriesList.GetMaxMin(index)
if yAxisOption.Min != nil {
min = *yAxisOption.Min
}
if yAxisOption.Max != nil {
max = *yAxisOption.Max
}
r := NewRange(AxisRangeOption{ r := NewRange(AxisRangeOption{
Painter: p,
Min: min, Min: min,
Max: max, Max: max,
// 高度需要减去x轴的高度 // 高度需要减去x轴的高度
Size: rangeHeight, 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 result.axisRanges[index] = r
if yAxisOption.Theme == nil { if yAxisOption.Theme == nil {
@ -215,16 +197,7 @@ func defaultRender(p *Painter, opt defaultRenderOption) (*defaultRenderResult, e
yAxisOption.Data = r.Values() yAxisOption.Data = r.Values()
} else { } else {
yAxisOption.isCategoryAxis = true yAxisOption.isCategoryAxis = true
// 由于x轴为value部分因此计算其label单独处理 opt.XAxis.Data = r.Values()
opt.XAxis.Data = NewRange(AxisRangeOption{
Painter: p,
Min: min,
Max: max,
// 高度需要减去x轴的高度
Size: rangeHeight,
// 分隔数量
DivideCount: defaultAxisDivideCount,
}).Values()
opt.XAxis.isValueAxis = true opt.XAxis.isValueAxis = true
} }
reverseStringSlice(yAxisOption.Data) reverseStringSlice(yAxisOption.Data)
@ -301,9 +274,6 @@ func Render(opt ChartOption, opts ...OptionFunc) (*Painter, error) {
opt.Parent = p opt.Parent = p
} }
p := opt.Parent p := opt.Parent
if opt.ValueFormatter != nil {
p.valueFormatter = opt.ValueFormatter
}
if !opt.Box.IsZero() { if !opt.Box.IsZero() {
p = p.Child(PainterBoxOption(opt.Box)) p = p.Child(PainterBoxOption(opt.Box))
} }
@ -359,10 +329,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) renderResult, err := defaultRender(p, renderOpt)
if err != nil { if err != nil {
@ -378,8 +344,6 @@ func Render(opt ChartOption, opts ...OptionFunc) (*Painter, error) {
Theme: opt.theme, Theme: opt.theme,
Font: opt.font, Font: opt.font,
XAxis: opt.XAxis, XAxis: opt.XAxis,
BarWidth: opt.BarWidth,
BarMargin: opt.BarMargin,
}).render(renderResult, barSeriesList) }).render(renderResult, barSeriesList)
return err return err
}) })
@ -391,8 +355,6 @@ func Render(opt ChartOption, opts ...OptionFunc) (*Painter, error) {
_, err := NewHorizontalBarChart(p, HorizontalBarChartOption{ _, err := NewHorizontalBarChart(p, HorizontalBarChartOption{
Theme: opt.theme, Theme: opt.theme,
Font: opt.font, Font: opt.font,
BarHeight: opt.BarHeight,
BarMargin: opt.BarMargin,
YAxisOptions: opt.YAxisOptions, YAxisOptions: opt.YAxisOptions,
}).render(renderResult, horizontalBarSeriesList) }).render(renderResult, horizontalBarSeriesList)
return err return err
@ -419,8 +381,6 @@ func Render(opt ChartOption, opts ...OptionFunc) (*Painter, error) {
XAxis: opt.XAxis, XAxis: opt.XAxis,
SymbolShow: opt.SymbolShow, SymbolShow: opt.SymbolShow,
StrokeWidth: opt.LineStrokeWidth, StrokeWidth: opt.LineStrokeWidth,
FillArea: opt.FillArea,
Opacity: opt.Opacity,
}).render(renderResult, lineSeriesList) }).render(renderResult, lineSeriesList)
return err return err
}) })

View file

@ -26,7 +26,7 @@ import (
"errors" "errors"
"testing" "testing"
"git.smarteching.com/zeni/go-chart/v2" "github.com/wcharczuk/go-chart/v2"
) )
func BenchmarkMultiChartPNGRender(b *testing.B) { func BenchmarkMultiChartPNGRender(b *testing.B) {

View file

@ -29,7 +29,7 @@ import (
"regexp" "regexp"
"strconv" "strconv"
"git.smarteching.com/zeni/go-chart/v2" "github.com/wcharczuk/go-chart/v2"
) )
func convertToArray(data []byte) []byte { func convertToArray(data []byte) []byte {
@ -344,11 +344,6 @@ func (esList EChartsSeriesList) ToSeriesList() SeriesList {
Data: NewSeriesDataFromValues(dataItem.Value.values), Data: NewSeriesDataFromValues(dataItem.Value.values),
Max: item.Max, Max: item.Max,
Min: item.Min, Min: item.Min,
Label: SeriesLabel{
Color: parseColor(item.Label.Color),
Show: item.Label.Show,
Distance: item.Label.Distance,
},
}) })
} }
continue continue

File diff suppressed because one or more lines are too long

View file

@ -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)
}
}

View file

@ -1,10 +1,11 @@
package main package main
import ( import (
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"git.smarteching.com/zeni/go-charts/v2" "github.com/vicanso/go-charts/v2"
) )
func writeFile(buf []byte) error { func writeFile(buf []byte) error {
@ -15,7 +16,7 @@ func writeFile(buf []byte) error {
} }
file := filepath.Join(tmpPath, "bar-chart.png") file := filepath.Join(tmpPath, "bar-chart.png")
err = os.WriteFile(file, buf, 0600) err = ioutil.WriteFile(file, buf, 0600)
if err != nil { if err != nil {
return err return err
} }

View file

@ -2,11 +2,10 @@ package main
import ( import (
"bytes" "bytes"
"fmt"
"net/http" "net/http"
"strconv" "strconv"
charts "git.smarteching.com/zeni/go-charts/v2" charts "github.com/vicanso/go-charts/v2"
) )
var html = `<!DOCTYPE html> var html = `<!DOCTYPE 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{ Title: charts.TitleOption{
@ -355,10 +325,6 @@ func indexHandler(w http.ResponseWriter, req *http.Request) {
Value: 180, Value: 180,
}, },
}, },
Label: charts.SeriesLabel{
Show: true,
Position: charts.PositionBottom,
},
}, },
}, },
}, },
@ -1969,6 +1935,5 @@ func echartsHandler(w http.ResponseWriter, req *http.Request) {
func main() { func main() {
http.HandleFunc("/", indexHandler) http.HandleFunc("/", indexHandler)
http.HandleFunc("/echarts", echartsHandler) http.HandleFunc("/echarts", echartsHandler)
fmt.Println("http://127.0.0.1:3012/")
http.ListenAndServe(":3012", nil) http.ListenAndServe(":3012", nil)
} }

View file

@ -5,7 +5,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"git.smarteching.com/zeni/go-charts/v2" "github.com/vicanso/go-charts/v2"
) )
func writeFile(buf []byte) error { func writeFile(buf []byte) error {
@ -16,7 +16,7 @@ func writeFile(buf []byte) error {
} }
file := filepath.Join(tmpPath, "chinese-line-chart.png") file := filepath.Join(tmpPath, "chinese-line-chart.png")
err = os.WriteFile(file, buf, 0600) err = ioutil.WriteFile(file, buf, 0600)
if err != nil { if err != nil {
return err return err
} }
@ -34,8 +34,6 @@ func main() {
if err != nil { if err != nil {
panic(err) panic(err)
} }
font, _ := charts.GetFont("noto")
charts.SetDefaultFont(font)
values := [][]float64{ values := [][]float64{
{ {
@ -87,6 +85,7 @@ func main() {
p, err := charts.LineRender( p, err := charts.LineRender(
values, values,
charts.TitleTextOptionFunc("测试"), charts.TitleTextOptionFunc("测试"),
charts.FontFamilyOptionFunc("noto"),
charts.XAxisDataOptionFunc([]string{ charts.XAxisDataOptionFunc([]string{
"星期一", "星期一",
"星期二", "星期二",

View file

@ -1,10 +1,11 @@
package main package main
import ( import (
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"git.smarteching.com/zeni/go-charts/v2" "github.com/vicanso/go-charts/v2"
) )
func writeFile(buf []byte) error { func writeFile(buf []byte) error {
@ -15,7 +16,7 @@ func writeFile(buf []byte) error {
} }
file := filepath.Join(tmpPath, "funnel-chart.png") file := filepath.Join(tmpPath, "funnel-chart.png")
err = os.WriteFile(file, buf, 0600) err = ioutil.WriteFile(file, buf, 0600)
if err != nil { if err != nil {
return err return err
} }
@ -29,8 +30,6 @@ func main() {
60, 60,
40, 40,
20, 20,
10,
0,
} }
p, err := charts.FunnelRender( p, err := charts.FunnelRender(
values, values,
@ -41,8 +40,6 @@ func main() {
"Visit", "Visit",
"Inquiry", "Inquiry",
"Order", "Order",
"Pay",
"Cancel",
}), }),
) )
if err != nil { if err != nil {

View file

@ -1,10 +1,11 @@
package main package main
import ( import (
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"git.smarteching.com/zeni/go-charts/v2" "github.com/vicanso/go-charts/v2"
) )
func writeFile(buf []byte) error { func writeFile(buf []byte) error {
@ -15,7 +16,7 @@ func writeFile(buf []byte) error {
} }
file := filepath.Join(tmpPath, "horizontal-bar-chart.png") file := filepath.Join(tmpPath, "horizontal-bar-chart.png")
err = os.WriteFile(file, buf, 0600) err = ioutil.WriteFile(file, buf, 0600)
if err != nil { if err != nil {
return err return err
} }
@ -25,22 +26,20 @@ func writeFile(buf []byte) error {
func main() { func main() {
values := [][]float64{ values := [][]float64{
{ {
10, 18203,
30, 23489,
50, 29034,
70, 104970,
90, 131744,
110, 630230,
130,
}, },
{ {
20, 19325,
40, 23438,
60, 31000,
80, 121594,
100, 134141,
120, 681807,
140,
}, },
} }
p, err := charts.HorizontalBarRender( p, err := charts.HorizontalBarRender(
@ -57,7 +56,6 @@ func main() {
"2012", "2012",
}), }),
charts.YAxisDataOptionFunc([]string{ charts.YAxisDataOptionFunc([]string{
"UN",
"Brazil", "Brazil",
"Indonesia", "Indonesia",
"USA", "USA",
@ -65,9 +63,6 @@ func main() {
"China", "China",
"World", "World",
}), }),
func(opt *charts.ChartOption) {
opt.SeriesList[0].RoundRadius = 5
},
) )
if err != nil { if err != nil {
panic(err) panic(err)

View file

@ -1,11 +1,11 @@
package main package main
import ( import (
"fmt" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"git.smarteching.com/zeni/go-charts/v2" "github.com/vicanso/go-charts/v2"
) )
func writeFile(buf []byte) error { func writeFile(buf []byte) error {
@ -16,7 +16,7 @@ func writeFile(buf []byte) error {
} }
file := filepath.Join(tmpPath, "line-chart.png") file := filepath.Join(tmpPath, "line-chart.png")
err = os.WriteFile(file, buf, 0600) err = ioutil.WriteFile(file, buf, 0600)
if err != nil { if err != nil {
return err return err
} }
@ -29,8 +29,7 @@ func main() {
120, 120,
132, 132,
101, 101,
// 134, 134,
charts.GetNullValue(),
90, 90,
230, 230,
210, 210,
@ -96,16 +95,8 @@ func main() {
Top: 5, Top: 5,
Bottom: 10, Bottom: 10,
} }
opt.YAxisOptions = []charts.YAxisOption{
{
SplitLineShow: charts.FalseFlag(),
},
}
opt.SymbolShow = charts.FalseFlag() opt.SymbolShow = charts.FalseFlag()
opt.LineStrokeWidth = 1 opt.LineStrokeWidth = 1
opt.ValueFormatter = func(f float64) string {
return fmt.Sprintf("%.0f", f)
}
}, },
) )

View file

@ -1,11 +1,12 @@
package main package main
import ( import (
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
charts "git.smarteching.com/zeni/go-charts/v2" charts "github.com/vicanso/go-charts/v2"
"git.smarteching.com/zeni/go-chart/v2/drawing" "github.com/wcharczuk/go-chart/v2/drawing"
) )
func writeFile(buf []byte) error { func writeFile(buf []byte) error {
@ -16,7 +17,7 @@ func writeFile(buf []byte) error {
} }
file := filepath.Join(tmpPath, "painter.png") file := filepath.Join(tmpPath, "painter.png")
err = os.WriteFile(file, buf, 0600) err = ioutil.WriteFile(file, buf, 0600)
if err != nil { if err != nil {
return err return err
} }

View file

@ -1,10 +1,11 @@
package main package main
import ( import (
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"git.smarteching.com/zeni/go-charts/v2" "github.com/vicanso/go-charts/v2"
) )
func writeFile(buf []byte) error { func writeFile(buf []byte) error {
@ -15,7 +16,7 @@ func writeFile(buf []byte) error {
} }
file := filepath.Join(tmpPath, "pie-chart.png") file := filepath.Join(tmpPath, "pie-chart.png")
err = os.WriteFile(file, buf, 0600) err = ioutil.WriteFile(file, buf, 0600)
if err != nil { if err != nil {
return err return err
} }

View file

@ -1,10 +1,11 @@
package main package main
import ( import (
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"git.smarteching.com/zeni/go-charts/v2" "github.com/vicanso/go-charts/v2"
) )
func writeFile(buf []byte) error { func writeFile(buf []byte) error {
@ -15,7 +16,7 @@ func writeFile(buf []byte) error {
} }
file := filepath.Join(tmpPath, "radar-chart.png") file := filepath.Join(tmpPath, "radar-chart.png")
err = os.WriteFile(file, buf, 0600) err = ioutil.WriteFile(file, buf, 0600)
if err != nil { if err != nil {
return err return err
} }

View file

@ -1,13 +1,14 @@
package main package main
import ( import (
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
"git.smarteching.com/zeni/go-charts/v2" "github.com/vicanso/go-charts/v2"
"git.smarteching.com/zeni/go-chart/v2/drawing" "github.com/wcharczuk/go-chart/v2/drawing"
) )
func writeFile(buf []byte, filename string) error { func writeFile(buf []byte, filename string) error {
@ -18,7 +19,7 @@ func writeFile(buf []byte, filename string) error {
} }
file := filepath.Join(tmpPath, filename) file := filepath.Join(tmpPath, filename)
err = os.WriteFile(file, buf, 0600) err = ioutil.WriteFile(file, buf, 0600)
if err != nil { if err != nil {
return err return err
} }

View file

@ -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)
}
}

21
font.go
View file

@ -27,18 +27,14 @@ import (
"sync" "sync"
"github.com/golang/freetype/truetype" "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 fonts = sync.Map{}
var ErrFontNotExists = errors.New("font is not exists") var ErrFontNotExists = errors.New("font is not exists")
var defaultFontFamily = "defaultFontFamily"
func init() { func init() {
name := "roboto" _ = InstallFont("roboto", roboto.Roboto)
_ = InstallFont(name, roboto.Roboto)
font, _ := GetFont(name)
SetDefaultFont(font)
} }
// InstallFont installs the font for charts // InstallFont installs the font for charts
@ -51,19 +47,6 @@ func InstallFont(fontFamily string, data []byte) error {
return nil 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 // GetFont get the font by font family
func GetFont(fontFamily string) (*truetype.Font, error) { func GetFont(fontFamily string) (*truetype.Font, error) {
value, ok := fonts.Load(fontFamily) value, ok := fonts.Load(fontFamily)

View file

@ -26,7 +26,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "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) { func TestInstallFont(t *testing.T) {

View file

@ -23,6 +23,9 @@
package charts package charts
import ( import (
"fmt"
"github.com/dustin/go-humanize"
"github.com/golang/freetype/truetype" "github.com/golang/freetype/truetype"
) )
@ -92,23 +95,13 @@ func (f *funnelChart) render(result *defaultRenderResult, seriesList SeriesList)
y := 0 y := 0
widthList := make([]int, len(seriesList)) widthList := make([]int, len(seriesList))
textList := make([]string, len(seriesList)) textList := make([]string, len(seriesList))
seriesNames := seriesList.Names()
offset := max - min
for index, item := range seriesList { for index, item := range seriesList {
value := item.Data[0].Value value := item.Data[0].Value
// 最大最小值一致则为100% widthPercent := (value - min) / (max - min)
widthPercent := 100.0
if offset != 0 {
widthPercent = (value - min) / offset
}
w := int(widthPercent * float64(width)) w := int(widthPercent * float64(width))
widthList[index] = w widthList[index] = w
// 如果最大值为0则占比100% p := humanize.CommafWithDigits(value/max*100, 2) + "%"
percent := 1.0 textList[index] = fmt.Sprintf("%s(%s)", item.Name, p)
if max != 0 {
percent = value / max
}
textList[index] = NewFunnelLabelFormatter(seriesNames, item.Label.Formatter)(index, value, percent)
} }
for index, w := range widthList { for index, w := range widthList {

12
go.mod
View file

@ -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 ( require (
git.smarteching.com/zeni/go-chart/v2 v2.1.4 github.com/dustin/go-humanize v1.0.0
github.com/dustin/go-humanize v1.0.1
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 github.com/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 ( require (
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // 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 gopkg.in/yaml.v3 v3.0.1 // indirect
) )

22
go.sum
View file

@ -1,17 +1,23 @@
git.smarteching.com/zeni/go-chart/v2 v2.1.4 h1:pF06+F6eqJLIG8uMiTVPR5TygPGMjM/FHMzTxmu5V/Q= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
git.smarteching.com/zeni/go-chart/v2 v2.1.4/go.mod h1:b3ueW9h3pGGXyhkormZAvilHaG4+mQti+bMNPdQBeOQ=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= 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 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= 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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
golang.org/x/image v0.21.0/go.mod h1:vUbsLavqK/W303ZroQQVKQ+Af3Yl6Uz1Ppu5J/cLz78= 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 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View file

@ -26,7 +26,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "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) { func TestGrid(t *testing.T) {

View file

@ -24,7 +24,7 @@ package charts
import ( import (
"github.com/golang/freetype/truetype" "github.com/golang/freetype/truetype"
"git.smarteching.com/zeni/go-chart/v2" "github.com/wcharczuk/go-chart/v2"
) )
type horizontalBarChart struct { type horizontalBarChart struct {
@ -49,9 +49,6 @@ type HorizontalBarChartOption struct {
Title TitleOption Title TitleOption
// The legend option // The legend option
Legend LegendOption Legend LegendOption
BarHeight int
// Margin of bar
BarMargin int
} }
// NewHorizontalBarChart returns a horizontal bar chart renderer // NewHorizontalBarChart returns a horizontal bar chart renderer
@ -83,46 +80,24 @@ func (h *horizontalBarChart) render(result *defaultRenderResult, seriesList Seri
margin = 5 margin = 5
barMargin = 3 barMargin = 3
} }
if opt.BarMargin > 0 {
barMargin = opt.BarMargin
}
seriesCount := len(seriesList) seriesCount := len(seriesList)
// 总的高度-两个margin-(总数-1)的barMargin // 总的高度-两个margin-(总数-1)的barMargin
barHeight := (height - 2*margin - barMargin*(seriesCount-1)) / seriesCount barHeight := (height - 2*margin - barMargin*(seriesCount-1)) / len(seriesList)
if opt.BarHeight > 0 && opt.BarHeight < barHeight {
barHeight = opt.BarHeight
margin = (height - seriesCount*barHeight - barMargin*(seriesCount-1)) / 2
}
theme := opt.Theme theme := opt.Theme
max, min := seriesList.GetMaxMin(0) max, min := seriesList.GetMaxMin(0)
xRange := NewRange(AxisRangeOption{ xRange := NewRange(AxisRangeOption{
Painter: p,
Min: min, Min: min,
Max: max, Max: max,
DivideCount: defaultAxisDivideCount, DivideCount: defaultAxisDivideCount,
Size: seriesPainter.Width(), Size: seriesPainter.Width(),
}) })
seriesNames := seriesList.Names()
rendererList := []Renderer{}
for index := range seriesList { for index := range seriesList {
series := seriesList[index] series := seriesList[index]
seriesColor := theme.GetSeriesColor(series.index) seriesColor := theme.GetSeriesColor(series.index)
divideValues := yRange.AutoDivide() 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 { for j, item := range series.Data {
if j >= yRange.divideCount { if j >= yRange.divideCount {
continue continue
@ -141,7 +116,6 @@ func (h *horizontalBarChart) render(result *defaultRenderResult, seriesList Seri
fillColor = item.Style.FillColor fillColor = item.Style.FillColor
} }
right := w right := w
if series.RoundRadius <= 0 {
seriesPainter.OverrideDrawingStyle(Style{ seriesPainter.OverrideDrawingStyle(Style{
FillColor: fillColor, FillColor: fillColor,
}).Rect(chart.Box{ }).Rect(chart.Box{
@ -150,47 +124,7 @@ func (h *horizontalBarChart) render(result *defaultRenderResult, seriesList Seri
Right: right, Right: right,
Bottom: y + barHeight, 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)
}
}
err := doRender(rendererList...)
if err != nil {
return BoxZero, err
} }
return p.box, nil return p.box, nil
} }

View file

@ -23,10 +23,8 @@
package charts package charts
import ( import (
"math"
"github.com/golang/freetype/truetype" "github.com/golang/freetype/truetype"
"git.smarteching.com/zeni/go-chart/v2/drawing" "github.com/wcharczuk/go-chart/v2/drawing"
) )
type lineChart struct { type lineChart struct {
@ -66,12 +64,8 @@ type LineChartOption struct {
SymbolShow *bool SymbolShow *bool
// The stroke width of line // The stroke width of line
StrokeWidth float64 StrokeWidth float64
// Fill the area of line
FillArea bool
// background is filled // background is filled
backgroundIsFilled bool backgroundIsFilled bool
// background fill (alpha) opacity
Opacity uint8
} }
func (l *lineChart) render(result *defaultRenderResult, seriesList SeriesList) (Box, error) { func (l *lineChart) render(result *defaultRenderResult, seriesList SeriesList) (Box, error) {
@ -107,7 +101,6 @@ func (l *lineChart) render(result *defaultRenderResult, seriesList SeriesList) (
if strokeWidth == 0 { if strokeWidth == 0 {
strokeWidth = defaultStrokeWidth strokeWidth = defaultStrokeWidth
} }
seriesNames := seriesList.Names()
for index := range seriesList { for index := range seriesList {
series := seriesList[index] series := seriesList[index]
seriesColor := opt.Theme.GetSeriesColor(series.index) seriesColor := opt.Theme.GetSeriesColor(series.index)
@ -115,70 +108,18 @@ func (l *lineChart) render(result *defaultRenderResult, seriesList SeriesList) (
StrokeColor: seriesColor, StrokeColor: seriesColor,
StrokeWidth: strokeWidth, StrokeWidth: strokeWidth,
} }
if len(series.Style.StrokeDashArray) > 0 {
drawingStyle.StrokeDashArray = series.Style.StrokeDashArray
}
seriesPainter.SetDrawingStyle(drawingStyle)
yRange := result.axisRanges[series.AxisIndex] yRange := result.axisRanges[series.AxisIndex]
points := make([]Point, 0) 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 { for i, item := range series.Data {
h := yRange.getRestHeight(item.Value) h := yRange.getRestHeight(item.Value)
if item.Value == nullValue {
h = int(math.MaxInt32)
}
p := Point{ p := Point{
X: xValues[i], X: xValues[i],
Y: h, Y: h,
} }
points = append(points, p) 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) seriesPainter.LineStroke(points)

View file

@ -24,6 +24,7 @@ package charts
import ( import (
"github.com/golang/freetype/truetype" "github.com/golang/freetype/truetype"
"github.com/wcharczuk/go-chart/v2"
) )
// NewMarkLine returns a series mark line // NewMarkLine returns a series mark line
@ -74,7 +75,7 @@ func (m *markLinePainter) Render() (Box, error) {
} }
font := opt.Font font := opt.Font
if font == nil { if font == nil {
font, _ = GetDefaultFont() font, _ = chart.GetDefaultFont()
} }
summary := s.Summary() summary := s.Summary()
for _, markLine := range s.MarkLine.Data { for _, markLine := range s.MarkLine.Data {

View file

@ -26,7 +26,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "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) { func TestMarkLine(t *testing.T) {
@ -55,7 +55,6 @@ func TestMarkLine(t *testing.T) {
StrokeColor: drawing.ColorBlack, StrokeColor: drawing.ColorBlack,
Series: series, Series: series,
Range: NewRange(AxisRangeOption{ Range: NewRange(AxisRangeOption{
Painter: p,
Min: 0, Min: 0,
Max: 5, Max: 5,
Size: p.Height(), Size: p.Height(),
@ -68,7 +67,7 @@ func TestMarkLine(t *testing.T) {
} }
return p.Bytes() return p.Bytes()
}, },
result: "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"600\" height=\"400\">\\n<circle cx=\"23\" cy=\"272\" r=\"3\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:rgba(0,0,0,1.0)\"/><path stroke-dasharray=\"4.0, 2.0\" d=\"\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:rgba(0,0,0,1.0)\"/><path stroke-dasharray=\"4.0, 2.0\" d=\"M 29 272\nL 562 272\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:rgba(0,0,0,1.0)\"/><path stroke-dasharray=\"4.0, 2.0\" d=\"M 562 267\nL 578 272\nL 562 277\nL 567 272\nL 562 267\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:rgba(0,0,0,1.0)\"/><text x=\"580\" y=\"276\" style=\"stroke-width:0;stroke:none;fill:rgba(0,0,0,1.0);font-size:12.8px;font-family:'Roboto Medium',sans-serif\">3</text><circle cx=\"23\" cy=\"308\" r=\"3\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:rgba(0,0,0,1.0)\"/><path stroke-dasharray=\"4.0, 2.0\" d=\"\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:rgba(0,0,0,1.0)\"/><path stroke-dasharray=\"4.0, 2.0\" d=\"M 29 308\nL 562 308\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:rgba(0,0,0,1.0)\"/><path stroke-dasharray=\"4.0, 2.0\" d=\"M 562 303\nL 578 308\nL 562 313\nL 567 308\nL 562 303\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:rgba(0,0,0,1.0)\"/><text x=\"580\" y=\"312\" style=\"stroke-width:0;stroke:none;fill:rgba(0,0,0,1.0);font-size:12.8px;font-family:'Roboto Medium',sans-serif\">2</text><circle cx=\"23\" cy=\"344\" r=\"3\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:rgba(0,0,0,1.0)\"/><path stroke-dasharray=\"4.0, 2.0\" d=\"\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:rgba(0,0,0,1.0)\"/><path stroke-dasharray=\"4.0, 2.0\" d=\"M 29 344\nL 562 344\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:rgba(0,0,0,1.0)\"/><path stroke-dasharray=\"4.0, 2.0\" d=\"M 562 339\nL 578 344\nL 562 349\nL 567 344\nL 562 339\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:rgba(0,0,0,1.0)\"/><text x=\"580\" y=\"348\" style=\"stroke-width:0;stroke:none;fill:rgba(0,0,0,1.0);font-size:12.8px;font-family:'Roboto Medium',sans-serif\">1</text></svg>", result: "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"600\" height=\"400\">\\n<circle cx=\"23\" cy=\"290\" r=\"3\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:rgba(0,0,0,1.0)\"/><path stroke-dasharray=\"4.0, 2.0\" d=\"\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:rgba(0,0,0,1.0)\"/><path stroke-dasharray=\"4.0, 2.0\" d=\"M 29 290\nL 562 290\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:rgba(0,0,0,1.0)\"/><path stroke-dasharray=\"4.0, 2.0\" d=\"M 562 285\nL 578 290\nL 562 295\nL 567 290\nL 562 285\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:rgba(0,0,0,1.0)\"/><text x=\"580\" y=\"294\" style=\"stroke-width:0;stroke:none;fill:rgba(0,0,0,1.0);font-size:12.8px;font-family:'Roboto Medium',sans-serif\">3</text><circle cx=\"23\" cy=\"320\" r=\"3\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:rgba(0,0,0,1.0)\"/><path stroke-dasharray=\"4.0, 2.0\" d=\"\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:rgba(0,0,0,1.0)\"/><path stroke-dasharray=\"4.0, 2.0\" d=\"M 29 320\nL 562 320\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:rgba(0,0,0,1.0)\"/><path stroke-dasharray=\"4.0, 2.0\" d=\"M 562 315\nL 578 320\nL 562 325\nL 567 320\nL 562 315\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:rgba(0,0,0,1.0)\"/><text x=\"580\" y=\"324\" style=\"stroke-width:0;stroke:none;fill:rgba(0,0,0,1.0);font-size:12.8px;font-family:'Roboto Medium',sans-serif\">2</text><circle cx=\"23\" cy=\"350\" r=\"3\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:rgba(0,0,0,1.0)\"/><path stroke-dasharray=\"4.0, 2.0\" d=\"\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:rgba(0,0,0,1.0)\"/><path stroke-dasharray=\"4.0, 2.0\" d=\"M 29 350\nL 562 350\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:rgba(0,0,0,1.0)\"/><path stroke-dasharray=\"4.0, 2.0\" d=\"M 562 345\nL 578 350\nL 562 355\nL 567 350\nL 562 345\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:rgba(0,0,0,1.0)\"/><text x=\"580\" y=\"354\" style=\"stroke-width:0;stroke:none;fill:rgba(0,0,0,1.0);font-size:12.8px;font-family:'Roboto Medium',sans-serif\">1</text></svg>",
}, },
} }
for _, tt := range tests { for _, tt := range tests {

View file

@ -24,6 +24,7 @@ package charts
import ( import (
"github.com/golang/freetype/truetype" "github.com/golang/freetype/truetype"
"github.com/wcharczuk/go-chart/v2/drawing"
) )
// NewMarkPoint returns a series mark point // NewMarkPoint returns a series mark point
@ -77,15 +78,16 @@ func (m *markPointPainter) Render() (Box, error) {
symbolSize = 30 symbolSize = 30
} }
textStyle := Style{ textStyle := Style{
FontColor: drawing.Color{
R: 238,
G: 238,
B: 238,
A: 255,
},
FontSize: labelFontSize, FontSize: labelFontSize,
StrokeWidth: 1, StrokeWidth: 1,
Font: opt.Font, Font: opt.Font,
} }
if isLightColor(opt.FillColor) {
textStyle.FontColor = defaultLightFontColor
} else {
textStyle.FontColor = defaultDarkFontColor
}
painter.OverrideDrawingStyle(Style{ painter.OverrideDrawingStyle(Style{
FillColor: opt.FillColor, FillColor: opt.FillColor,
}).OverrideTextStyle(textStyle) }).OverrideTextStyle(textStyle)

View file

@ -26,7 +26,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "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) { func TestMarkPoint(t *testing.T) {

View file

@ -28,11 +28,9 @@ import (
"math" "math"
"github.com/golang/freetype/truetype" "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 { type Painter struct {
render chart.Renderer render chart.Renderer
box Box box Box
@ -42,7 +40,6 @@ type Painter struct {
theme ColorPalette theme ColorPalette
// 类型 // 类型
outputType string outputType string
valueFormatter ValueFormatter
} }
type PainterOptions struct { type PainterOptions struct {
@ -59,8 +56,6 @@ type PainterOptions struct {
type PainterOption func(*Painter) type PainterOption func(*Painter)
type TicksOption struct { type TicksOption struct {
// the first tick
First int
Length int Length int
Orient string Orient string
Count int Count int
@ -73,11 +68,6 @@ type MultiTextOption struct {
Unit int Unit int
Position string Position string
Align string Align string
// The text rotation of label
TextRotation float64
Offset Box
// The first text index
First int
} }
type GridOption struct { type GridOption struct {
@ -156,7 +146,7 @@ func NewPainter(opts PainterOptions, opt ...PainterOption) (*Painter, error) {
} }
font := opts.Font font := opts.Font
if font == nil { if font == nil {
f, err := GetDefaultFont() f, err := chart.GetDefaultFont()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -198,9 +188,6 @@ func (p *Painter) setOptions(opts ...PainterOption) {
func (p *Painter) Child(opt ...PainterOption) *Painter { func (p *Painter) Child(opt ...PainterOption) *Painter {
child := &Painter{ child := &Painter{
// 格式化
valueFormatter: p.valueFormatter,
// render
render: p.render, render: p.render,
box: p.box.Clone(), box: p.box.Clone(),
font: p.font, font: p.font,
@ -451,18 +438,11 @@ func (p *Painter) MeasureTextMaxWidthHeight(textList []string) (int, int) {
} }
func (p *Painter) LineStroke(points []Point) *Painter { func (p *Painter) LineStroke(points []Point) *Painter {
shouldMoveTo := false
for index, point := range points { for index, point := range points {
x := point.X x := point.X
y := point.Y y := point.Y
if y == int(math.MaxInt32) { if index == 0 {
p.Stroke()
shouldMoveTo = true
continue
}
if shouldMoveTo || index == 0 {
p.MoveTo(x, y) p.MoveTo(x, y)
shouldMoveTo = false
} else { } else {
p.LineTo(x, y) p.LineTo(x, y)
} }
@ -565,19 +545,6 @@ func (p *Painter) Text(body string, x, y int) *Painter {
return p 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 { func (p *Painter) TextFit(body string, x, y, width int, textAligns ...string) chart.Box {
style := p.style style := p.style
textWarp := style.TextWrap textWarp := style.TextWrap
@ -620,7 +587,6 @@ func (p *Painter) Ticks(opt TicksOption) *Painter {
return p return p
} }
count := opt.Count count := opt.Count
first := opt.First
width := p.Width() width := p.Width()
height := p.Height() height := p.Height()
unit := 1 unit := 1
@ -635,10 +601,7 @@ func (p *Painter) Ticks(opt TicksOption) *Painter {
values = autoDivide(width, count) values = autoDivide(width, count)
} }
for index, value := range values { for index, value := range values {
if index < first { if index%unit != 0 {
continue
}
if (index-first)%unit != 0 {
continue continue
} }
if isVertical { if isVertical {
@ -693,19 +656,10 @@ func (p *Painter) MultiText(opt MultiTextOption) *Painter {
} else { } else {
values = autoDivide(width, count) values = autoDivide(width, count)
} }
isTextRotation := opt.TextRotation != 0
offset := opt.Offset
for index, text := range opt.TextList { for index, text := range opt.TextList {
if index < opt.First { if opt.Unit != 0 && index%opt.Unit != showIndex {
continue continue
} }
if opt.Unit != 0 && (index-opt.First)%opt.Unit != showIndex {
continue
}
if isTextRotation {
p.ClearTextRotation()
p.SetTextRotation(opt.TextRotation)
}
box := p.MeasureText(text) box := p.MeasureText(text)
start := values[index] start := values[index]
if positionCenter { if positionCenter {
@ -726,13 +680,8 @@ func (p *Painter) MultiText(opt MultiTextOption) *Painter {
} else { } else {
x = start - box.Width()>>1 x = start - box.Width()>>1
} }
x += offset.Left
y += offset.Top
p.Text(text, x, y) p.Text(text, x, y)
} }
if isTextRotation {
p.ClearTextRotation()
}
return p return p
} }
@ -803,48 +752,6 @@ func (p *Painter) Rect(box Box) *Painter {
return p 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 { func (p *Painter) LegendLineDot(box Box) *Painter {
width := box.Width() width := box.Width()
height := box.Height() height := box.Height()
@ -860,7 +767,3 @@ func (p *Painter) LegendLineDot(box Box) *Painter {
p.FillStroke() p.FillStroke()
return p return p
} }
func (p *Painter) GetRenderer() chart.Renderer {
return p.render
}

View file

@ -28,8 +28,8 @@ import (
"github.com/golang/freetype/truetype" "github.com/golang/freetype/truetype"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"git.smarteching.com/zeni/go-chart/v2" "github.com/wcharczuk/go-chart/v2"
"git.smarteching.com/zeni/go-chart/v2/drawing" "github.com/wcharczuk/go-chart/v2/drawing"
) )
func TestPainterOption(t *testing.T) { 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("<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"400\" height=\"300\">\\n<path d=\"M 15 10\nL 25 10\nL 25 10\nA 5 5 90.00 0 1 30 15\nL 30 145\nL 30 145\nA 5 5 90.00 0 1 25 150\nL 15 150\nL 15 150\nA 5 5 90.00 0 1 10 145\nL 10 15\nL 10 15\nA 5 5 90.00 0 1 15 10\nZ\" style=\"stroke-width:1;stroke:rgba(255,255,255,1.0);fill:rgba(255,255,255,1.0)\"/><path d=\"\" style=\"stroke-width:1;stroke:rgba(255,255,255,1.0);fill:rgba(255,255,255,1.0)\"/></svg>", string(buf))
}
func TestPainterTextFit(t *testing.T) { func TestPainterTextFit(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
p, err := NewPainter(PainterOptions{ p, err := NewPainter(PainterOptions{
@ -374,7 +351,7 @@ func TestPainterTextFit(t *testing.T) {
Type: ChartOutputSVG, Type: ChartOutputSVG,
}) })
assert.Nil(err) assert.Nil(err)
f, _ := GetDefaultFont() f, _ := chart.GetDefaultFont()
style := Style{ style := Style{
FontSize: 12, FontSize: 12,
FontColor: chart.ColorBlack, FontColor: chart.ColorBlack,

View file

@ -27,7 +27,7 @@ import (
"math" "math"
"github.com/golang/freetype/truetype" "github.com/golang/freetype/truetype"
"git.smarteching.com/zeni/go-chart/v2" "github.com/wcharczuk/go-chart/v2"
) )
type pieChart struct { 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) { func (p *pieChart) render(result *defaultRenderResult, seriesList SeriesList) (Box, error) {
opt := p.opt opt := p.opt
values := make([]float64, len(seriesList)) values := make([]float64, len(seriesList))
@ -191,103 +101,79 @@ func (p *pieChart) render(result *defaultRenderResult, seriesList SeriesList) (B
theme := opt.Theme theme := opt.Theme
currentValue := float64(0) currentValue := float64(0)
prevEndX := 0
var quadrant1, quadrant2, quadrant3, quadrant4 []sector prevEndY := 0
for index, v := range values { 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{ seriesPainter.OverrideDrawingStyle(Style{
StrokeWidth: 1, StrokeWidth: 1,
StrokeColor: s.color, StrokeColor: theme.GetSeriesColor(index),
FillColor: s.color, FillColor: theme.GetSeriesColor(index),
}) })
seriesPainter.MoveTo(s.cx, s.cy) seriesPainter.MoveTo(cx, cy)
seriesPainter.ArcTo(s.cx, s.cy, s.rx, s.ry, s.start, s.delta).LineTo(s.cx, s.cy).Close().FillStroke() start := chart.PercentToRadians(currentValue/total) - math.Pi/2
if !s.showLabel { 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 continue
} }
if currentQuadrant != s.quadrant {
if s.quadrant == 1 { // label的角度为饼块中间
minY = cy * 2 angle := start + delta/2
maxY = 0 startx := cx + int(radius*math.Cos(angle))
prevY = cy * 2 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)
} }
if s.quadrant == 2 { prevEndX = endx
if currentQuadrant != 3 { prevEndY = endy
prevY = s.lineEndY
} else { seriesPainter.MoveTo(startx, starty)
prevY = minY seriesPainter.LineTo(endx, endy)
offset := labelLineWidth
if endx < cx {
offset *= -1
} }
} seriesPainter.MoveTo(endx, endy)
if s.quadrant == 3 { endx += offset
if currentQuadrant != 4 { seriesPainter.LineTo(endx, endy)
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
}
prevY = s.calculateY(prevY)
if prevY > maxY {
maxY = prevY
}
if prevY < minY {
minY = prevY
}
seriesPainter.MoveTo(s.lineStartX, s.lineStartY)
seriesPainter.LineTo(s.lineBranchX, s.lineBranchY)
seriesPainter.MoveTo(s.lineBranchX, s.lineBranchY)
seriesPainter.LineTo(s.lineEndX, s.lineEndY)
seriesPainter.Stroke() seriesPainter.Stroke()
textStyle := Style{ textStyle := Style{
FontColor: theme.GetTextColor(), FontColor: theme.GetTextColor(),
FontSize: labelFontSize, FontSize: labelFontSize,
Font: opt.Font, Font: opt.Font,
} }
if !s.series.Label.Color.IsZero() { if !series.Label.Color.IsZero() {
textStyle.FontColor = s.series.Label.Color textStyle.FontColor = series.Label.Color
} }
seriesPainter.OverrideTextStyle(textStyle) seriesPainter.OverrideTextStyle(textStyle)
x, y := s.calculateTextXY(seriesPainter.MeasureText(s.label)) text := NewPieLabelFormatter(seriesNames, series.Label.Formatter)(index, v, percent)
seriesPainter.Text(s.label, x, y) 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 return p.p.box, nil
} }

File diff suppressed because one or more lines are too long

View file

@ -25,10 +25,9 @@ package charts
import ( import (
"errors" "errors"
"github.com/dustin/go-humanize"
"github.com/golang/freetype/truetype" "github.com/golang/freetype/truetype"
"git.smarteching.com/zeni/go-chart/v2" "github.com/wcharczuk/go-chart/v2"
"git.smarteching.com/zeni/go-chart/v2/drawing" "github.com/wcharczuk/go-chart/v2/drawing"
) )
type radarChart struct { type radarChart struct {
@ -231,15 +230,9 @@ func (r *radarChart) render(result *defaultRenderResult, seriesList SeriesList)
StrokeColor: color, StrokeColor: color,
FillColor: dotFillColor, FillColor: dotFillColor,
}) })
for index, point := range linePoints { for _, point := range linePoints {
seriesPainter.Circle(dotWith, point.X, point.Y) seriesPainter.Circle(dotWith, point.X, point.Y)
seriesPainter.FillStroke() 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)
}
} }
} }

View file

@ -29,7 +29,6 @@ import (
const defaultAxisDivideCount = 6 const defaultAxisDivideCount = 6
type axisRange struct { type axisRange struct {
p *Painter
divideCount int divideCount int
min float64 min float64
max float64 max float64
@ -38,7 +37,6 @@ type axisRange struct {
} }
type AxisRangeOption struct { type AxisRangeOption struct {
Painter *Painter
// The min value of axis // The min value of axis
Min float64 Min float64
// The max value of axis // The max value of axis
@ -62,10 +60,7 @@ func NewRange(opt AxisRangeOption) axisRange {
r := math.Abs(max - min) r := math.Abs(max - min)
// 最小单位计算 // 最小单位计算
unit := 1 unit := 2
if r > 5 {
unit = 2
}
if r > 10 { if r > 10 {
unit = 4 unit = 4
} }
@ -90,12 +85,7 @@ func NewRange(opt AxisRangeOption) axisRange {
} }
} }
max = min + float64(unit*divideCount) max = min + float64(unit*divideCount)
expectMax := opt.Max * 2
if max > expectMax {
max = float64(ceilFloatToInt(expectMax))
}
return axisRange{ return axisRange{
p: opt.Painter,
divideCount: divideCount, divideCount: divideCount,
min: min, min: min,
max: max, max: max,
@ -108,22 +98,15 @@ func NewRange(opt AxisRangeOption) axisRange {
func (r axisRange) Values() []string { func (r axisRange) Values() []string {
offset := (r.max - r.min) / float64(r.divideCount) offset := (r.max - r.min) / float64(r.divideCount)
values := make([]string, 0) 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++ { for i := 0; i <= r.divideCount; i++ {
v := r.min + float64(i)*offset v := r.min + float64(i)*offset
value := formatter(v) value := commafWithDigits(v)
values = append(values, value) values = append(values, value)
} }
return values return values
} }
func (r *axisRange) getHeight(value float64) int { func (r *axisRange) getHeight(value float64) int {
if r.max <= r.min {
return 0
}
v := (value - r.min) / (r.max - r.min) v := (value - r.min) / (r.max - r.min)
return int(v * float64(r.size)) return int(v * float64(r.size))
} }

View file

@ -26,7 +26,7 @@ import (
"strings" "strings"
"github.com/dustin/go-humanize" "github.com/dustin/go-humanize"
"git.smarteching.com/zeni/go-chart/v2" "github.com/wcharczuk/go-chart/v2"
) )
type SeriesData struct { type SeriesData struct {
@ -79,12 +79,6 @@ type SeriesLabel struct {
Show bool Show bool
// Distance to the host graphic element. // Distance to the host graphic element.
Distance int Distance int
// The position of label
Position string
// The offset of label's position
Offset Box
// The font size of label
FontSize float64
} }
const ( const (
@ -126,8 +120,6 @@ type Series struct {
Name string Name string
// Radius for Pie chart, e.g.: 40%, default is "40%" // Radius for Pie chart, e.g.: 40%, default is "40%"
Radius string Radius string
// Round for bar chart
RoundRadius int
// Mark point for series // Mark point for series
MarkPoint SeriesMarkPoint MarkPoint SeriesMarkPoint
// Make line for series // Make line for series
@ -173,10 +165,6 @@ func (sl SeriesList) GetMaxMin(axisIndex int) (float64, float64) {
continue continue
} }
for _, item := range series.Data { for _, item := range series.Data {
// 如果为空值,忽略
if item.Value == nullValue {
continue
}
if item.Value > max { if item.Value > max {
max = item.Value max = item.Value
} }
@ -281,14 +269,6 @@ func NewPieLabelFormatter(seriesNames []string, layout string) LabelFormatter {
return NewLabelFormatter(seriesNames, layout) 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 // NewValueLabelFormatter returns a value formatter
func NewValueLabelFormatter(seriesNames []string, layout string) LabelFormatter { func NewValueLabelFormatter(seriesNames []string, layout string) LabelFormatter {
if len(layout) == 0 { if len(layout) == 0 {

View file

@ -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: &params.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
}

View file

@ -26,8 +26,8 @@ import (
"errors" "errors"
"github.com/golang/freetype/truetype" "github.com/golang/freetype/truetype"
"git.smarteching.com/zeni/go-chart/v2" "github.com/wcharczuk/go-chart/v2"
"git.smarteching.com/zeni/go-chart/v2/drawing" "github.com/wcharczuk/go-chart/v2/drawing"
) )
type tableChart struct { type tableChart struct {

View file

@ -24,7 +24,8 @@ package charts
import ( import (
"github.com/golang/freetype/truetype" "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" const ThemeDark = "dark"
@ -76,19 +77,6 @@ const defaultFontSize = 12.0
var defaultTheme ColorPalette 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() { func init() {
echartSeriesColors := []Color{ echartSeriesColors := []Color{
parseColor("#5470c6"), parseColor("#5470c6"),
@ -323,7 +311,7 @@ func (t *themeColorPalette) GetFont() *truetype.Font {
if t.font != nil { if t.font != nil {
return t.font return t.font
} }
f, _ := GetDefaultFont() f, _ := chart.GetDefaultFont()
return f return f
} }

33
util.go
View file

@ -29,8 +29,8 @@ import (
"strings" "strings"
"github.com/dustin/go-humanize" "github.com/dustin/go-humanize"
"git.smarteching.com/zeni/go-chart/v2" "github.com/wcharczuk/go-chart/v2"
"git.smarteching.com/zeni/go-chart/v2/drawing" "github.com/wcharczuk/go-chart/v2/drawing"
) )
func TrueFlag() *bool { func TrueFlag() *bool {
@ -160,25 +160,15 @@ func NewFloatPoint(f float64) *float64 {
v := f v := f
return &v 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 { func commafWithDigits(value float64) string {
decimals := 2 decimals := 2
if value >= T_VALUE { m := float64(1000 * 1000)
return humanize.CommafWithDigits(value/T_VALUE, decimals) + "T" if value >= m {
return humanize.CommafWithDigits(value/m, decimals) + "M"
} }
if value >= G_VALUE { k := float64(1000)
return humanize.CommafWithDigits(value/G_VALUE, decimals) + "G" if value >= k {
} return humanize.CommafWithDigits(value/k, decimals) + "k"
if value >= M_VALUE {
return humanize.CommafWithDigits(value/M_VALUE, decimals) + "M"
}
if value >= K_VALUE {
return humanize.CommafWithDigits(value/K_VALUE, decimals) + "k"
} }
return humanize.CommafWithDigits(value, decimals) return humanize.CommafWithDigits(value, decimals)
} }
@ -262,10 +252,3 @@ func getPolygonPoints(center Point, radius float64, sides int) []Point {
} }
return points 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
}

View file

@ -26,8 +26,8 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"git.smarteching.com/zeni/go-chart/v2" "github.com/wcharczuk/go-chart/v2"
"git.smarteching.com/zeni/go-chart/v2/drawing" "github.com/wcharczuk/go-chart/v2/drawing"
) )
func TestGetDefaultInt(t *testing.T) { func TestGetDefaultInt(t *testing.T) {
@ -189,35 +189,3 @@ func TestParseColor(t *testing.T) {
A: 250, A: 250,
}, c) }, 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,
}))
}

View file

@ -48,12 +48,6 @@ type XAxisOption struct {
StrokeColor Color StrokeColor Color
// The color of label // The color of label
FontColor Color FontColor Color
// The text rotation of label
TextRotation float64
// The first axis
FirstAxis int
// The offset of label
LabelOffset Box
isValueAxis bool isValueAxis bool
} }
@ -87,9 +81,6 @@ func (opt *XAxisOption) ToAxisOption() AxisOption {
FontColor: opt.FontColor, FontColor: opt.FontColor,
Show: opt.Show, Show: opt.Show,
SplitLineColor: opt.Theme.GetAxisSplitLineColor(), SplitLineColor: opt.Theme.GetAxisSplitLineColor(),
TextRotation: opt.TextRotation,
LabelOffset: opt.LabelOffset,
FirstAxis: opt.FirstAxis,
} }
if opt.isValueAxis { if opt.isValueAxis {
axisOpt.SplitLineShow = true axisOpt.SplitLineShow = true

View file

@ -47,11 +47,7 @@ type YAxisOption struct {
Color Color Color Color
// The flag for show axis, set this to *false will hide axis // The flag for show axis, set this to *false will hide axis
Show *bool Show *bool
DivideCount int
Unit int
isCategoryAxis bool 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 // NewYAxisOptions returns a y axis option
@ -91,7 +87,6 @@ func (opt *YAxisOption) ToAxisOption(p *Painter) AxisOption {
SplitLineShow: true, SplitLineShow: true,
SplitLineColor: theme.GetAxisSplitLineColor(), SplitLineColor: theme.GetAxisSplitLineColor(),
Show: opt.Show, Show: opt.Show,
Unit: opt.Unit,
} }
if !opt.Color.IsZero() { if !opt.Color.IsZero() {
axisOpt.FontColor = opt.Color axisOpt.FontColor = opt.Color
@ -102,9 +97,6 @@ func (opt *YAxisOption) ToAxisOption(p *Painter) AxisOption {
axisOpt.StrokeWidth = 1 axisOpt.StrokeWidth = 1
axisOpt.SplitLineShow = false axisOpt.SplitLineShow = false
} }
if opt.SplitLineShow != nil {
axisOpt.SplitLineShow = *opt.SplitLineShow
}
return axisOpt return axisOpt
} }