feat: support echart options
This commit is contained in:
parent
b934b853a9
commit
519c8a492e
8 changed files with 803 additions and 2 deletions
|
|
@ -125,6 +125,10 @@ func barChartRender(opt barChartOption, result *basicRenderResult) ([]markPointR
|
|||
if !series.Label.Show {
|
||||
continue
|
||||
}
|
||||
distance := series.Label.Distance
|
||||
if distance == 0 {
|
||||
distance = 5
|
||||
}
|
||||
text := NewValueLabelFormater(seriesNames, series.Label.Formatter)(i, item.Value, -1)
|
||||
labelStyle := chart.Style{
|
||||
FontColor: theme.GetTextColor(),
|
||||
|
|
@ -136,7 +140,7 @@ func barChartRender(opt barChartOption, result *basicRenderResult) ([]markPointR
|
|||
}
|
||||
labelStyle.GetTextOptions().WriteToRenderer(r)
|
||||
textBox := r.MeasureText(text)
|
||||
d.text(text, x+(barWidth-textBox.Width())>>1, barMaxHeight-h-5)
|
||||
d.text(text, x+(barWidth-textBox.Width())>>1, barMaxHeight-h-distance)
|
||||
}
|
||||
|
||||
// 生成mark point的参数
|
||||
|
|
|
|||
5
chart.go
5
chart.go
|
|
@ -25,6 +25,7 @@ package charts
|
|||
import (
|
||||
"errors"
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/freetype/truetype"
|
||||
"github.com/wcharczuk/go-chart/v2"
|
||||
|
|
@ -155,6 +156,10 @@ func (o *ChartOption) FillDefault(theme string) {
|
|||
}
|
||||
}
|
||||
}
|
||||
// 如果无legend数据,则隐藏
|
||||
if len(strings.Join(o.Legend.Data, "")) == 0 {
|
||||
o.Legend.Show = FalseFlag()
|
||||
}
|
||||
if o.Legend.Style.Font == nil {
|
||||
o.Legend.Style.Font = o.Font
|
||||
}
|
||||
|
|
|
|||
407
echarts.go
Normal file
407
echarts.go
Normal file
|
|
@ -0,0 +1,407 @@
|
|||
// 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 (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
"github.com/wcharczuk/go-chart/v2"
|
||||
)
|
||||
|
||||
func convertToArray(data []byte) []byte {
|
||||
data = bytes.TrimSpace(data)
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
if data[0] != '[' {
|
||||
data = []byte("[" + string(data) + "]")
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
type EChartsPosition string
|
||||
|
||||
func (p *EChartsPosition) 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)(p)
|
||||
return json.Unmarshal(data, s)
|
||||
}
|
||||
|
||||
type EChartStyle struct {
|
||||
Color string `json:"color"`
|
||||
}
|
||||
|
||||
func (es *EChartStyle) ToStyle() chart.Style {
|
||||
color := parseColor(es.Color)
|
||||
return chart.Style{
|
||||
FillColor: color,
|
||||
FontColor: color,
|
||||
StrokeColor: color,
|
||||
}
|
||||
}
|
||||
|
||||
type EChartsSeriesData struct {
|
||||
Value float64 `json:"value"`
|
||||
Name string `json:"name"`
|
||||
ItemStyle EChartStyle `json:"itemStyle"`
|
||||
}
|
||||
type _EChartsSeriesData EChartsSeriesData
|
||||
|
||||
func (es *EChartsSeriesData) UnmarshalJSON(data []byte) error {
|
||||
data = bytes.TrimSpace(data)
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
if regexp.MustCompile(`^\d+`).Match(data) {
|
||||
v, err := strconv.ParseFloat(string(data), 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
es.Value = v
|
||||
return nil
|
||||
}
|
||||
v := _EChartsSeriesData{}
|
||||
err := json.Unmarshal(data, &v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
es.Name = v.Name
|
||||
es.Value = v.Value
|
||||
es.ItemStyle = v.ItemStyle
|
||||
return nil
|
||||
}
|
||||
|
||||
type EChartsXAxisData struct {
|
||||
BoundaryGap *bool `json:"boundaryGap"`
|
||||
SplitNumber int `json:"splitNumber"`
|
||||
Data []string `json:"data"`
|
||||
}
|
||||
type EChartsXAxis struct {
|
||||
Data []EChartsXAxisData
|
||||
}
|
||||
|
||||
func (ex *EChartsXAxis) UnmarshalJSON(data []byte) error {
|
||||
data = convertToArray(data)
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
return json.Unmarshal(data, &ex.Data)
|
||||
}
|
||||
|
||||
type EChartsAxisLabel struct {
|
||||
Formatter string `json:"formatter"`
|
||||
}
|
||||
type EChartsYAxisData struct {
|
||||
Min *float64 `json:"min"`
|
||||
Max *float64 `json:"max"`
|
||||
AxisLabel EChartsAxisLabel `json:"axisLabel"`
|
||||
AxisLine struct {
|
||||
LineStyle struct {
|
||||
Color string
|
||||
}
|
||||
}
|
||||
}
|
||||
type EChartsYAxis struct {
|
||||
Data []EChartsYAxisData `json:"data"`
|
||||
}
|
||||
|
||||
func (ey *EChartsYAxis) UnmarshalJSON(data []byte) error {
|
||||
data = convertToArray(data)
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
return json.Unmarshal(data, &ey.Data)
|
||||
}
|
||||
|
||||
type EChartsPadding struct {
|
||||
Box chart.Box
|
||||
}
|
||||
|
||||
func (eb *EChartsPadding) UnmarshalJSON(data []byte) error {
|
||||
data = convertToArray(data)
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
arr := make([]int, 0)
|
||||
err := json.Unmarshal(data, &arr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(arr) == 0 {
|
||||
return nil
|
||||
}
|
||||
switch len(arr) {
|
||||
case 1:
|
||||
eb.Box = chart.Box{
|
||||
Left: arr[0],
|
||||
Top: arr[0],
|
||||
Bottom: arr[0],
|
||||
Right: arr[0],
|
||||
}
|
||||
case 2:
|
||||
eb.Box = chart.Box{
|
||||
Top: arr[0],
|
||||
Bottom: arr[0],
|
||||
Left: arr[1],
|
||||
Right: arr[1],
|
||||
}
|
||||
default:
|
||||
result := make([]int, 4)
|
||||
copy(result, arr)
|
||||
if len(arr) == 3 {
|
||||
result[3] = result[1]
|
||||
}
|
||||
// 上右下左
|
||||
eb.Box = chart.Box{
|
||||
Top: result[0],
|
||||
Right: result[1],
|
||||
Bottom: result[2],
|
||||
Left: result[3],
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type EChartsLabelOption struct {
|
||||
Show bool `json:"show"`
|
||||
Distance int `json:"distance"`
|
||||
Color string `json:"color"`
|
||||
}
|
||||
type EChartsLegend struct {
|
||||
Show *bool `json:"show"`
|
||||
Data []string `json:"data"`
|
||||
Align string `json:"align"`
|
||||
Orient string `json:"orient"`
|
||||
Padding EChartsPadding `json:"padding"`
|
||||
Left EChartsPosition `json:"left"`
|
||||
Top EChartsPosition `json:"top"`
|
||||
TextStyle EChartsTextStyle `json:"textStyle"`
|
||||
}
|
||||
|
||||
type EChartsMarkPoint struct {
|
||||
SymbolSize int `json:"symbolSize"`
|
||||
Data []struct {
|
||||
Type string `json:"type"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
func (emp *EChartsMarkPoint) ToSeriesMarkPoint() SeriesMarkPoint {
|
||||
data := make([]SeriesMarkData, len(emp.Data))
|
||||
for index, item := range emp.Data {
|
||||
data[index] = SeriesMarkData{
|
||||
Type: item.Type,
|
||||
}
|
||||
}
|
||||
return SeriesMarkPoint{
|
||||
Data: data,
|
||||
SymbolSize: emp.SymbolSize,
|
||||
}
|
||||
}
|
||||
|
||||
type EChartsMarkLine struct {
|
||||
Data []struct {
|
||||
Type string `json:"type"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
func (eml *EChartsMarkLine) ToSeriesMarkLine() SeriesMarkLine {
|
||||
data := make([]SeriesMarkData, len(eml.Data))
|
||||
for index, item := range eml.Data {
|
||||
data[index] = SeriesMarkData{
|
||||
Type: item.Type,
|
||||
}
|
||||
}
|
||||
return SeriesMarkLine{
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
type EChartsSeries struct {
|
||||
Data []EChartsSeriesData `json:"data"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Radius string `json:"radius"`
|
||||
YAxisIndex int `json:"yAxisIndex"`
|
||||
ItemStyle EChartStyle `json:"itemStyle"`
|
||||
// label的配置
|
||||
Label EChartsLabelOption `json:"label"`
|
||||
MarkPoint EChartsMarkPoint `json:"markPoint"`
|
||||
MarkLine EChartsMarkLine `json:"markLine"`
|
||||
}
|
||||
type EChartsSeriesList []EChartsSeries
|
||||
|
||||
func (esList EChartsSeriesList) ToSeriesList() SeriesList {
|
||||
seriesList := make(SeriesList, len(esList))
|
||||
for index, item := range esList {
|
||||
data := make([]SeriesData, len(item.Data))
|
||||
for j, dataItem := range item.Data {
|
||||
data[j] = SeriesData{
|
||||
Value: dataItem.Value,
|
||||
Style: dataItem.ItemStyle.ToStyle(),
|
||||
}
|
||||
}
|
||||
seriesList[index] = Series{
|
||||
Type: item.Type,
|
||||
Data: data,
|
||||
YAxisIndex: item.YAxisIndex,
|
||||
Style: item.ItemStyle.ToStyle(),
|
||||
Label: SeriesLabel{
|
||||
Color: parseColor(item.Label.Color),
|
||||
Show: item.Label.Show,
|
||||
Distance: item.Label.Distance,
|
||||
},
|
||||
Name: item.Name,
|
||||
Radius: item.Radius,
|
||||
MarkPoint: item.MarkPoint.ToSeriesMarkPoint(),
|
||||
MarkLine: item.MarkLine.ToSeriesMarkLine(),
|
||||
}
|
||||
}
|
||||
return seriesList
|
||||
}
|
||||
|
||||
type EChartsTextStyle struct {
|
||||
Color string `json:"color"`
|
||||
FontFamily string `json:"fontFamily"`
|
||||
FontSize float64 `json:"fontSize"`
|
||||
}
|
||||
|
||||
func (et *EChartsTextStyle) ToStyle() chart.Style {
|
||||
return chart.Style{
|
||||
FontSize: et.FontSize,
|
||||
FontColor: parseColor(et.Color),
|
||||
}
|
||||
}
|
||||
|
||||
type EChartsOption struct {
|
||||
Type string `json:"type"`
|
||||
Theme string `json:"theme"`
|
||||
FontFamily string `json:"fontFamily"`
|
||||
Padding EChartsPadding `json:"padding"`
|
||||
Box chart.Box `json:"box"`
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
Title struct {
|
||||
Text string `json:"text"`
|
||||
Subtext string `json:"subtext"`
|
||||
Left EChartsPosition `json:"left"`
|
||||
Top EChartsPosition `json:"top"`
|
||||
TextStyle EChartsTextStyle `json:"textStyle"`
|
||||
SubtextStyle EChartsTextStyle `json:"subtextStyle"`
|
||||
} `json:"title"`
|
||||
XAxis EChartsXAxis `json:"xAxis"`
|
||||
YAxis EChartsYAxis `json:"yAxis"`
|
||||
Legend EChartsLegend `json:"legend"`
|
||||
Series EChartsSeriesList `json:"series"`
|
||||
Children []EChartsOption `json:"children"`
|
||||
}
|
||||
|
||||
func (eo *EChartsOption) ToOption() ChartOption {
|
||||
fontFamily := eo.FontFamily
|
||||
if len(fontFamily) == 0 {
|
||||
fontFamily = eo.Title.TextStyle.FontFamily
|
||||
}
|
||||
o := ChartOption{
|
||||
Type: eo.Type,
|
||||
FontFamily: fontFamily,
|
||||
Theme: eo.Theme,
|
||||
Title: TitleOption{
|
||||
Text: eo.Title.Text,
|
||||
Subtext: eo.Title.Subtext,
|
||||
Style: eo.Title.TextStyle.ToStyle(),
|
||||
SubtextStyle: eo.Title.SubtextStyle.ToStyle(),
|
||||
Left: string(eo.Title.Left),
|
||||
Top: string(eo.Title.Top),
|
||||
},
|
||||
Legend: LegendOption{
|
||||
Show: eo.Legend.Show,
|
||||
Style: eo.Legend.TextStyle.ToStyle(),
|
||||
Data: eo.Legend.Data,
|
||||
Left: string(eo.Legend.Left),
|
||||
Top: string(eo.Legend.Top),
|
||||
Align: eo.Legend.Align,
|
||||
Orient: eo.Legend.Orient,
|
||||
},
|
||||
Width: eo.Width,
|
||||
Height: eo.Height,
|
||||
Padding: eo.Padding.Box,
|
||||
Box: eo.Box,
|
||||
SeriesList: eo.Series.ToSeriesList(),
|
||||
}
|
||||
if len(eo.XAxis.Data) != 0 {
|
||||
xAxisData := eo.XAxis.Data[0]
|
||||
o.XAxis = XAxisOption{
|
||||
BoundaryGap: xAxisData.BoundaryGap,
|
||||
Data: xAxisData.Data,
|
||||
SplitNumber: xAxisData.SplitNumber,
|
||||
}
|
||||
}
|
||||
yAxisOptions := make([]YAxisOption, len(eo.YAxis.Data))
|
||||
for index, item := range eo.YAxis.Data {
|
||||
yAxisOptions[index] = YAxisOption{
|
||||
Min: item.Min,
|
||||
Max: item.Max,
|
||||
Formatter: item.AxisLabel.Formatter,
|
||||
Color: parseColor(item.AxisLine.LineStyle.Color),
|
||||
}
|
||||
}
|
||||
|
||||
if len(eo.Children) != 0 {
|
||||
o.Children = make([]ChartOption, len(eo.Children))
|
||||
for index, item := range eo.Children {
|
||||
o.Children[index] = item.ToOption()
|
||||
}
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
func renderEcharts(options, outputType string) ([]byte, error) {
|
||||
o := EChartsOption{}
|
||||
err := json.Unmarshal([]byte(options), &o)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opt := o.ToOption()
|
||||
opt.Type = outputType
|
||||
d, err := Render(opt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.Bytes()
|
||||
}
|
||||
|
||||
func RenderEChartsToPNG(options string) ([]byte, error) {
|
||||
return renderEcharts(options, "png")
|
||||
}
|
||||
|
||||
func RenderEChartsToSVG(options string) ([]byte, error) {
|
||||
return renderEcharts(options, "svg")
|
||||
}
|
||||
301
echarts_test.go
Normal file
301
echarts_test.go
Normal file
|
|
@ -0,0 +1,301 @@
|
|||
// 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 (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/wcharczuk/go-chart/v2"
|
||||
)
|
||||
|
||||
func TestEChartsPosition(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
var p EChartsPosition
|
||||
err := p.UnmarshalJSON([]byte("12"))
|
||||
assert.Nil(err)
|
||||
assert.Equal("12", string(p))
|
||||
|
||||
err = p.UnmarshalJSON([]byte(`"12%"`))
|
||||
assert.Nil(err)
|
||||
assert.Equal("12%", string(p))
|
||||
}
|
||||
|
||||
func TestEChartsXAxis(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
ex := EChartsXAxis{}
|
||||
err := ex.UnmarshalJSON([]byte(`{
|
||||
"boundaryGap": false,
|
||||
"splitNumber": 5,
|
||||
"data": [
|
||||
"Mon",
|
||||
"Tue",
|
||||
"Wed",
|
||||
"Thu",
|
||||
"Fri",
|
||||
"Sat",
|
||||
"Sun"
|
||||
]
|
||||
}`))
|
||||
assert.Nil(err)
|
||||
assert.Equal(EChartsXAxis{
|
||||
Data: []EChartsXAxisData{
|
||||
{
|
||||
BoundaryGap: FalseFlag(),
|
||||
SplitNumber: 5,
|
||||
Data: []string{
|
||||
"Mon",
|
||||
"Tue",
|
||||
"Wed",
|
||||
"Thu",
|
||||
"Fri",
|
||||
"Sat",
|
||||
"Sun",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, ex)
|
||||
}
|
||||
|
||||
func TestEChartsYAxis(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
ey := EChartsYAxis{}
|
||||
|
||||
err := ey.UnmarshalJSON([]byte(`{
|
||||
"min": 1,
|
||||
"max": 10,
|
||||
"axisLabel": {
|
||||
"formatter": "ab"
|
||||
}
|
||||
}`))
|
||||
assert.Nil(err)
|
||||
assert.Equal(EChartsYAxis{
|
||||
Data: []EChartsYAxisData{
|
||||
{
|
||||
Min: NewFloatPoint(1),
|
||||
Max: NewFloatPoint(10),
|
||||
AxisLabel: EChartsAxisLabel{
|
||||
Formatter: "ab",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, ey)
|
||||
|
||||
ey = EChartsYAxis{}
|
||||
err = ey.UnmarshalJSON([]byte(`[
|
||||
{
|
||||
"min": 1,
|
||||
"max": 10,
|
||||
"axisLabel": {
|
||||
"formatter": "ab"
|
||||
}
|
||||
},
|
||||
{
|
||||
"min": 2,
|
||||
"max": 20,
|
||||
"axisLabel": {
|
||||
"formatter": "cd"
|
||||
}
|
||||
}
|
||||
]`))
|
||||
assert.Nil(err)
|
||||
assert.Equal(EChartsYAxis{
|
||||
Data: []EChartsYAxisData{
|
||||
{
|
||||
Min: NewFloatPoint(1),
|
||||
Max: NewFloatPoint(10),
|
||||
AxisLabel: EChartsAxisLabel{
|
||||
Formatter: "ab",
|
||||
},
|
||||
},
|
||||
{
|
||||
Min: NewFloatPoint(2),
|
||||
Max: NewFloatPoint(20),
|
||||
AxisLabel: EChartsAxisLabel{
|
||||
Formatter: "cd",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, ey)
|
||||
}
|
||||
|
||||
func TestEChartsPadding(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
ep := EChartsPadding{}
|
||||
|
||||
ep.UnmarshalJSON([]byte(`10`))
|
||||
assert.Equal(EChartsPadding{
|
||||
Box: chart.Box{
|
||||
Top: 10,
|
||||
Right: 10,
|
||||
Bottom: 10,
|
||||
Left: 10,
|
||||
},
|
||||
}, ep)
|
||||
|
||||
ep = EChartsPadding{}
|
||||
ep.UnmarshalJSON([]byte(`[10, 20]`))
|
||||
assert.Equal(EChartsPadding{
|
||||
Box: chart.Box{
|
||||
Top: 10,
|
||||
Right: 20,
|
||||
Bottom: 10,
|
||||
Left: 20,
|
||||
},
|
||||
}, ep)
|
||||
|
||||
ep = EChartsPadding{}
|
||||
ep.UnmarshalJSON([]byte(`[10, 20, 30]`))
|
||||
assert.Equal(EChartsPadding{
|
||||
Box: chart.Box{
|
||||
Top: 10,
|
||||
Right: 20,
|
||||
Bottom: 30,
|
||||
Left: 20,
|
||||
},
|
||||
}, ep)
|
||||
|
||||
ep = EChartsPadding{}
|
||||
ep.UnmarshalJSON([]byte(`[10, 20, 30, 40]`))
|
||||
assert.Equal(EChartsPadding{
|
||||
Box: chart.Box{
|
||||
Top: 10,
|
||||
Right: 20,
|
||||
Bottom: 30,
|
||||
Left: 40,
|
||||
},
|
||||
}, ep)
|
||||
|
||||
}
|
||||
func TestEChartsLegend(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
el := EChartsLegend{}
|
||||
|
||||
err := json.Unmarshal([]byte(`{
|
||||
"data": ["a", "b", "c"],
|
||||
"align": "right",
|
||||
"padding": [10],
|
||||
"left": "20%",
|
||||
"top": 10
|
||||
}`), &el)
|
||||
assert.Nil(err)
|
||||
assert.Equal(EChartsLegend{
|
||||
Data: []string{
|
||||
"a",
|
||||
"b",
|
||||
"c",
|
||||
},
|
||||
Align: "right",
|
||||
Padding: EChartsPadding{
|
||||
Box: chart.Box{
|
||||
Left: 10,
|
||||
Top: 10,
|
||||
Right: 10,
|
||||
Bottom: 10,
|
||||
},
|
||||
},
|
||||
Left: EChartsPosition("20%"),
|
||||
Top: EChartsPosition("10"),
|
||||
}, el)
|
||||
}
|
||||
|
||||
func TestEChartsSeriesData(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
esd := EChartsSeriesData{}
|
||||
err := esd.UnmarshalJSON([]byte(`123`))
|
||||
assert.Nil(err)
|
||||
assert.Equal(EChartsSeriesData{
|
||||
Value: 123,
|
||||
}, esd)
|
||||
|
||||
esd = EChartsSeriesData{}
|
||||
err = esd.UnmarshalJSON([]byte(`{
|
||||
"value": 123.12,
|
||||
"name": "test",
|
||||
"itemStyle": {
|
||||
"color": "#aaa"
|
||||
}
|
||||
}`))
|
||||
assert.Nil(err)
|
||||
assert.Equal(EChartsSeriesData{
|
||||
Value: 123.12,
|
||||
Name: "test",
|
||||
ItemStyle: EChartStyle{
|
||||
Color: "#aaa",
|
||||
},
|
||||
}, esd)
|
||||
}
|
||||
|
||||
func TestEChartsSeries(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
esList := make([]EChartsSeries, 0)
|
||||
err := json.Unmarshal([]byte(`[
|
||||
{
|
||||
"name": "Email",
|
||||
"data": [
|
||||
120,
|
||||
132
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Union Ads",
|
||||
"type": "bar",
|
||||
"data": [
|
||||
220,
|
||||
182
|
||||
]
|
||||
}
|
||||
]`), &esList)
|
||||
assert.Nil(err)
|
||||
assert.Equal([]EChartsSeries{
|
||||
{
|
||||
Name: "Email",
|
||||
Data: []EChartsSeriesData{
|
||||
{
|
||||
Value: 120,
|
||||
},
|
||||
{
|
||||
Value: 132,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Union Ads",
|
||||
Type: "bar",
|
||||
Data: []EChartsSeriesData{
|
||||
{
|
||||
Value: 220,
|
||||
},
|
||||
{
|
||||
Value: 182,
|
||||
},
|
||||
},
|
||||
},
|
||||
}, esList)
|
||||
}
|
||||
|
|
@ -85,6 +85,10 @@ func lineChartRender(opt lineChartOption, result *basicRenderResult) ([]markPoin
|
|||
if !series.Label.Show {
|
||||
continue
|
||||
}
|
||||
distance := series.Label.Distance
|
||||
if distance == 0 {
|
||||
distance = 5
|
||||
}
|
||||
text := NewValueLabelFormater(seriesNames, series.Label.Formatter)(i, item.Value, -1)
|
||||
labelStyle := chart.Style{
|
||||
FontColor: theme.GetTextColor(),
|
||||
|
|
@ -96,7 +100,7 @@ func lineChartRender(opt lineChartOption, result *basicRenderResult) ([]markPoin
|
|||
}
|
||||
labelStyle.GetTextOptions().WriteToRenderer(r)
|
||||
textBox := r.MeasureText(text)
|
||||
d.text(text, x-textBox.Width()>>1, y-5)
|
||||
d.text(text, x-textBox.Width()>>1, y-distance)
|
||||
}
|
||||
|
||||
dotFillColor := drawing.ColorWhite
|
||||
|
|
|
|||
|
|
@ -68,6 +68,8 @@ type SeriesLabel struct {
|
|||
Color drawing.Color
|
||||
// Show flag for label
|
||||
Show bool
|
||||
// Distance to the host graphic element.
|
||||
Distance int
|
||||
}
|
||||
|
||||
const (
|
||||
|
|
|
|||
38
util.go
38
util.go
|
|
@ -23,11 +23,13 @@
|
|||
package charts
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/wcharczuk/go-chart/v2"
|
||||
"github.com/wcharczuk/go-chart/v2/drawing"
|
||||
)
|
||||
|
||||
func TrueFlag() *bool {
|
||||
|
|
@ -130,3 +132,39 @@ func commafWithDigits(value float64) string {
|
|||
}
|
||||
return humanize.CommafWithDigits(value, decimals)
|
||||
}
|
||||
|
||||
func parseColor(color string) drawing.Color {
|
||||
c := drawing.Color{}
|
||||
if color == "" {
|
||||
return c
|
||||
}
|
||||
if strings.HasPrefix(color, "#") {
|
||||
return drawing.ColorFromHex(color[1:])
|
||||
}
|
||||
reg := regexp.MustCompile(`\((\S+)\)`)
|
||||
result := reg.FindAllStringSubmatch(color, 1)
|
||||
if len(result) == 0 || len(result[0]) != 2 {
|
||||
return c
|
||||
}
|
||||
arr := strings.Split(result[0][1], ",")
|
||||
if len(arr) < 3 {
|
||||
return c
|
||||
}
|
||||
// 设置默认为255
|
||||
c.A = 255
|
||||
for index, v := range arr {
|
||||
value, _ := strconv.Atoi(strings.TrimSpace(v))
|
||||
ui8 := uint8(value)
|
||||
switch index {
|
||||
case 0:
|
||||
c.R = ui8
|
||||
case 1:
|
||||
c.G = ui8
|
||||
case 2:
|
||||
c.B = ui8
|
||||
default:
|
||||
c.A = ui8
|
||||
}
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
|
|
|||
40
util_test.go
40
util_test.go
|
|
@ -27,6 +27,7 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/wcharczuk/go-chart/v2"
|
||||
"github.com/wcharczuk/go-chart/v2/drawing"
|
||||
)
|
||||
|
||||
func TestGetDefaultInt(t *testing.T) {
|
||||
|
|
@ -139,3 +140,42 @@ func TestConvertPercent(t *testing.T) {
|
|||
assert.Equal(-1.0, convertPercent("a%"))
|
||||
assert.Equal(0.1, convertPercent("10%"))
|
||||
}
|
||||
|
||||
func TestParseColor(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
c := parseColor("")
|
||||
assert.True(c.IsZero())
|
||||
|
||||
c = parseColor("#333")
|
||||
assert.Equal(drawing.Color{
|
||||
R: 51,
|
||||
G: 51,
|
||||
B: 51,
|
||||
A: 255,
|
||||
}, c)
|
||||
|
||||
c = parseColor("#313233")
|
||||
assert.Equal(drawing.Color{
|
||||
R: 49,
|
||||
G: 50,
|
||||
B: 51,
|
||||
A: 255,
|
||||
}, c)
|
||||
|
||||
c = parseColor("rgb(31,32,33)")
|
||||
assert.Equal(drawing.Color{
|
||||
R: 31,
|
||||
G: 32,
|
||||
B: 33,
|
||||
A: 255,
|
||||
}, c)
|
||||
|
||||
c = parseColor("rgba(50,51,52,250)")
|
||||
assert.Equal(drawing.Color{
|
||||
R: 50,
|
||||
G: 51,
|
||||
B: 52,
|
||||
A: 250,
|
||||
}, c)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue