Compare commits
No commits in common. "main" and "v2.0.3" have entirely different histories.
58 changed files with 328 additions and 2143 deletions
8
.github/workflows/test.yml
vendored
8
.github/workflows/test.yml
vendored
|
|
@ -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
|
||||||
|
|
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -16,5 +16,3 @@
|
||||||
*.png
|
*.png
|
||||||
*.svg
|
*.svg
|
||||||
tmp
|
tmp
|
||||||
NotoSansSC.ttf
|
|
||||||
.vscode
|
|
||||||
18
README.md
18
README.md
|
|
@ -1,7 +1,5 @@
|
||||||
# go-charts
|
# go-charts
|
||||||
|
|
||||||
Clone from https://github.com/vicanso/go-charts
|
|
||||||
|
|
||||||
[](https://github.com/vicanso/go-charts/blob/master/LICENSE)
|
[](https://github.com/vicanso/go-charts/blob/master/LICENSE)
|
||||||
[](https://github.com/vicanso/go-charts/actions)
|
[](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() {
|
||||||
|
|
|
||||||
18
README_zh.md
18
README_zh.md
|
|
@ -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)
|
||||||
|
|
|
||||||
4
alias.go
4
alias.go
|
|
@ -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
|
||||||
|
|
|
||||||
37
axis.go
37
axis.go
|
|
@ -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,16 @@ 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)
|
||||||
// 根据文本宽度计算较为符合的展示项
|
textCount := ceilFloatToInt(float64(top.Width()) / textFillWidth)
|
||||||
fitTextCount := ceilFloatToInt(float64(top.Width()) / textFillWidth)
|
unit := ceilFloatToInt(float64(dataCount) / float64(chart.MaxInt(textCount, opt.SplitNumber)))
|
||||||
|
|
||||||
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 {
|
if unit%2 == 0 && dataCount%(unit+1) == 0 {
|
||||||
unit++
|
unit++
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
width := 0
|
width := 0
|
||||||
height := 0
|
height := 0
|
||||||
|
|
@ -257,7 +235,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,19 +253,15 @@ 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 {
|
||||||
style.StrokeColor = opt.SplitLineColor
|
style.StrokeColor = opt.SplitLineColor
|
||||||
style.StrokeWidth = 1
|
|
||||||
top.OverrideDrawingStyle(style)
|
top.OverrideDrawingStyle(style)
|
||||||
if isVertical {
|
if isVertical {
|
||||||
x0 := p.Width()
|
x0 := p.Width()
|
||||||
|
|
@ -297,9 +270,7 @@ func (a *axisPainter) Render() (Box, error) {
|
||||||
x0 = 0
|
x0 = 0
|
||||||
x1 = top.Width() - p.Width()
|
x1 = top.Width() - p.Width()
|
||||||
}
|
}
|
||||||
yValues := autoDivide(height, tickCount)
|
for _, y := range autoDivide(height, tickCount) {
|
||||||
yValues = yValues[0 : len(yValues)-1]
|
|
||||||
for _, y := range yValues {
|
|
||||||
top.LineStroke([]Point{
|
top.LineStroke([]Point{
|
||||||
{
|
{
|
||||||
X: x0,
|
X: x0,
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
@ -113,7 +113,7 @@ func TestAxis(t *testing.T) {
|
||||||
}).Render()
|
}).Render()
|
||||||
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<path d=\"M 36 0\nL 41 0\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><path d=\"M 36 66\nL 41 66\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><path d=\"M 36 133\nL 41 133\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><path d=\"M 36 200\nL 41 200\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><path d=\"M 36 266\nL 41 266\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><path d=\"M 36 333\nL 41 333\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><path d=\"M 36 400\nL 41 400\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><path d=\"M 41 0\nL 41 400\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><text x=\"0\" y=\"7\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Mon</text><text x=\"4\" y=\"73\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Tue</text><text x=\"0\" y=\"140\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Wed</text><text x=\"4\" y=\"207\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Thu</text><text x=\"13\" y=\"273\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Fri</text><text x=\"8\" y=\"340\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Sat</text><text x=\"4\" y=\"407\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Sun</text><path d=\"M 41 0\nL 600 0\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/><path d=\"M 41 66\nL 600 66\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/><path d=\"M 41 133\nL 600 133\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/><path d=\"M 41 200\nL 600 200\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/><path d=\"M 41 266\nL 600 266\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/><path d=\"M 41 333\nL 600 333\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/></svg>",
|
result: "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"600\" height=\"400\">\\n<path d=\"M 36 0\nL 41 0\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><path d=\"M 36 66\nL 41 66\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><path d=\"M 36 133\nL 41 133\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><path d=\"M 36 200\nL 41 200\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><path d=\"M 36 266\nL 41 266\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><path d=\"M 36 333\nL 41 333\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><path d=\"M 36 400\nL 41 400\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><path d=\"M 41 0\nL 41 400\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><text x=\"0\" y=\"7\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Mon</text><text x=\"4\" y=\"73\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Tue</text><text x=\"0\" y=\"140\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Wed</text><text x=\"4\" y=\"207\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Thu</text><text x=\"13\" y=\"273\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Fri</text><text x=\"8\" y=\"340\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Sat</text><text x=\"4\" y=\"407\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Sun</text><path d=\"M 41 0\nL 600 0\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/><path d=\"M 41 66\nL 600 66\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/><path d=\"M 41 133\nL 600 133\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/><path d=\"M 41 200\nL 600 200\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/><path d=\"M 41 266\nL 600 266\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/><path d=\"M 41 333\nL 600 333\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/><path d=\"M 41 400\nL 600 400\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/></svg>",
|
||||||
},
|
},
|
||||||
// 右侧
|
// 右侧
|
||||||
{
|
{
|
||||||
|
|
@ -135,7 +135,7 @@ func TestAxis(t *testing.T) {
|
||||||
}).Render()
|
}).Render()
|
||||||
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<path d=\"M 559 0\nL 564 0\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><path d=\"M 559 66\nL 564 66\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><path d=\"M 559 133\nL 564 133\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><path d=\"M 559 200\nL 564 200\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><path d=\"M 559 266\nL 564 266\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><path d=\"M 559 333\nL 564 333\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><path d=\"M 559 400\nL 564 400\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><path d=\"M 559 0\nL 559 400\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><text x=\"569\" y=\"7\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Mon</text><text x=\"569\" y=\"73\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Tue</text><text x=\"569\" y=\"140\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Wed</text><text x=\"569\" y=\"207\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Thu</text><text x=\"569\" y=\"273\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Fri</text><text x=\"569\" y=\"340\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Sat</text><text x=\"569\" y=\"407\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Sun</text><path d=\"M 0 0\nL 559 0\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/><path d=\"M 0 66\nL 559 66\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/><path d=\"M 0 133\nL 559 133\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/><path d=\"M 0 200\nL 559 200\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/><path d=\"M 0 266\nL 559 266\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/><path d=\"M 0 333\nL 559 333\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/></svg>",
|
result: "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"600\" height=\"400\">\\n<path d=\"M 559 0\nL 564 0\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><path d=\"M 559 66\nL 564 66\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><path d=\"M 559 133\nL 564 133\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><path d=\"M 559 200\nL 564 200\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><path d=\"M 559 266\nL 564 266\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><path d=\"M 559 333\nL 564 333\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><path d=\"M 559 400\nL 564 400\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><path d=\"M 559 0\nL 559 400\" style=\"stroke-width:1;stroke:rgba(110,112,121,1.0);fill:none\"/><text x=\"569\" y=\"7\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Mon</text><text x=\"569\" y=\"73\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Tue</text><text x=\"569\" y=\"140\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Wed</text><text x=\"569\" y=\"207\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Thu</text><text x=\"569\" y=\"273\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Fri</text><text x=\"569\" y=\"340\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Sat</text><text x=\"569\" y=\"407\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:15.3px;font-family:'Roboto Medium',sans-serif\">Sun</text><path d=\"M 0 0\nL 559 0\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/><path d=\"M 0 66\nL 559 66\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/><path d=\"M 0 133\nL 559 133\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/><path d=\"M 0 200\nL 559 200\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/><path d=\"M 0 266\nL 559 266\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/><path d=\"M 0 333\nL 559 333\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/><path d=\"M 0 400\nL 559 400\" style=\"stroke-width:1;stroke:rgba(0,0,0,1.0);fill:none\"/></svg>",
|
||||||
},
|
},
|
||||||
// 顶部
|
// 顶部
|
||||||
{
|
{
|
||||||
|
|
|
||||||
94
bar_chart.go
94
bar_chart.go
|
|
@ -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
|
|
@ -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 {
|
||||||
|
|
@ -61,24 +62,8 @@ type ChartOption struct {
|
||||||
RadarIndicators []RadarIndicator
|
RadarIndicators []RadarIndicator
|
||||||
// The background color of chart
|
// The background color of chart
|
||||||
BackgroundColor Color
|
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
|
// The child charts
|
||||||
Children []ChartOption
|
Children []ChartOption
|
||||||
// The value formatter
|
|
||||||
ValueFormatter ValueFormatter
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// OptionFunc option function
|
// OptionFunc option function
|
||||||
|
|
@ -123,12 +108,9 @@ func TitleOptionFunc(title TitleOption) OptionFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TitleTextOptionFunc set title text of chart
|
// TitleTextOptionFunc set title text of chart
|
||||||
func TitleTextOptionFunc(text string, subtext ...string) OptionFunc {
|
func TitleTextOptionFunc(text string) OptionFunc {
|
||||||
return func(opt *ChartOption) {
|
return func(opt *ChartOption) {
|
||||||
opt.Title.Text = text
|
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)
|
o.font, _ = GetFont(o.FontFamily)
|
||||||
|
|
||||||
if o.font == nil {
|
if o.font == nil {
|
||||||
o.font, _ = GetDefaultFont()
|
o.font, _ = chart.GetDefaultFont()
|
||||||
} else {
|
|
||||||
// 如果指定了字体,则设置主题的字体
|
|
||||||
t.SetFont(o.font)
|
|
||||||
}
|
}
|
||||||
if o.BackgroundColor.IsZero() {
|
if o.BackgroundColor.IsZero() {
|
||||||
o.BackgroundColor = t.GetBackgroundColor()
|
o.BackgroundColor = t.GetBackgroundColor()
|
||||||
|
|
@ -387,11 +366,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
77
charts.go
77
charts.go
|
|
@ -24,10 +24,7 @@ package charts
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"math"
|
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"git.smarteching.com/zeni/go-chart/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const labelFontSize = 10
|
const labelFontSize = 10
|
||||||
|
|
@ -52,18 +49,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)
|
||||||
}
|
}
|
||||||
|
|
@ -125,16 +110,14 @@ func defaultRender(p *Painter, opt defaultRenderOption) (*defaultRenderResult, e
|
||||||
p = p.Child(PainterPaddingOption(opt.Padding))
|
p = p.Child(PainterPaddingOption(opt.Padding))
|
||||||
}
|
}
|
||||||
|
|
||||||
legendHeight := 0
|
|
||||||
if len(opt.LegendOption.Data) != 0 {
|
if len(opt.LegendOption.Data) != 0 {
|
||||||
if opt.LegendOption.Theme == nil {
|
if opt.LegendOption.Theme == nil {
|
||||||
opt.LegendOption.Theme = opt.Theme
|
opt.LegendOption.Theme = opt.Theme
|
||||||
}
|
}
|
||||||
legendResult, err := NewLegendPainter(p, opt.LegendOption).Render()
|
_, err := NewLegendPainter(p, opt.LegendOption).Render()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
legendHeight = legendResult.Height()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果有标题
|
// 如果有标题
|
||||||
|
|
@ -148,15 +131,9 @@ func defaultRender(p *Painter, opt defaultRenderOption) (*defaultRenderResult, e
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
top := chart.MaxInt(legendHeight, titleBox.Height())
|
|
||||||
// 如果是垂直方式,则不计算legend高度
|
|
||||||
if opt.LegendOption.Orient == OrientVertical {
|
|
||||||
top = titleBox.Height()
|
|
||||||
}
|
|
||||||
p = p.Child(PainterPaddingOption(Box{
|
p = p.Child(PainterPaddingOption(Box{
|
||||||
// 标题下留白
|
// 标题下留白
|
||||||
Top: top + 20,
|
Top: titleBox.Height() + 20,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -186,26 +163,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 +187,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 +264,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))
|
||||||
}
|
}
|
||||||
|
|
@ -346,8 +306,9 @@ func Render(opt ChartOption, opts ...OptionFunc) (*Painter, error) {
|
||||||
TitleOption: opt.Title,
|
TitleOption: opt.Title,
|
||||||
LegendOption: opt.Legend,
|
LegendOption: opt.Legend,
|
||||||
axisReversed: axisReversed,
|
axisReversed: axisReversed,
|
||||||
// 前置已设置背景色
|
}
|
||||||
backgroundIsFilled: true,
|
if isChild {
|
||||||
|
renderOpt.backgroundIsFilled = true
|
||||||
}
|
}
|
||||||
if len(pieSeriesList) != 0 ||
|
if len(pieSeriesList) != 0 ||
|
||||||
len(radarSeriesList) != 0 ||
|
len(radarSeriesList) != 0 ||
|
||||||
|
|
@ -359,10 +320,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 +335,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 +346,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
|
||||||
|
|
@ -417,10 +370,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,
|
||||||
SymbolShow: opt.SymbolShow,
|
|
||||||
StrokeWidth: opt.LineStrokeWidth,
|
|
||||||
FillArea: opt.FillArea,
|
|
||||||
Opacity: opt.Opacity,
|
|
||||||
}).render(renderResult, lineSeriesList)
|
}).render(renderResult, lineSeriesList)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
@ -25,8 +25,7 @@ func writeFile(buf []byte) error {
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// 字体文件需要自行下载
|
// 字体文件需要自行下载
|
||||||
// https://github.com/googlefonts/noto-cjk
|
buf, err := ioutil.ReadFile("../NotoSansSC.ttf")
|
||||||
buf, err := ioutil.ReadFile("./NotoSansSC.ttf")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
@ -34,8 +33,6 @@ func main() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
font, _ := charts.GetFont("noto")
|
|
||||||
charts.SetDefaultFont(font)
|
|
||||||
|
|
||||||
values := [][]float64{
|
values := [][]float64{
|
||||||
{
|
{
|
||||||
|
|
@ -86,7 +83,8 @@ func main() {
|
||||||
}
|
}
|
||||||
p, err := charts.LineRender(
|
p, err := charts.LineRender(
|
||||||
values,
|
values,
|
||||||
charts.TitleTextOptionFunc("测试"),
|
charts.TitleTextOptionFunc("Line"),
|
||||||
|
charts.FontFamilyOptionFunc("noto"),
|
||||||
charts.XAxisDataOptionFunc([]string{
|
charts.XAxisDataOptionFunc([]string{
|
||||||
"星期一",
|
"星期一",
|
||||||
"星期二",
|
"星期二",
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
@ -90,23 +89,7 @@ func main() {
|
||||||
"Video Ads",
|
"Video Ads",
|
||||||
"Direct",
|
"Direct",
|
||||||
"Search Engine",
|
"Search Engine",
|
||||||
}, "50"),
|
}, charts.PositionCenter),
|
||||||
func(opt *charts.ChartOption) {
|
|
||||||
opt.Legend.Padding = charts.Box{
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
21
font.go
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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
12
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 (
|
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
22
go.sum
|
|
@ -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=
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
28
legend.go
28
legend.go
|
|
@ -59,8 +59,6 @@ type LegendOption struct {
|
||||||
FontColor Color
|
FontColor Color
|
||||||
// The flag for show legend, set this to *false will hide legend
|
// The flag for show legend, set this to *false will hide legend
|
||||||
Show *bool
|
Show *bool
|
||||||
// The padding of legend
|
|
||||||
Padding Box
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLegendOption returns a legend option
|
// NewLegendOption returns a legend option
|
||||||
|
|
@ -113,11 +111,9 @@ func (l *legendPainter) Render() (Box, error) {
|
||||||
if opt.Left == "" {
|
if opt.Left == "" {
|
||||||
opt.Left = PositionCenter
|
opt.Left = PositionCenter
|
||||||
}
|
}
|
||||||
padding := opt.Padding
|
p := l.p.Child(PainterPaddingOption(Box{
|
||||||
if padding.IsZero() {
|
Top: 5,
|
||||||
padding.Top = 5
|
}))
|
||||||
}
|
|
||||||
p := l.p.Child(PainterPaddingOption(padding))
|
|
||||||
p.SetTextStyle(Style{
|
p.SetTextStyle(Style{
|
||||||
FontSize: opt.FontSize,
|
FontSize: opt.FontSize,
|
||||||
FontColor: opt.FontColor,
|
FontColor: opt.FontColor,
|
||||||
|
|
@ -150,8 +146,6 @@ func (l *legendPainter) Render() (Box, error) {
|
||||||
width += item.Width()
|
width += item.Width()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 增加padding
|
|
||||||
itemMaxHeight += 10
|
|
||||||
if opt.Orient == OrientVertical {
|
if opt.Orient == OrientVertical {
|
||||||
width = maxTextWidth + textOffset + legendWidth
|
width = maxTextWidth + textOffset + legendWidth
|
||||||
height = offset * len(opt.Data)
|
height = offset * len(opt.Data)
|
||||||
|
|
@ -186,7 +180,6 @@ func (l *legendPainter) Render() (Box, error) {
|
||||||
|
|
||||||
x := int(left)
|
x := int(left)
|
||||||
y := int(top) + 10
|
y := int(top) + 10
|
||||||
startY := y
|
|
||||||
x0 := x
|
x0 := x
|
||||||
y0 := y
|
y0 := y
|
||||||
|
|
||||||
|
|
@ -208,21 +201,15 @@ func (l *legendPainter) Render() (Box, error) {
|
||||||
}
|
}
|
||||||
return left + legendWidth
|
return left + legendWidth
|
||||||
}
|
}
|
||||||
lastIndex := len(opt.Data) - 1
|
|
||||||
for index, text := range opt.Data {
|
for index, text := range opt.Data {
|
||||||
color := theme.GetSeriesColor(index)
|
color := theme.GetSeriesColor(index)
|
||||||
p.SetDrawingStyle(Style{
|
p.SetDrawingStyle(Style{
|
||||||
FillColor: color,
|
FillColor: color,
|
||||||
StrokeColor: color,
|
StrokeColor: color,
|
||||||
})
|
})
|
||||||
itemWidth := x0 + measureList[index].Width() + textOffset + offset + legendWidth
|
if x0+measureList[index].Width() > p.Width() {
|
||||||
if lastIndex == index {
|
|
||||||
itemWidth = x0 + measureList[index].Width() + legendWidth
|
|
||||||
}
|
|
||||||
if itemWidth > p.Width() {
|
|
||||||
x0 = 0
|
x0 = 0
|
||||||
y += itemMaxHeight
|
y0 += itemMaxHeight
|
||||||
y0 = y
|
|
||||||
}
|
}
|
||||||
if opt.Align != AlignRight {
|
if opt.Align != AlignRight {
|
||||||
x0 = drawIcon(y0, x0)
|
x0 = drawIcon(y0, x0)
|
||||||
|
|
@ -232,7 +219,7 @@ func (l *legendPainter) Render() (Box, error) {
|
||||||
x0 += measureList[index].Width()
|
x0 += measureList[index].Width()
|
||||||
if opt.Align == AlignRight {
|
if opt.Align == AlignRight {
|
||||||
x0 += textOffset
|
x0 += textOffset
|
||||||
x0 = drawIcon(y0, x0)
|
x0 = drawIcon(0, x0)
|
||||||
}
|
}
|
||||||
if opt.Orient == OrientVertical {
|
if opt.Orient == OrientVertical {
|
||||||
y0 += offset
|
y0 += offset
|
||||||
|
|
@ -241,11 +228,10 @@ func (l *legendPainter) Render() (Box, error) {
|
||||||
x0 += offset
|
x0 += offset
|
||||||
y0 = y
|
y0 = y
|
||||||
}
|
}
|
||||||
height = y0 - startY + 10
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Box{
|
return Box{
|
||||||
Right: width,
|
Right: width,
|
||||||
Bottom: height + padding.Bottom + padding.Top,
|
Bottom: height,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
@ -62,16 +60,8 @@ type LineChartOption struct {
|
||||||
Title TitleOption
|
Title TitleOption
|
||||||
// The legend option
|
// The legend option
|
||||||
Legend LegendOption
|
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
|
// 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) {
|
||||||
|
|
@ -103,82 +93,25 @@ func (l *lineChart) render(result *defaultRenderResult, seriesList SeriesList) (
|
||||||
markPointPainter,
|
markPointPainter,
|
||||||
markLinePainter,
|
markLinePainter,
|
||||||
}
|
}
|
||||||
strokeWidth := opt.StrokeWidth
|
|
||||||
if strokeWidth == 0 {
|
|
||||||
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)
|
||||||
drawingStyle := Style{
|
drawingStyle := Style{
|
||||||
StrokeColor: seriesColor,
|
StrokeColor: seriesColor,
|
||||||
StrokeWidth: strokeWidth,
|
StrokeWidth: defaultStrokeWidth,
|
||||||
}
|
|
||||||
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)
|
||||||
|
|
||||||
|
|
@ -190,9 +123,7 @@ func (l *lineChart) render(result *defaultRenderResult, seriesList SeriesList) (
|
||||||
}
|
}
|
||||||
drawingStyle.StrokeWidth = 1
|
drawingStyle.StrokeWidth = 1
|
||||||
seriesPainter.SetDrawingStyle(drawingStyle)
|
seriesPainter.SetDrawingStyle(drawingStyle)
|
||||||
if !isFalse(opt.SymbolShow) {
|
|
||||||
seriesPainter.Dots(points)
|
seriesPainter.Dots(points)
|
||||||
}
|
|
||||||
markPointPainter.Add(markPointRenderOption{
|
markPointPainter.Add(markPointRenderOption{
|
||||||
FillColor: seriesColor,
|
FillColor: seriesColor,
|
||||||
Font: opt.Font,
|
Font: opt.Font,
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
111
painter.go
111
painter.go
|
|
@ -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 {
|
||||||
|
|
@ -674,15 +637,12 @@ func (p *Painter) MultiText(opt MultiTextOption) *Painter {
|
||||||
}
|
}
|
||||||
count := len(opt.TextList)
|
count := len(opt.TextList)
|
||||||
positionCenter := true
|
positionCenter := true
|
||||||
showIndex := opt.Unit / 2
|
|
||||||
if containsString([]string{
|
if containsString([]string{
|
||||||
PositionLeft,
|
PositionLeft,
|
||||||
PositionTop,
|
PositionTop,
|
||||||
}, opt.Position) {
|
}, opt.Position) {
|
||||||
positionCenter = false
|
positionCenter = false
|
||||||
count--
|
count--
|
||||||
// 非居中
|
|
||||||
showIndex = 0
|
|
||||||
}
|
}
|
||||||
width := p.Width()
|
width := p.Width()
|
||||||
height := p.Height()
|
height := p.Height()
|
||||||
|
|
@ -693,19 +653,11 @@ func (p *Painter) MultiText(opt MultiTextOption) *Painter {
|
||||||
} else {
|
} else {
|
||||||
values = autoDivide(width, count)
|
values = autoDivide(width, count)
|
||||||
}
|
}
|
||||||
isTextRotation := opt.TextRotation != 0
|
showIndex := opt.Unit / 2
|
||||||
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 +678,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 +750,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 +765,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
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
228
pie_chart.go
228
pie_chart.go
|
|
@ -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
|
|
@ -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 {
|
||||||
|
|
@ -201,11 +200,7 @@ func (r *radarChart) render(result *defaultRenderResult, seriesList SeriesList)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
indicator := indicators[j]
|
indicator := indicators[j]
|
||||||
var percent float64
|
percent := (item.Value - indicator.Min) / (indicator.Max - indicator.Min)
|
||||||
offset := indicator.Max - indicator.Min
|
|
||||||
if offset > 0 {
|
|
||||||
percent = (item.Value - indicator.Min) / offset
|
|
||||||
}
|
|
||||||
r := percent * radius
|
r := percent * radius
|
||||||
p := getPolygonPoint(center, r, angles[j])
|
p := getPolygonPoint(center, r, angles[j])
|
||||||
linePoints = append(linePoints, p)
|
linePoints = append(linePoints, p)
|
||||||
|
|
@ -231,15 +226,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)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
21
range.go
21
range.go
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
22
series.go
22
series.go
|
|
@ -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 {
|
||||||
|
|
|
||||||
148
series_label.go
148
series_label.go
|
|
@ -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
|
|
||||||
}
|
|
||||||
254
start_zh.md
254
start_zh.md
|
|
@ -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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
```
|
|
||||||
4
table.go
4
table.go
|
|
@ -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 {
|
||||||
|
|
|
||||||
57
theme.go
57
theme.go
|
|
@ -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"
|
||||||
|
|
@ -35,19 +36,12 @@ const ThemeAnt = "ant"
|
||||||
type ColorPalette interface {
|
type ColorPalette interface {
|
||||||
IsDark() bool
|
IsDark() bool
|
||||||
GetAxisStrokeColor() Color
|
GetAxisStrokeColor() Color
|
||||||
SetAxisStrokeColor(Color)
|
|
||||||
GetAxisSplitLineColor() Color
|
GetAxisSplitLineColor() Color
|
||||||
SetAxisSplitLineColor(Color)
|
|
||||||
GetSeriesColor(int) Color
|
GetSeriesColor(int) Color
|
||||||
SetSeriesColor([]Color)
|
|
||||||
GetBackgroundColor() Color
|
GetBackgroundColor() Color
|
||||||
SetBackgroundColor(Color)
|
|
||||||
GetTextColor() Color
|
GetTextColor() Color
|
||||||
SetTextColor(Color)
|
|
||||||
GetFontSize() float64
|
GetFontSize() float64
|
||||||
SetFontSize(float64)
|
|
||||||
GetFont() *truetype.Font
|
GetFont() *truetype.Font
|
||||||
SetFont(*truetype.Font)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type themeColorPalette struct {
|
type themeColorPalette struct {
|
||||||
|
|
@ -70,25 +64,12 @@ type ThemeOption struct {
|
||||||
SeriesColors []Color
|
SeriesColors []Color
|
||||||
}
|
}
|
||||||
|
|
||||||
var palettes = map[string]*themeColorPalette{}
|
var palettes = map[string]ColorPalette{}
|
||||||
|
|
||||||
const defaultFontSize = 12.0
|
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"),
|
||||||
|
|
@ -260,8 +241,7 @@ func NewTheme(name string) ColorPalette {
|
||||||
if !ok {
|
if !ok {
|
||||||
p = palettes[ThemeLight]
|
p = palettes[ThemeLight]
|
||||||
}
|
}
|
||||||
clone := *p
|
return p
|
||||||
return &clone
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *themeColorPalette) IsDark() bool {
|
func (t *themeColorPalette) IsDark() bool {
|
||||||
|
|
@ -272,42 +252,23 @@ func (t *themeColorPalette) GetAxisStrokeColor() Color {
|
||||||
return t.axisStrokeColor
|
return t.axisStrokeColor
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *themeColorPalette) SetAxisStrokeColor(c Color) {
|
|
||||||
t.axisStrokeColor = c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *themeColorPalette) GetAxisSplitLineColor() Color {
|
func (t *themeColorPalette) GetAxisSplitLineColor() Color {
|
||||||
return t.axisSplitLineColor
|
return t.axisSplitLineColor
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *themeColorPalette) SetAxisSplitLineColor(c Color) {
|
|
||||||
t.axisSplitLineColor = c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *themeColorPalette) GetSeriesColor(index int) Color {
|
func (t *themeColorPalette) GetSeriesColor(index int) Color {
|
||||||
colors := t.seriesColors
|
colors := t.seriesColors
|
||||||
return colors[index%len(colors)]
|
return colors[index%len(colors)]
|
||||||
}
|
}
|
||||||
func (t *themeColorPalette) SetSeriesColor(colors []Color) {
|
|
||||||
t.seriesColors = colors
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *themeColorPalette) GetBackgroundColor() Color {
|
func (t *themeColorPalette) GetBackgroundColor() Color {
|
||||||
return t.backgroundColor
|
return t.backgroundColor
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *themeColorPalette) SetBackgroundColor(c Color) {
|
|
||||||
t.backgroundColor = c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *themeColorPalette) GetTextColor() Color {
|
func (t *themeColorPalette) GetTextColor() Color {
|
||||||
return t.textColor
|
return t.textColor
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *themeColorPalette) SetTextColor(c Color) {
|
|
||||||
t.textColor = c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *themeColorPalette) GetFontSize() float64 {
|
func (t *themeColorPalette) GetFontSize() float64 {
|
||||||
if t.fontSize != 0 {
|
if t.fontSize != 0 {
|
||||||
return t.fontSize
|
return t.fontSize
|
||||||
|
|
@ -315,18 +276,10 @@ func (t *themeColorPalette) GetFontSize() float64 {
|
||||||
return defaultFontSize
|
return defaultFontSize
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *themeColorPalette) SetFontSize(fontSize float64) {
|
|
||||||
t.fontSize = fontSize
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *themeColorPalette) GetFont() *truetype.Font {
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *themeColorPalette) SetFont(f *truetype.Font) {
|
|
||||||
t.font = f
|
|
||||||
}
|
|
||||||
|
|
|
||||||
4
title.go
4
title.go
|
|
@ -36,6 +36,10 @@ type TitleOption struct {
|
||||||
Text string
|
Text string
|
||||||
// Subtitle text, support \n for new line
|
// Subtitle text, support \n for new line
|
||||||
Subtext string
|
Subtext string
|
||||||
|
// // Title style
|
||||||
|
// Style Style
|
||||||
|
// // Subtitle style
|
||||||
|
// SubtextStyle Style
|
||||||
// Distance between title component and the left side of the container.
|
// Distance between title component and the left side of the container.
|
||||||
// It can be pixel value: 20, percentage value: 20%,
|
// It can be pixel value: 20, percentage value: 20%,
|
||||||
// or position value: right, center.
|
// or position value: right, center.
|
||||||
|
|
|
||||||
33
util.go
33
util.go
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
|
||||||
36
util_test.go
36
util_test.go
|
|
@ -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,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
|
||||||
9
xaxis.go
9
xaxis.go
|
|
@ -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
|
||||||
|
|
|
||||||
8
yaxis.go
8
yaxis.go
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue