docs: add document for echarts option
This commit is contained in:
parent
3406bd75a1
commit
805184b74d
8 changed files with 236 additions and 57 deletions
43
.github/workflows/test.yml
vendored
Normal file
43
.github/workflows/test.yml
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
name: Test
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ main ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
|
||||||
|
build:
|
||||||
|
name: Build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
go:
|
||||||
|
- '1.17'
|
||||||
|
- '1.16'
|
||||||
|
- '1.15'
|
||||||
|
- '1.14'
|
||||||
|
- '1.13'
|
||||||
|
steps:
|
||||||
|
|
||||||
|
- name: Go ${{ matrix.go }} test
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: ${{ matrix.go }}
|
||||||
|
|
||||||
|
- name: Check out code into the Go module directory
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Get dependencies
|
||||||
|
run:
|
||||||
|
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin latest
|
||||||
|
|
||||||
|
- name: Lint
|
||||||
|
run: make lint
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: make test
|
||||||
|
|
||||||
|
- name: Bench
|
||||||
|
run: make bench
|
||||||
38
README.md
38
README.md
|
|
@ -1,7 +1,5 @@
|
||||||
# go-echarts
|
# go-echarts
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
[go-chart](https://github.com/wcharczuk/go-chart)是golang常用的可视化图表库,支持`svg`与`png`的输出,`Apache ECharts`在前端开发中得到众多开发者的认可。go-echarts则是结合两者的方式,兼容`Apache ECharts`的配置参数,简单快捷的生成相似的图表(`svg`或`png`),方便插入至Email或分享使用。下面为常用的几种图表截图:
|
[go-chart](https://github.com/wcharczuk/go-chart)是golang常用的可视化图表库,支持`svg`与`png`的输出,`Apache ECharts`在前端开发中得到众多开发者的认可。go-echarts则是结合两者的方式,兼容`Apache ECharts`的配置参数,简单快捷的生成相似的图表(`svg`或`png`),方便插入至Email或分享使用。下面为常用的几种图表截图:
|
||||||
|
|
||||||

|

|
||||||
|
|
@ -44,4 +42,38 @@ func main() {
|
||||||
}
|
}
|
||||||
os.WriteFile("output.png", buf, 0600)
|
os.WriteFile("output.png", buf, 0600)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 参数说明
|
||||||
|
|
||||||
|
- `theme` 颜色主题,支持`dark`与`light`模式,默认为`light`
|
||||||
|
- `padding` 图表的内边距,单位px。支持以下几种模式的设置
|
||||||
|
- `padding: 5` 设置内边距为5
|
||||||
|
- `padding: [5, 10]` 设置上下的内边距为 5,左右的内边距为 10
|
||||||
|
- `padding:[5, 10, 5, 10]` 分别设置`上右下左`边距
|
||||||
|
- `title` 图表标题,包括标题内容、高度、颜色等
|
||||||
|
- `title.text` 标题内容
|
||||||
|
- `title.textStyle.color` 标题文字颜色
|
||||||
|
- `title.textStyle.fontSize` 标题文字字体大小
|
||||||
|
- `title.textStyle.height` 标题高度
|
||||||
|
- `xAxis` 直角坐标系grid中的x轴,由于go-echarts仅支持单一个x轴,因此若参数为数组多个x轴,只使用第一个配置
|
||||||
|
- `xAxis.boundaryGap` 坐标轴两边留白策略,仅支持三种设置方式`null`, `true`或者`false`。`null`或`true`时则数据点展示在两个刻度中间
|
||||||
|
- `xAxis.splitNumber` 坐标轴的分割段数,需要注意的是这个分割段数只是个预估值,最后实际显示的段数会在这个基础上根据分割后坐标轴刻度显示的易读程度作调整
|
||||||
|
- `xAxis.data` x轴的展示文案,暂只支持字符串数组,如["Mon", "Tue"],其数量需要与展示点一致
|
||||||
|
- `yAxis` 直角坐标系grid中的y轴,最多支持两个y轴
|
||||||
|
- `yAxis.min` 坐标轴刻度最小值,若不设置则自动计算
|
||||||
|
- `yAxis.max` 坐标轴刻度最大值,若不设置则自动计算
|
||||||
|
- `yAxis.axisLabel.formatter` 刻度标签的内容格式器,如`"formatter": "{value} kg"`
|
||||||
|
- `legend` 图表中不同系列的标记
|
||||||
|
- `legend.data` 图例的数据数组,为字符串数组,如["Email", "Video Ads"]
|
||||||
|
- `legend.align` 图例标记和文本的对齐,默认为标记靠左`left`
|
||||||
|
- `legend.padding` legend的padding,配置方式与图表的`padding`一致
|
||||||
|
- `legend.left` legend离容器左侧的距离,其值可以为具体的像素值(20)或百分比(20%)
|
||||||
|
- `legend.right` legend离容器右侧的距离,其值可以为具体的像素值(20)或百分比(20%)
|
||||||
|
- `series` 图表的数据项列表
|
||||||
|
- `series.type` 图表的展示类型,暂支持`line`, `bar`以及`pie`,需要注意`pie`只能单独使用
|
||||||
|
- `series.yAxisIndex` 该数据项使用的y轴,默认为0,对yAxis的配置对应
|
||||||
|
- `series.itemStyle.color` 该数据项展示时使用的颜色
|
||||||
|
- `series.data` 数据项对应的数据数组,支持以下形式的数据:
|
||||||
|
- `数值` 常用形式,数组数据为浮点数组,如[1.1, 2,3, 5.2]
|
||||||
|
- `结构体` pie图表或bar图表中指定样式使用,如[{"value": 1048, "name": "Search Engine"},{"value": 735,"name": "Direct"}]
|
||||||
17
charts.go
17
charts.go
|
|
@ -49,6 +49,10 @@ type (
|
||||||
Data []string
|
Data []string
|
||||||
Align string
|
Align string
|
||||||
Padding chart.Box
|
Padding chart.Box
|
||||||
|
Left string
|
||||||
|
Right string
|
||||||
|
Top string
|
||||||
|
Bottom string
|
||||||
}
|
}
|
||||||
Options struct {
|
Options struct {
|
||||||
Padding chart.Box
|
Padding chart.Box
|
||||||
|
|
@ -193,11 +197,14 @@ func newChart(opt Options) *chart.Chart {
|
||||||
if legendSize != 0 {
|
if legendSize != 0 {
|
||||||
c.Elements = []chart.Renderable{
|
c.Elements = []chart.Renderable{
|
||||||
LegendCustomize(c.Series, LegendOption{
|
LegendCustomize(c.Series, LegendOption{
|
||||||
Theme: opt.Theme,
|
Theme: opt.Theme,
|
||||||
TextPosition: LegendTextPositionRight,
|
IconDraw: DefaultLegendIconDraw,
|
||||||
IconDraw: DefaultLegendIconDraw,
|
Align: opt.Legend.Align,
|
||||||
Align: opt.Legend.Align,
|
Padding: opt.Legend.Padding,
|
||||||
Padding: opt.Legend.Padding,
|
Left: opt.Legend.Left,
|
||||||
|
Right: opt.Legend.Right,
|
||||||
|
Top: opt.Legend.Top,
|
||||||
|
Bottom: opt.Legend.Bottom,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
28
echarts.go
28
echarts.go
|
|
@ -25,6 +25,7 @@ package charts
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -81,6 +82,19 @@ type EChartsPadding struct {
|
||||||
box chart.Box
|
box chart.Box
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LegendPostion string
|
||||||
|
|
||||||
|
func (lp *LegendPostion) UnmarshalJSON(data []byte) error {
|
||||||
|
if len(data) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if regexp.MustCompile(`^\d+`).Match(data) {
|
||||||
|
data = []byte(fmt.Sprintf(`"%s"`, string(data)))
|
||||||
|
}
|
||||||
|
s := (*string)(lp)
|
||||||
|
return json.Unmarshal(data, s)
|
||||||
|
}
|
||||||
|
|
||||||
func (ep *EChartsPadding) UnmarshalJSON(data []byte) error {
|
func (ep *EChartsPadding) UnmarshalJSON(data []byte) error {
|
||||||
data = convertToArray(data)
|
data = convertToArray(data)
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
|
|
@ -128,9 +142,9 @@ func (ep *EChartsPadding) UnmarshalJSON(data []byte) error {
|
||||||
|
|
||||||
type EChartsYAxis struct {
|
type EChartsYAxis struct {
|
||||||
Data []struct {
|
Data []struct {
|
||||||
Min *float64 `json:"min"`
|
Min *float64 `json:"min"`
|
||||||
Max *float64 `json:"max"`
|
Max *float64 `json:"max"`
|
||||||
Interval int `json:"interval"`
|
// Interval int `json:"interval"`
|
||||||
AxisLabel struct {
|
AxisLabel struct {
|
||||||
Formatter string `json:"formatter"`
|
Formatter string `json:"formatter"`
|
||||||
} `json:"axisLabel"`
|
} `json:"axisLabel"`
|
||||||
|
|
@ -147,7 +161,7 @@ func (ey *EChartsYAxis) UnmarshalJSON(data []byte) error {
|
||||||
|
|
||||||
type EChartsXAxis struct {
|
type EChartsXAxis struct {
|
||||||
Data []struct {
|
Data []struct {
|
||||||
Type string `json:"type"`
|
// Type string `json:"type"`
|
||||||
BoundaryGap *bool `json:"boundaryGap"`
|
BoundaryGap *bool `json:"boundaryGap"`
|
||||||
SplitNumber int `json:"splitNumber"`
|
SplitNumber int `json:"splitNumber"`
|
||||||
Data []string `json:"data"`
|
Data []string `json:"data"`
|
||||||
|
|
@ -183,6 +197,10 @@ type ECharsOptions struct {
|
||||||
Data []string `json:"data"`
|
Data []string `json:"data"`
|
||||||
Align string `json:"align"`
|
Align string `json:"align"`
|
||||||
Padding EChartsPadding `json:"padding"`
|
Padding EChartsPadding `json:"padding"`
|
||||||
|
Left LegendPostion `json:"left"`
|
||||||
|
Right LegendPostion `json:"right"`
|
||||||
|
// Top string `json:"top"`
|
||||||
|
// Bottom string `json:"bottom"`
|
||||||
} `json:"legend"`
|
} `json:"legend"`
|
||||||
Series []struct {
|
Series []struct {
|
||||||
Data []ECharsSeriesData `json:"data"`
|
Data []ECharsSeriesData `json:"data"`
|
||||||
|
|
@ -293,6 +311,8 @@ func (e *ECharsOptions) ToOptions() Options {
|
||||||
Data: e.Legend.Data,
|
Data: e.Legend.Data,
|
||||||
Align: e.Legend.Align,
|
Align: e.Legend.Align,
|
||||||
Padding: e.Legend.Padding.box,
|
Padding: e.Legend.Padding.box,
|
||||||
|
Left: string(e.Legend.Left),
|
||||||
|
Right: string(e.Legend.Right),
|
||||||
}
|
}
|
||||||
if len(e.YAxis.Data) != 0 {
|
if len(e.YAxis.Data) != 0 {
|
||||||
yAxisOptions := make([]*YAxisOption, len(e.YAxis.Data))
|
yAxisOptions := make([]*YAxisOption, len(e.YAxis.Data))
|
||||||
|
|
|
||||||
|
|
@ -397,3 +397,25 @@ func TestParseECharsOptions(t *testing.T) {
|
||||||
},
|
},
|
||||||
}, options)
|
}, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkEChartsRender(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := RenderEChartsToPNG(`{
|
||||||
|
"title": {
|
||||||
|
"text": "Line"
|
||||||
|
},
|
||||||
|
"xAxis": {
|
||||||
|
"type": "category",
|
||||||
|
"data": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
|
||||||
|
},
|
||||||
|
"series": [
|
||||||
|
{
|
||||||
|
"data": [150, 230, 224, 218, 135, 147, 260]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,7 @@ var chartOptions = []map[string]string{
|
||||||
},
|
},
|
||||||
"legend": {
|
"legend": {
|
||||||
"align": "left",
|
"align": "left",
|
||||||
"padding": [5, 0, 0, 50],
|
"right": 0,
|
||||||
"data": ["Email", "Union Ads", "Video Ads", "Direct", "Search Engine"]
|
"data": ["Email", "Union Ads", "Video Ads", "Direct", "Search Engine"]
|
||||||
},
|
},
|
||||||
"xAxis": {
|
"xAxis": {
|
||||||
|
|
|
||||||
126
legend.go
126
legend.go
|
|
@ -23,16 +23,22 @@
|
||||||
package charts
|
package charts
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart/v2"
|
"github.com/wcharczuk/go-chart/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LegendOption struct {
|
type LegendOption struct {
|
||||||
Style chart.Style
|
Style chart.Style
|
||||||
Padding chart.Box
|
Padding chart.Box
|
||||||
Align string
|
Left string
|
||||||
TextPosition string
|
Right string
|
||||||
Theme string
|
Top string
|
||||||
IconDraw LegendIconDraw
|
Bottom string
|
||||||
|
Align string
|
||||||
|
Theme string
|
||||||
|
IconDraw LegendIconDraw
|
||||||
}
|
}
|
||||||
|
|
||||||
type LegendIconDrawOption struct {
|
type LegendIconDrawOption struct {
|
||||||
|
|
@ -43,13 +49,8 @@ type LegendIconDrawOption struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
LegendAlignLeft = "left"
|
LegendAlignLeft = "left"
|
||||||
LegendAlignCenter = "center"
|
LegendAlignRight = "right"
|
||||||
LegendAlignRight = "right"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
LegendTextPositionRight = "right"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type LegendIconDraw func(r chart.Renderer, opt LegendIconDrawOption)
|
type LegendIconDraw func(r chart.Renderer, opt LegendIconDrawOption)
|
||||||
|
|
@ -71,6 +72,61 @@ func DefaultLegendIconDraw(r chart.Renderer, opt LegendIconDrawOption) {
|
||||||
r.FillStroke()
|
r.FillStroke()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func covertPercent(value string) float64 {
|
||||||
|
if !strings.HasSuffix(value, "%") {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
v, err := strconv.Atoi(strings.ReplaceAll(value, "%", ""))
|
||||||
|
if err != nil {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return float64(v) / 100
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLegendLeft(width, legendBoxWidth int, opt LegendOption) int {
|
||||||
|
left := (width - legendBoxWidth) / 2
|
||||||
|
leftValue := opt.Left
|
||||||
|
if leftValue == "auto" || leftValue == "center" {
|
||||||
|
leftValue = ""
|
||||||
|
}
|
||||||
|
if leftValue == "left" {
|
||||||
|
leftValue = "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
rightValue := opt.Right
|
||||||
|
if rightValue == "auto" || leftValue == "center" {
|
||||||
|
rightValue = ""
|
||||||
|
}
|
||||||
|
if rightValue == "right" {
|
||||||
|
rightValue = "0"
|
||||||
|
}
|
||||||
|
if leftValue == "" && rightValue == "" {
|
||||||
|
return left
|
||||||
|
}
|
||||||
|
if leftValue != "" {
|
||||||
|
percent := covertPercent(leftValue)
|
||||||
|
if percent >= 0 {
|
||||||
|
return int(float64(width) * percent)
|
||||||
|
}
|
||||||
|
v, _ := strconv.Atoi(leftValue)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
if rightValue != "" {
|
||||||
|
percent := covertPercent(rightValue)
|
||||||
|
if percent >= 0 {
|
||||||
|
return width - legendBoxWidth - int(float64(width)*percent)
|
||||||
|
}
|
||||||
|
v, _ := strconv.Atoi(rightValue)
|
||||||
|
return width - legendBoxWidth - v
|
||||||
|
}
|
||||||
|
return left
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLegendTop(height, legendBoxHeight int, opt LegendOption) int {
|
||||||
|
// TODO 支持top的处理
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
func LegendCustomize(series []chart.Series, opt LegendOption) chart.Renderable {
|
func LegendCustomize(series []chart.Series, opt LegendOption) chart.Renderable {
|
||||||
return func(r chart.Renderer, cb chart.Box, chartDefaults chart.Style) {
|
return func(r chart.Renderer, cb chart.Box, chartDefaults chart.Style) {
|
||||||
legendDefaults := chart.Style{
|
legendDefaults := chart.Style{
|
||||||
|
|
@ -115,26 +171,23 @@ func LegendCustomize(series []chart.Series, opt LegendOption) chart.Renderable {
|
||||||
chartPadding := cb.Top
|
chartPadding := cb.Top
|
||||||
legendYMargin := (chartPadding - legendBoxHeight) >> 1
|
legendYMargin := (chartPadding - legendBoxHeight) >> 1
|
||||||
|
|
||||||
lineLengthMinimum := 25
|
iconWidth := 25
|
||||||
|
lineTextGap := 5
|
||||||
|
|
||||||
labelWidth += lineLengthMinimum * len(labels)
|
iconAllWidth := iconWidth * len(labels)
|
||||||
|
spaceAllWidth := chart.DefaultMinimumTickHorizontalSpacing * (len(labels) - 1)
|
||||||
|
|
||||||
left := 0
|
legendBoxWidth := labelWidth + iconAllWidth + spaceAllWidth
|
||||||
switch opt.Align {
|
|
||||||
case LegendAlignLeft:
|
left := getLegendLeft(cb.Width(), legendBoxWidth, opt)
|
||||||
left = 0
|
top := getLegendTop(cb.Height(), legendBoxHeight, opt)
|
||||||
case LegendAlignRight:
|
|
||||||
left = cb.Width() - labelWidth
|
|
||||||
default:
|
|
||||||
left = (cb.Width() - labelWidth) / 2
|
|
||||||
}
|
|
||||||
|
|
||||||
left += opt.Padding.Left
|
left += opt.Padding.Left
|
||||||
top := opt.Padding.Top
|
top += opt.Padding.Top
|
||||||
|
|
||||||
legendBox := chart.Box{
|
legendBox := chart.Box{
|
||||||
Left: left,
|
Left: left,
|
||||||
Right: left + labelWidth,
|
Right: left + legendBoxWidth,
|
||||||
Top: top,
|
Top: top,
|
||||||
Bottom: top + legendBoxHeight,
|
Bottom: top + legendBoxHeight,
|
||||||
}
|
}
|
||||||
|
|
@ -145,8 +198,6 @@ func LegendCustomize(series []chart.Series, opt LegendOption) chart.Renderable {
|
||||||
r.SetFontColor(legendStyle.GetFontColor())
|
r.SetFontColor(legendStyle.GetFontColor())
|
||||||
r.SetFontSize(legendStyle.GetFontSize())
|
r.SetFontSize(legendStyle.GetFontSize())
|
||||||
|
|
||||||
lineTextGap := 5
|
|
||||||
|
|
||||||
startX := legendBox.Left + legendStyle.Padding.Left
|
startX := legendBox.Left + legendStyle.Padding.Left
|
||||||
ty := top + legendYMargin + legendStyle.Padding.Top + textHeight
|
ty := top + legendYMargin + legendStyle.Padding.Top + textHeight
|
||||||
var label string
|
var label string
|
||||||
|
|
@ -155,13 +206,17 @@ func LegendCustomize(series []chart.Series, opt LegendOption) chart.Renderable {
|
||||||
if iconDraw == nil {
|
if iconDraw == nil {
|
||||||
iconDraw = DefaultLegendIconDraw
|
iconDraw = DefaultLegendIconDraw
|
||||||
}
|
}
|
||||||
|
align := opt.Align
|
||||||
|
if align == "" {
|
||||||
|
align = LegendAlignLeft
|
||||||
|
}
|
||||||
for index := range labels {
|
for index := range labels {
|
||||||
label = labels[index]
|
label = labels[index]
|
||||||
if len(label) > 0 {
|
if len(label) > 0 {
|
||||||
x = startX
|
x = startX
|
||||||
|
|
||||||
// 如果文本靠左显示
|
// 如果图例标记靠右展示
|
||||||
if opt.TextPosition != LegendTextPositionRight {
|
if align == LegendAlignRight {
|
||||||
textBox = r.MeasureText(label)
|
textBox = r.MeasureText(label)
|
||||||
r.Text(label, x, ty)
|
r.Text(label, x, ty)
|
||||||
x = startX + textBox.Width() + lineTextGap
|
x = startX + textBox.Width() + lineTextGap
|
||||||
|
|
@ -175,20 +230,21 @@ func LegendCustomize(series []chart.Series, opt LegendOption) chart.Renderable {
|
||||||
Box: chart.Box{
|
Box: chart.Box{
|
||||||
Left: x,
|
Left: x,
|
||||||
Top: ty,
|
Top: ty,
|
||||||
Right: x + lineLengthMinimum,
|
Right: x + iconWidth,
|
||||||
Bottom: ty + textHeight,
|
Bottom: ty + textHeight,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
x += (lineLengthMinimum + lineTextGap)
|
x += (iconWidth + lineTextGap)
|
||||||
|
|
||||||
// 如果文本靠右显示
|
// 如果图例标记靠左展示
|
||||||
if opt.TextPosition == LegendTextPositionRight {
|
if align == LegendAlignLeft {
|
||||||
textBox = r.MeasureText(label)
|
textBox = r.MeasureText(label)
|
||||||
r.Text(label, x, ty)
|
r.Text(label, x, ty)
|
||||||
|
x += textBox.Width()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算下一个legend的位置
|
// 计算下一个legend的位置
|
||||||
startX += textBox.Width() + chart.DefaultMinimumTickHorizontalSpacing + lineTextGap + lineLengthMinimum
|
startX = x + chart.DefaultMinimumTickHorizontalSpacing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,16 +43,16 @@ func TestLegendCustomize(t *testing.T) {
|
||||||
}, chart.TickPositionBetweenTicks, "")
|
}, chart.TickPositionBetweenTicks, "")
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
textPosition string
|
align string
|
||||||
svg string
|
svg string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
textPosition: LegendTextPositionRight,
|
align: LegendAlignLeft,
|
||||||
svg: "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"800\" height=\"600\">\\n<path d=\"M 100 100\nL 208 100\nL 208 110\nL 100 110\nL 100 100\" style=\"stroke-width:0;stroke:rgba(51,51,51,1.0);fill:none\"/><path d=\"M 100 107\nL 125 107\" style=\"stroke-width:2;stroke:rgba(84,112,198,1.0);fill:none\"/><circle cx=\"112\" cy=\"107\" r=\"5\" style=\"stroke-width:2;stroke:rgba(84,112,198,1.0);fill:rgba(255,255,255,1.0)\"/><path d=\"\" style=\"stroke-width:2;stroke:rgba(84,112,198,1.0);fill:rgba(255,255,255,1.0)\"/><text x=\"130\" y=\"110\" style=\"stroke-width:0;stroke:none;fill:rgba(51,51,51,1.0);font-size:10.2px;font-family:'Roboto Medium',sans-serif\">chrome</text><path d=\"M 185 107\nL 210 107\" style=\"stroke-width:2;stroke:rgba(145,204,117,1.0);fill:rgba(255,255,255,1.0)\"/><circle cx=\"197\" cy=\"107\" r=\"5\" style=\"stroke-width:2;stroke:rgba(145,204,117,1.0);fill:rgba(255,255,255,1.0)\"/><path d=\"\" style=\"stroke-width:2;stroke:rgba(145,204,117,1.0);fill:rgba(255,255,255,1.0)\"/><text x=\"215\" y=\"110\" style=\"stroke-width:0;stroke:none;fill:rgba(51,51,51,1.0);font-size:10.2px;font-family:'Roboto Medium',sans-serif\">edge</text></svg>",
|
svg: "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"800\" height=\"600\">\\n<path d=\"M 404 100\nL 532 100\nL 532 110\nL 404 110\nL 404 100\" style=\"stroke-width:0;stroke:rgba(51,51,51,1.0);fill:none\"/><path d=\"M 404 107\nL 429 107\" style=\"stroke-width:2;stroke:rgba(84,112,198,1.0);fill:none\"/><circle cx=\"416\" cy=\"107\" r=\"5\" style=\"stroke-width:2;stroke:rgba(84,112,198,1.0);fill:rgba(255,255,255,1.0)\"/><path d=\"\" style=\"stroke-width:2;stroke:rgba(84,112,198,1.0);fill:rgba(255,255,255,1.0)\"/><text x=\"434\" y=\"110\" style=\"stroke-width:0;stroke:none;fill:rgba(51,51,51,1.0);font-size:10.2px;font-family:'Roboto Medium',sans-serif\">chrome</text><path d=\"M 489 107\nL 514 107\" style=\"stroke-width:2;stroke:rgba(145,204,117,1.0);fill:rgba(255,255,255,1.0)\"/><circle cx=\"501\" cy=\"107\" r=\"5\" style=\"stroke-width:2;stroke:rgba(145,204,117,1.0);fill:rgba(255,255,255,1.0)\"/><path d=\"\" style=\"stroke-width:2;stroke:rgba(145,204,117,1.0);fill:rgba(255,255,255,1.0)\"/><text x=\"519\" y=\"110\" style=\"stroke-width:0;stroke:none;fill:rgba(51,51,51,1.0);font-size:10.2px;font-family:'Roboto Medium',sans-serif\">edge</text></svg>",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
textPosition: LegendAlignLeft,
|
align: LegendAlignRight,
|
||||||
svg: "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"800\" height=\"600\">\\n<path d=\"M 100 100\nL 208 100\nL 208 110\nL 100 110\nL 100 100\" style=\"stroke-width:0;stroke:rgba(51,51,51,1.0);fill:none\"/><text x=\"100\" y=\"110\" style=\"stroke-width:0;stroke:none;fill:rgba(51,51,51,1.0);font-size:10.2px;font-family:'Roboto Medium',sans-serif\">chrome</text><path d=\"M 140 107\nL 165 107\" style=\"stroke-width:2;stroke:rgba(84,112,198,1.0);fill:none\"/><circle cx=\"152\" cy=\"107\" r=\"5\" style=\"stroke-width:2;stroke:rgba(84,112,198,1.0);fill:rgba(255,255,255,1.0)\"/><path d=\"\" style=\"stroke-width:2;stroke:rgba(84,112,198,1.0);fill:rgba(255,255,255,1.0)\"/><text x=\"185\" y=\"110\" style=\"stroke-width:0;stroke:none;fill:rgba(51,51,51,1.0);font-size:10.2px;font-family:'Roboto Medium',sans-serif\">edge</text><path d=\"M 213 107\nL 238 107\" style=\"stroke-width:2;stroke:rgba(145,204,117,1.0);fill:rgba(255,255,255,1.0)\"/><circle cx=\"225\" cy=\"107\" r=\"5\" style=\"stroke-width:2;stroke:rgba(145,204,117,1.0);fill:rgba(255,255,255,1.0)\"/><path d=\"\" style=\"stroke-width:2;stroke:rgba(145,204,117,1.0);fill:rgba(255,255,255,1.0)\"/></svg>",
|
svg: "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"800\" height=\"600\">\\n<path d=\"M 404 100\nL 532 100\nL 532 110\nL 404 110\nL 404 100\" style=\"stroke-width:0;stroke:rgba(51,51,51,1.0);fill:none\"/><text x=\"404\" y=\"110\" style=\"stroke-width:0;stroke:none;fill:rgba(51,51,51,1.0);font-size:10.2px;font-family:'Roboto Medium',sans-serif\">chrome</text><path d=\"M 444 107\nL 469 107\" style=\"stroke-width:2;stroke:rgba(84,112,198,1.0);fill:none\"/><circle cx=\"456\" cy=\"107\" r=\"5\" style=\"stroke-width:2;stroke:rgba(84,112,198,1.0);fill:rgba(255,255,255,1.0)\"/><path d=\"\" style=\"stroke-width:2;stroke:rgba(84,112,198,1.0);fill:rgba(255,255,255,1.0)\"/><text x=\"494\" y=\"110\" style=\"stroke-width:0;stroke:none;fill:rgba(51,51,51,1.0);font-size:10.2px;font-family:'Roboto Medium',sans-serif\">edge</text><path d=\"M 522 107\nL 547 107\" style=\"stroke-width:2;stroke:rgba(145,204,117,1.0);fill:rgba(255,255,255,1.0)\"/><circle cx=\"534\" cy=\"107\" r=\"5\" style=\"stroke-width:2;stroke:rgba(145,204,117,1.0);fill:rgba(255,255,255,1.0)\"/><path d=\"\" style=\"stroke-width:2;stroke:rgba(145,204,117,1.0);fill:rgba(255,255,255,1.0)\"/></svg>",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -60,9 +60,8 @@ func TestLegendCustomize(t *testing.T) {
|
||||||
r, err := chart.SVG(800, 600)
|
r, err := chart.SVG(800, 600)
|
||||||
assert.Nil(err)
|
assert.Nil(err)
|
||||||
fn := LegendCustomize(series, LegendOption{
|
fn := LegendCustomize(series, LegendOption{
|
||||||
TextPosition: tt.textPosition,
|
Align: tt.align,
|
||||||
IconDraw: DefaultLegendIconDraw,
|
IconDraw: DefaultLegendIconDraw,
|
||||||
Align: LegendAlignLeft,
|
|
||||||
Padding: chart.Box{
|
Padding: chart.Box{
|
||||||
Left: 100,
|
Left: 100,
|
||||||
Top: 100,
|
Top: 100,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue