diff --git a/alias.go b/alias.go
index 3a09919..3bacc67 100644
--- a/alias.go
+++ b/alias.go
@@ -31,3 +31,34 @@ type Box = chart.Box
type Renderer = chart.Renderer
type Style = chart.Style
type Color = drawing.Color
+
+type Point struct {
+ X int
+ Y int
+}
+
+const (
+ ChartTypeLine = "line"
+ ChartTypeBar = "bar"
+ ChartTypePie = "pie"
+ ChartTypeRadar = "radar"
+ ChartTypeFunnel = "funnel"
+)
+
+const (
+ ChartOutputSVG = "svg"
+ ChartOutputPNG = "png"
+)
+
+const (
+ PositionLeft = "left"
+ PositionRight = "right"
+ PositionCenter = "center"
+ PositionTop = "top"
+ PositionBottom = "bottom"
+)
+
+const (
+ OrientHorizontal = "horizontal"
+ OrientVertical = "vertical"
+)
diff --git a/axis.go b/axis.go
deleted file mode 100644
index 5881f5e..0000000
--- a/axis.go
+++ /dev/null
@@ -1,472 +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 (
- "math"
-
- "github.com/golang/freetype/truetype"
- "github.com/wcharczuk/go-chart/v2"
-)
-
-type AxisOption struct {
- // The boundary gap on both sides of a coordinate axis.
- // Nil or *true means the center part of two axis ticks
- BoundaryGap *bool
- // The flag for show axis, set this to *false will hide axis
- Show *bool
- // The position of axis, it can be 'left', 'top', 'right' or 'bottom'
- Position string
- // Number of segments that the axis is split into. Note that this number serves only as a recommendation.
- SplitNumber int
- ClassName string
- // The line color of axis
- StrokeColor Color
- // The line width
- StrokeWidth float64
- // The length of the axis tick
- TickLength int
- // The flag for show axis tick, set this to *false will hide axis tick
- TickShow *bool
- // The margin value of label
- LabelMargin int
- // The font size of label
- FontSize float64
- // The font of label
- Font *truetype.Font
- // The color of label
- FontColor Color
- // The flag for show axis split line, set this to true will show axis split line
- SplitLineShow bool
- // The color of split line
- SplitLineColor Color
-}
-
-type axis struct {
- painter *Painter
- data *AxisDataList
- option *AxisOption
-}
-type axisMeasurement struct {
- Width int
- Height int
-}
-
-// NewAxis creates a new axis with data and style options
-func NewAxis(p *Painter, data AxisDataList, option AxisOption) *axis {
- return &axis{
- painter: p,
- data: &data,
- option: &option,
- }
-
-}
-
-// GetLabelMargin returns the label margin value
-func (as *AxisOption) GetLabelMargin() int {
- return getDefaultInt(as.LabelMargin, 8)
-}
-
-// GetTickLength returns the tick length value
-func (as *AxisOption) GetTickLength() int {
- return getDefaultInt(as.TickLength, 5)
-}
-
-// Style returns the style of axis
-func (as *AxisOption) Style(f *truetype.Font) chart.Style {
- s := chart.Style{
- ClassName: as.ClassName,
- StrokeColor: as.StrokeColor,
- StrokeWidth: as.StrokeWidth,
- FontSize: as.FontSize,
- FontColor: as.FontColor,
- Font: as.Font,
- }
- if s.FontSize == 0 {
- s.FontSize = chart.DefaultFontSize
- }
- if s.Font == nil {
- s.Font = f
- }
- return s
-}
-
-type AxisData struct {
- // The text value of axis
- Text string
-}
-type AxisDataList []AxisData
-
-// TextList returns the text list of axis data
-func (l AxisDataList) TextList() []string {
- textList := make([]string, len(l))
- for index, item := range l {
- textList[index] = item.Text
- }
- return textList
-}
-
-type axisRenderOption struct {
- textMaxWith int
- textMaxHeight int
- boundaryGap bool
- unitCount int
- modValue int
-}
-
-// NewAxisDataListFromStringList creates a new axis data list from string list
-func NewAxisDataListFromStringList(textList []string) AxisDataList {
- list := make(AxisDataList, len(textList))
- for index, text := range textList {
- list[index] = AxisData{
- Text: text,
- }
- }
- return list
-}
-
-func (a *axis) axisLabel(renderOpt *axisRenderOption) {
- option := a.option
- data := *a.data
- // d := a.d
- if option.FontColor.IsZero() || len(data) == 0 {
- return
- }
- // r := d.Render
-
- // s.GetTextOptions().WriteTextOptionsToRenderer(r)
- p := a.painter
- s := option.Style(p.font)
- p.SetTextStyle(s)
-
- width := p.Width()
- height := p.Height()
- textList := data.TextList()
- count := len(textList)
-
- boundaryGap := renderOpt.boundaryGap
- if !boundaryGap {
- count--
- }
-
- unitCount := renderOpt.unitCount
- modValue := renderOpt.modValue
- labelMargin := option.GetLabelMargin()
-
- // 轴线
- labelHeight := labelMargin + renderOpt.textMaxHeight
- labelWidth := labelMargin + renderOpt.textMaxWith
-
- // 坐标轴文本
- position := option.Position
- switch position {
- case PositionLeft:
- fallthrough
- case PositionRight:
- values := autoDivide(height, count)
- textList := data.TextList()
- // 由下往上
- reverseIntSlice(values)
- for index, text := range textList {
- y := values[index] - 2
- b := p.MeasureText(text)
- if boundaryGap {
- height := y - values[index+1]
- y -= (height - b.Height()) >> 1
- } else {
- y += b.Height() >> 1
- }
- // 左右位置的x不一样
- x := width - renderOpt.textMaxWith
- if position == PositionLeft {
- x = labelWidth - b.Width() - 1
- }
- p.Text(text, x, y)
- }
- default:
- // 定位bottom,重新计算y0的定位
- y0 := height - labelMargin
- if position == PositionTop {
- y0 = labelHeight - labelMargin
- }
- values := autoDivide(width, count)
- for index, text := range data.TextList() {
- if unitCount != 0 && index%unitCount != modValue {
- continue
- }
- x := values[index]
- leftOffset := 0
- b := p.MeasureText(text)
- if boundaryGap {
- width := values[index+1] - x
- leftOffset = (width - b.Width()) >> 1
- } else {
- // 左移文本长度
- leftOffset = -b.Width() >> 1
- }
- p.Text(text, x+leftOffset, y0)
- }
- }
-}
-
-func (a *axis) axisLine(renderOpt *axisRenderOption) {
- // d := a.d
- // r := d.Render
- p := a.painter
- option := a.option
- s := option.Style(p.font)
- p.SetDrawingStyle(s.GetStrokeOptions())
-
- x0 := 0
- y0 := 0
- x1 := 0
- y1 := 0
- width := p.Width()
- height := p.Height()
- labelMargin := option.GetLabelMargin()
-
- // 轴线
- labelHeight := labelMargin + renderOpt.textMaxHeight
- labelWidth := labelMargin + renderOpt.textMaxWith
- tickLength := option.GetTickLength()
- switch option.Position {
- case PositionLeft:
- x0 = tickLength + labelWidth
- x1 = x0
- y0 = 0
- y1 = height
- case PositionRight:
- x0 = width - labelWidth
- x1 = x0
- y0 = 0
- y1 = height
- case PositionTop:
- x0 = 0
- x1 = width
- y0 = labelHeight
- y1 = y0
- // bottom
- default:
- x0 = 0
- x1 = width
- y0 = height - tickLength - labelHeight
- y1 = y0
- }
-
- p.MoveTo(x0, y0)
- p.LineTo(x1, y1)
- p.FillStroke()
-}
-
-func (a *axis) axisTick(renderOpt *axisRenderOption) {
- // d := a.d
- // r := d.Render
-
- p := a.painter
- option := a.option
- s := option.Style(p.font)
- p.SetDrawingStyle(s.GetStrokeOptions())
-
- width := p.Width()
- height := p.Height()
- data := *a.data
- tickCount := len(data)
- if tickCount == 0 {
- return
- }
- if !renderOpt.boundaryGap {
- tickCount--
- }
- labelMargin := option.GetLabelMargin()
- tickShow := true
- if isFalse(option.TickShow) {
- tickShow = false
- }
- unitCount := renderOpt.unitCount
-
- tickLengthValue := option.GetTickLength()
- labelHeight := labelMargin + renderOpt.textMaxHeight
- labelWidth := labelMargin + renderOpt.textMaxWith
- position := option.Position
- switch position {
- case PositionLeft:
- fallthrough
- case PositionRight:
- values := autoDivide(height, tickCount)
- // 左右仅是x0的位置不一样
- x0 := width - labelWidth
- if option.Position == PositionLeft {
- x0 = labelWidth
- }
- if tickShow {
- for _, v := range values {
- x := x0
- y := v
- p.MoveTo(x, y)
- p.LineTo(x+tickLengthValue, y)
- p.Stroke()
- }
- }
- // 辅助线
- if option.SplitLineShow && !option.SplitLineColor.IsZero() {
- p.SetStrokeColor(option.SplitLineColor)
- splitLineWidth := width - labelWidth - tickLengthValue
- x0 = labelWidth + tickLengthValue
- if position == PositionRight {
- x0 = 0
- splitLineWidth = width - labelWidth - 1
- }
- for _, v := range values[0 : len(values)-1] {
- x := x0
- y := v
- p.MoveTo(x, y)
- p.LineTo(x+splitLineWidth, y)
- p.Stroke()
- }
- }
- default:
- values := autoDivide(width, tickCount)
- // 上下仅是y0的位置不一样
- y0 := height - labelHeight
- if position == PositionTop {
- y0 = labelHeight
- }
- if tickShow {
- for index, v := range values {
- if index%unitCount != 0 {
- continue
- }
- x := v
- y := y0
- p.MoveTo(x, y-tickLengthValue)
- p.LineTo(x, y)
- p.Stroke()
- }
- }
- // 辅助线
- if option.SplitLineShow && !option.SplitLineColor.IsZero() {
- p.SetStrokeColor(option.SplitLineColor)
- y0 = 0
- splitLineHeight := height - labelHeight - tickLengthValue
- if position == PositionTop {
- y0 = labelHeight
- splitLineHeight = height - labelHeight
- }
-
- for index, v := range values {
- if index%unitCount != 0 {
- continue
- }
- x := v
- y := y0
-
- p.MoveTo(x, y)
- p.LineTo(x, y0+splitLineHeight)
- p.Stroke()
- }
- }
- }
-}
-
-func (a *axis) measureTextMaxWidthHeight() (int, int) {
- // d := a.d
- // r := d.Render
- p := a.painter
- s := a.option.Style(p.font)
- data := a.data
- p.SetDrawingStyle(s.GetStrokeOptions())
- p.SetTextStyle(s.GetTextOptions())
- return measureTextMaxWidthHeight(data.TextList(), p)
-}
-
-// measure returns the measurement of axis.
-// Width will be textMaxWidth + labelMargin + tickLength for position left or right.
-// Height will be textMaxHeight + labelMargin + tickLength for position top or bottom.
-func (a *axis) measure() axisMeasurement {
- option := a.option
- value := option.GetLabelMargin() + option.GetTickLength()
- textMaxWidth, textMaxHeight := a.measureTextMaxWidthHeight()
- info := axisMeasurement{}
- if option.Position == PositionLeft ||
- option.Position == PositionRight {
- info.Width = textMaxWidth + value
- } else {
- info.Height = textMaxHeight + value
- }
- return info
-}
-
-// Render renders the axis for chart
-func (a *axis) Render() {
- option := a.option
- if isFalse(option.Show) {
- return
- }
- textMaxWidth, textMaxHeight := a.measureTextMaxWidthHeight()
- opt := &axisRenderOption{
- textMaxWith: textMaxWidth,
- textMaxHeight: textMaxHeight,
- boundaryGap: true,
- }
- if isFalse(option.BoundaryGap) {
- opt.boundaryGap = false
- }
-
- unitCount := chart.MaxInt(option.SplitNumber, 1)
- width := a.painter.Width()
- textList := a.data.TextList()
- count := len(textList)
-
- position := option.Position
- switch position {
- case PositionLeft:
- fallthrough
- case PositionRight:
- default:
- maxCount := width / (opt.textMaxWith + 10)
- // 可以显示所有
- if maxCount >= count {
- unitCount = 1
- } else if maxCount < count/unitCount {
- unitCount = int(math.Ceil(float64(count) / float64(maxCount)))
- }
- }
-
- boundaryGap := opt.boundaryGap
- modValue := 0
- if boundaryGap && unitCount > 1 {
- // 如果是居中,unit count需要设置为奇数
- if unitCount%2 == 0 {
- unitCount++
- }
- modValue = unitCount / 2
- }
- opt.modValue = modValue
- opt.unitCount = unitCount
-
- // 坐标轴线
- a.axisLine(opt)
- a.axisTick(opt)
- // 坐标文本
- a.axisLabel(opt)
-}
diff --git a/axis_test.go b/axis_test.go
deleted file mode 100644
index fe253e9..0000000
--- a/axis_test.go
+++ /dev/null
@@ -1,259 +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 (
- "testing"
-
- "github.com/golang/freetype/truetype"
- "github.com/stretchr/testify/assert"
- "github.com/wcharczuk/go-chart/v2"
- "github.com/wcharczuk/go-chart/v2/drawing"
-)
-
-func TestAxisOption(t *testing.T) {
- assert := assert.New(t)
-
- as := AxisOption{}
-
- assert.Equal(8, as.GetLabelMargin())
- as.LabelMargin = 10
- assert.Equal(10, as.GetLabelMargin())
-
- assert.Equal(5, as.GetTickLength())
- as.TickLength = 6
- assert.Equal(6, as.GetTickLength())
-
- f := &truetype.Font{}
- style := as.Style(f)
- assert.Equal(float64(10), style.FontSize)
- assert.Equal(f, style.Font)
-}
-
-func TestAxisDataList(t *testing.T) {
- assert := assert.New(t)
-
- textList := []string{
- "a",
- "b",
- }
- data := NewAxisDataListFromStringList(textList)
- assert.Equal(textList, data.TextList())
-}
-
-func TestAxis(t *testing.T) {
- assert := assert.New(t)
-
- axisData := NewAxisDataListFromStringList([]string{
- "Mon",
- "Tue",
- "Wed",
- "Thu",
- "Fri",
- "Sat",
- "Sun",
- })
- getDefaultOption := func() AxisOption {
- return AxisOption{
- StrokeColor: drawing.ColorBlack,
- StrokeWidth: 1,
- FontColor: drawing.ColorBlack,
- Show: TrueFlag(),
- TickShow: TrueFlag(),
- SplitLineShow: true,
- SplitLineColor: drawing.ColorBlack.WithAlpha(60),
- }
- }
- tests := []struct {
- newOption func() AxisOption
- newData func() AxisDataList
- result string
- }{
- // 文本按起始位置展示
- // axis位于bottom
- {
- newOption: func() AxisOption {
- opt := getDefaultOption()
- opt.BoundaryGap = FalseFlag()
- return opt
- },
- result: "",
- },
- // 文本居中展示
- // axis位于bottom
- {
- newOption: func() AxisOption {
- opt := getDefaultOption()
- return opt
- },
- result: "",
- },
- // 文本按起始位置展示
- // axis位于top
- {
- newOption: func() AxisOption {
- opt := getDefaultOption()
- opt.Position = PositionTop
- opt.BoundaryGap = FalseFlag()
- return opt
- },
- result: "",
- },
- // 文本居中展示
- // axis位于top
- {
- newOption: func() AxisOption {
- opt := getDefaultOption()
- opt.Position = PositionTop
- return opt
- },
- result: "",
- },
- // 文本按起始位置展示
- // axis位于left
- {
- newOption: func() AxisOption {
- opt := getDefaultOption()
- opt.Position = PositionLeft
- opt.BoundaryGap = FalseFlag()
- return opt
- },
- result: "",
- },
- // 文本居中展示
- // axis位于left
- {
- newOption: func() AxisOption {
- opt := getDefaultOption()
- opt.Position = PositionLeft
- return opt
- },
- result: "",
- },
- // 文本按起始位置展示
- // axis位于right
- {
- newOption: func() AxisOption {
- opt := getDefaultOption()
- opt.Position = PositionRight
- opt.BoundaryGap = FalseFlag()
- return opt
- },
- result: "",
- },
- // 文本居中展示
- // axis位于right
- {
- newOption: func() AxisOption {
- opt := getDefaultOption()
- opt.Position = PositionRight
- return opt
- },
- result: "",
- },
- // text较多,仅展示部分
- {
- newOption: func() AxisOption {
- opt := getDefaultOption()
- opt.Position = PositionBottom
- return opt
- },
- newData: func() AxisDataList {
- return NewAxisDataListFromStringList([]string{
- "01-01",
- "01-02",
- "01-03",
- "01-04",
- "01-05",
- "01-06",
- "01-07",
- "01-08",
- "01-09",
- "01-10",
- "01-11",
- "01-12",
- "01-13",
- "01-14",
- "01-15",
- "01-16",
- "01-17",
- "01-18",
- "01-19",
- "01-20",
- "01-21",
- })
- },
- result: "",
- },
- }
- for _, tt := range tests {
- p, err := NewPainter(PainterOptions{
- Width: 400,
- Height: 300,
- }, PainterPaddingOption(chart.Box{
- Left: 5,
- Top: 5,
- Right: 5,
- Bottom: 5,
- }))
- assert.Nil(err)
- style := tt.newOption()
- data := axisData
- if tt.newData != nil {
- data = tt.newData()
- }
- NewAxis(p, data, style).Render()
-
- result, err := p.Bytes()
- assert.Nil(err)
- assert.Equal(tt.result, string(result))
- }
-}
-
-func TestMeasureAxis(t *testing.T) {
- assert := assert.New(t)
-
- p, err := NewPainter(PainterOptions{
- Width: 400,
- Height: 300,
- })
- assert.Nil(err)
- data := NewAxisDataListFromStringList([]string{
- "Mon",
- "Sun",
- })
- f, _ := chart.GetDefaultFont()
- width := NewAxis(p, data, AxisOption{
- FontSize: 12,
- Font: f,
- Position: PositionLeft,
- }).measure().Width
- assert.Equal(44, width)
-
- height := NewAxis(p, data, AxisOption{
- FontSize: 12,
- Font: f,
- Position: PositionTop,
- }).measure().Height
- assert.Equal(28, height)
-}
diff --git a/bar.go b/bar.go
deleted file mode 100644
index 1090f6b..0000000
--- a/bar.go
+++ /dev/null
@@ -1,58 +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/wcharczuk/go-chart/v2"
- "github.com/wcharczuk/go-chart/v2/drawing"
-)
-
-type BarStyle struct {
- ClassName string
- StrokeDashArray []float64
- FillColor drawing.Color
-}
-
-func (bs *BarStyle) Style() chart.Style {
- return chart.Style{
- ClassName: bs.ClassName,
- StrokeDashArray: bs.StrokeDashArray,
- StrokeColor: bs.FillColor,
- StrokeWidth: 1,
- FillColor: bs.FillColor,
- }
-}
-
-// Bar renders bar for chart
-func (d *Draw) Bar(b chart.Box, style BarStyle) {
- s := style.Style()
-
- r := d.Render
- s.GetFillAndStrokeOptions().WriteToRenderer(r)
- d.moveTo(b.Left, b.Top)
- d.lineTo(b.Right, b.Top)
- d.lineTo(b.Right, b.Bottom)
- d.lineTo(b.Left, b.Bottom)
- d.lineTo(b.Left, b.Top)
- d.Render.FillStroke()
-}
diff --git a/bar_chart.go b/bar_chart.go
deleted file mode 100644
index 32373b1..0000000
--- a/bar_chart.go
+++ /dev/null
@@ -1,163 +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"
- "github.com/wcharczuk/go-chart/v2"
-)
-
-type barChartOption struct {
- // The series list fo bar chart
- SeriesList SeriesList
- // The theme
- Theme string
- // The font
- Font *truetype.Font
-}
-
-func barChartRender(opt barChartOption, result *basicRenderResult) ([]markPointRenderOption, error) {
- d, err := NewDraw(DrawOption{
- Parent: result.d,
- }, PaddingOption(chart.Box{
- Top: result.titleBox.Height(),
- // TODO 后续考虑是否需要根据左侧是否展示y轴再生成对应的left
- Left: YAxisWidth,
- }))
- if err != nil {
- return nil, err
- }
- xRange := result.xRange
- x0, x1 := xRange.GetRange(0)
- width := int(x1 - x0)
- // 每一块之间的margin
- margin := 10
- // 每一个bar之间的margin
- barMargin := 5
- if width < 20 {
- margin = 2
- barMargin = 2
- } else if width < 50 {
- margin = 5
- barMargin = 3
- }
-
- seriesCount := len(opt.SeriesList)
- // 总的宽度-两个margin-(总数-1)的barMargin
- barWidth := (width - 2*margin - barMargin*(seriesCount-1)) / len(opt.SeriesList)
-
- barMaxHeight := result.getYRange(0).Size
- theme := NewTheme(opt.Theme)
-
- seriesNames := opt.SeriesList.Names()
-
- r := d.Render
-
- markPointRenderOptions := make([]markPointRenderOption, 0)
-
- for i, s := range opt.SeriesList {
- // 由于series是for range,为同一个数据,因此需要clone
- // 后续需要使用,如mark point
- series := s
- yRange := result.getYRange(series.YAxisIndex)
- points := make([]Point, len(series.Data))
- index := series.index
- if index == 0 {
- index = i
- }
- seriesColor := theme.GetSeriesColor(index)
- // mark line
- markLineRender(markLineRenderOption{
- Draw: d,
- FillColor: seriesColor,
- FontColor: theme.GetTextColor(),
- StrokeColor: seriesColor,
- Font: opt.Font,
- Series: &series,
- Range: yRange,
- })
- divideValues := xRange.AutoDivide()
- for j, item := range series.Data {
- if j >= xRange.divideCount {
- continue
- }
- x := divideValues[j]
- x += margin
- if i != 0 {
- x += i * (barWidth + barMargin)
- }
-
- h := int(yRange.getHeight(item.Value))
- fillColor := seriesColor
- if !item.Style.FillColor.IsZero() {
- fillColor = item.Style.FillColor
- }
- top := barMaxHeight - h
- d.Bar(chart.Box{
- Top: top,
- Left: x,
- Right: x + barWidth,
- Bottom: barMaxHeight - 1,
- }, BarStyle{
- FillColor: fillColor,
- })
- // 用于生成marker point
- points[j] = Point{
- // 居中的位置
- X: x + barWidth>>1,
- Y: top,
- }
- // 如果label不需要展示,则返回
- 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(),
- FontSize: labelFontSize,
- Font: opt.Font,
- }
- if !series.Label.Color.IsZero() {
- labelStyle.FontColor = series.Label.Color
- }
- labelStyle.GetTextOptions().WriteToRenderer(r)
- textBox := r.MeasureText(text)
- d.text(text, x+(barWidth-textBox.Width())>>1, barMaxHeight-h-distance)
- }
-
- // 生成mark point的参数
- markPointRenderOptions = append(markPointRenderOptions, markPointRenderOption{
- Draw: d,
- FillColor: seriesColor,
- Font: opt.Font,
- Points: points,
- Series: &series,
- })
- }
-
- return markPointRenderOptions, nil
-}
diff --git a/bar_chart_test.go b/bar_chart_test.go
deleted file mode 100644
index f10a1bc..0000000
--- a/bar_chart_test.go
+++ /dev/null
@@ -1,131 +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 (
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/wcharczuk/go-chart/v2"
- "github.com/wcharczuk/go-chart/v2/drawing"
-)
-
-func TestBarChartRender(t *testing.T) {
- assert := assert.New(t)
-
- width := 400
- height := 300
- d, err := NewDraw(DrawOption{
- Width: width,
- Height: height,
- })
- assert.Nil(err)
-
- result := basicRenderResult{
- xRange: &Range{
- Min: 0,
- Max: 4,
- divideCount: 4,
- Size: width,
- Boundary: true,
- },
- yRangeList: []*Range{
- {
- divideCount: 6,
- Max: 100,
- Min: 0,
- Size: height,
- },
- },
- d: d,
- }
- f, _ := chart.GetDefaultFont()
-
- markPointOptions, err := barChartRender(barChartOption{
- Font: f,
- SeriesList: SeriesList{
- {
- Label: SeriesLabel{
- Show: true,
- Color: drawing.ColorBlue,
- },
- MarkLine: NewMarkLine(
- SeriesMarkDataTypeMin,
- ),
- Data: []SeriesData{
- {
- Value: 20,
- },
- {
- Value: 60,
- Style: chart.Style{
- FillColor: drawing.ColorRed,
- },
- },
- {
- Value: 90,
- },
- },
- },
- NewSeriesFromValues([]float64{
- 80,
- 30,
- 70,
- }),
- },
- }, &result)
- assert.Nil(err)
- assert.Equal(2, len(markPointOptions))
- assert.Equal([]Point{
- {
- X: 28,
- Y: 240,
- },
- {
- X: 128,
- Y: 120,
- },
- {
- X: 228,
- Y: 30,
- },
- }, markPointOptions[0].Points)
- assert.Equal([]Point{
- {
- X: 70,
- Y: 60,
- },
- {
- X: 170,
- Y: 210,
- },
- {
- X: 270,
- Y: 90,
- },
- }, markPointOptions[1].Points)
-
- data, err := d.Bytes()
- assert.Nil(err)
- assert.Equal("", string(data))
-}
diff --git a/bar_test.go b/bar_test.go
deleted file mode 100644
index 01b6d3c..0000000
--- a/bar_test.go
+++ /dev/null
@@ -1,78 +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 (
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/wcharczuk/go-chart/v2"
- "github.com/wcharczuk/go-chart/v2/drawing"
-)
-
-func TestBarStyle(t *testing.T) {
- assert := assert.New(t)
-
- bs := BarStyle{
- ClassName: "test",
- StrokeDashArray: []float64{
- 1.0,
- },
- FillColor: drawing.ColorBlack,
- }
-
- assert.Equal(chart.Style{
- ClassName: "test",
- StrokeDashArray: []float64{
- 1.0,
- },
- StrokeWidth: 1,
- FillColor: drawing.ColorBlack,
- StrokeColor: drawing.ColorBlack,
- }, bs.Style())
-}
-
-func TestDrawBar(t *testing.T) {
- assert := assert.New(t)
- d, err := NewDraw(DrawOption{
- Width: 400,
- Height: 300,
- }, PaddingOption(chart.Box{
- Left: 10,
- Top: 20,
- Right: 30,
- Bottom: 40,
- }))
- assert.Nil(err)
- d.Bar(chart.Box{
- Left: 0,
- Top: 0,
- Right: 20,
- Bottom: 200,
- }, BarStyle{
- FillColor: drawing.ColorBlack,
- })
- data, err := d.Bytes()
- assert.Nil(err)
- assert.Equal("", string(data))
-}
diff --git a/chart.go b/chart.go
deleted file mode 100644
index 21f2071..0000000
--- a/chart.go
+++ /dev/null
@@ -1,502 +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 (
- "errors"
- "math"
- "sort"
- "strings"
-
- "github.com/golang/freetype/truetype"
- "github.com/wcharczuk/go-chart/v2"
- "github.com/wcharczuk/go-chart/v2/drawing"
-)
-
-const (
- ChartTypeLine = "line"
- ChartTypeBar = "bar"
- ChartTypePie = "pie"
- ChartTypeRadar = "radar"
- ChartTypeFunnel = "funnel"
-)
-
-const (
- ChartOutputSVG = "svg"
- ChartOutputPNG = "png"
-)
-
-type Point struct {
- X int
- Y int
-}
-
-const labelFontSize = 10
-const defaultDotWidth = 2.0
-const defaultStrokeWidth = 2.0
-
-var defaultChartWidth = 600
-var defaultChartHeight = 400
-
-type ChartOption struct {
- // The output type of chart, "svg" or "png", default value is "svg"
- Type string
- // The font family, which should be installed first
- FontFamily string
- // The font of chart, the default font is "roboto"
- Font *truetype.Font
- // The theme of chart, "light" and "dark".
- // The default theme is "light"
- Theme string
- // The title option
- Title TitleOption
- // The legend option
- Legend LegendOption
- // The x axis option
- XAxis XAxisOption
- // The y axis option list
- YAxisList []YAxisOption
- // The width of chart, default width is 600
- Width int
- // The height of chart, default height is 400
- Height int
- Parent *Draw
- // The padding for chart, default padding is [20, 10, 10, 10]
- Padding chart.Box
- // The canvas box for chart
- Box chart.Box
- // The series list
- SeriesList SeriesList
- // The radar indicator list
- RadarIndicators []RadarIndicator
- // The background color of chart
- BackgroundColor drawing.Color
- // The child charts
- Children []ChartOption
-}
-
-// FillDefault fills the default value for chart option
-func (o *ChartOption) FillDefault(theme string) {
- t := NewTheme(theme)
- // 如果为空,初始化
- yAxisCount := 1
- for _, series := range o.SeriesList {
- if series.YAxisIndex >= yAxisCount {
- yAxisCount++
- }
- }
- yAxisList := make([]YAxisOption, yAxisCount)
- copy(yAxisList, o.YAxisList)
- o.YAxisList = yAxisList
-
- if o.Font == nil {
- o.Font, _ = chart.GetDefaultFont()
- }
- if o.BackgroundColor.IsZero() {
- o.BackgroundColor = t.GetBackgroundColor()
- }
- if o.Padding.IsZero() {
- o.Padding = chart.Box{
- Top: 10,
- Right: 10,
- Bottom: 10,
- Left: 10,
- }
- }
-
- // 标题的默认值
- if o.Title.Style.FontColor.IsZero() {
- o.Title.Style.FontColor = t.GetTextColor()
- }
- if o.Title.Style.FontSize == 0 {
- o.Title.Style.FontSize = 14
- }
- if o.Title.Style.Font == nil {
- o.Title.Style.Font = o.Font
- }
- if o.Title.Style.Padding.IsZero() {
- o.Title.Style.Padding = chart.Box{
- Bottom: 10,
- }
- }
- // 副标题
- if o.Title.SubtextStyle.FontColor.IsZero() {
- o.Title.SubtextStyle.FontColor = o.Title.Style.FontColor.WithAlpha(180)
- }
- if o.Title.SubtextStyle.FontSize == 0 {
- o.Title.SubtextStyle.FontSize = labelFontSize
- }
- if o.Title.SubtextStyle.Font == nil {
- o.Title.SubtextStyle.Font = o.Font
- }
-
- o.Legend.theme = theme
- if o.Legend.Style.FontSize == 0 {
- o.Legend.Style.FontSize = labelFontSize
- }
- if o.Legend.Left == "" {
- o.Legend.Left = PositionCenter
- }
- // legend与series name的关联
- if len(o.Legend.Data) == 0 {
- o.Legend.Data = o.SeriesList.Names()
- } else {
- seriesCount := len(o.SeriesList)
- for index, name := range o.Legend.Data {
- if index < seriesCount &&
- len(o.SeriesList[index].Name) == 0 {
- o.SeriesList[index].Name = name
- }
- }
- nameIndexDict := map[string]int{}
- for index, name := range o.Legend.Data {
- nameIndexDict[name] = index
- }
- // 保证series的顺序与legend一致
- sort.Slice(o.SeriesList, func(i, j int) bool {
- return nameIndexDict[o.SeriesList[i].Name] < nameIndexDict[o.SeriesList[j].Name]
- })
- }
- // 如果无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
- }
- if o.Legend.Style.FontColor.IsZero() {
- o.Legend.Style.FontColor = t.GetTextColor()
- }
- if o.XAxis.Theme == "" {
- o.XAxis.Theme = theme
- }
- o.XAxis.Font = o.Font
-}
-
-func (o *ChartOption) getWidth() int {
- if o.Width != 0 {
- return o.Width
- }
- if o.Parent != nil {
- return o.Parent.Box.Width()
- }
- return defaultChartWidth
-}
-
-func SetDefaultWidth(width int) {
- if width > 0 {
- defaultChartWidth = width
- }
-}
-func SetDefaultHeight(height int) {
- if height > 0 {
- defaultChartHeight = height
- }
-}
-
-func (o *ChartOption) getHeight() int {
-
- if o.Height != 0 {
- return o.Height
- }
- if o.Parent != nil {
- return o.Parent.Box.Height()
- }
- return defaultChartHeight
-}
-
-func (o *ChartOption) newYRange(axisIndex int) Range {
- min := math.MaxFloat64
- max := -math.MaxFloat64
- if axisIndex >= len(o.YAxisList) {
- axisIndex = 0
- }
- yAxis := o.YAxisList[axisIndex]
-
- for _, series := range o.SeriesList {
- if series.YAxisIndex != axisIndex {
- continue
- }
- for _, item := range series.Data {
- if item.Value > max {
- max = item.Value
- }
- if item.Value < min {
- min = item.Value
- }
- }
- }
- min = min * 0.9
- max = max * 1.1
- if yAxis.Min != nil {
- min = *yAxis.Min
- }
- if yAxis.Max != nil {
- max = *yAxis.Max
- }
- divideCount := 6
- // y轴分设置默认划分为6块
- r := NewRange(min, max, divideCount)
-
- // 由于NewRange会重新计算min max
- if yAxis.Min != nil {
- r.Min = min
- }
- if yAxis.Max != nil {
- r.Max = max
- }
-
- return r
-}
-
-type basicRenderResult struct {
- xRange *Range
- yRangeList []*Range
- d *Draw
- titleBox chart.Box
-}
-
-func (r *basicRenderResult) getYRange(index int) *Range {
- if index >= len(r.yRangeList) {
- index = 0
- }
- return r.yRangeList[index]
-}
-
-// Render renders the chart by option
-func Render(opt ChartOption, optFuncs ...OptionFunc) (*Draw, error) {
- for _, optFunc := range optFuncs {
- optFunc(&opt)
- }
- if len(opt.SeriesList) == 0 {
- return nil, errors.New("series can not be nil")
- }
- if len(opt.FontFamily) != 0 {
- f, err := GetFont(opt.FontFamily)
- if err != nil {
- return nil, err
- }
- opt.Font = f
- }
- opt.FillDefault(opt.Theme)
-
- lineSeries := make([]Series, 0)
- barSeries := make([]Series, 0)
- isPieChart := false
- isRadarChart := false
- isFunnelChart := false
- for index := range opt.SeriesList {
- opt.SeriesList[index].index = index
- item := opt.SeriesList[index]
- switch item.Type {
- case ChartTypePie:
- isPieChart = true
- case ChartTypeRadar:
- isRadarChart = true
- case ChartTypeFunnel:
- isFunnelChart = true
- case ChartTypeBar:
- barSeries = append(barSeries, item)
- default:
- lineSeries = append(lineSeries, item)
- }
- }
- // 如果指定了pie,则以pie的形式处理,pie不支持多类型图表
- // pie不需要axis
- // radar 同样处理
- if isPieChart ||
- isRadarChart ||
- isFunnelChart {
- opt.XAxis.Hidden = true
- for index := range opt.YAxisList {
- opt.YAxisList[index].Hidden = true
- }
- }
- result, err := chartBasicRender(&opt)
- if err != nil {
- return nil, err
- }
- markPointRenderOptions := make([]markPointRenderOption, 0)
- fns := []func() error{
- // pie render
- func() error {
- if !isPieChart {
- return nil
- }
- return pieChartRender(pieChartOption{
- SeriesList: opt.SeriesList,
- Theme: opt.Theme,
- Font: opt.Font,
- }, result)
- },
- // radar render
- func() error {
- if !isRadarChart {
- return nil
- }
- return radarChartRender(radarChartOption{
- SeriesList: opt.SeriesList,
- Theme: opt.Theme,
- Font: opt.Font,
- Indicators: opt.RadarIndicators,
- }, result)
- },
- // funnel render
- func() error {
- if !isFunnelChart {
- return nil
- }
- return funnelChartRender(funnelChartOption{
- SeriesList: opt.SeriesList,
- Theme: opt.Theme,
- Font: opt.Font,
- }, result)
- },
- // bar render
- func() error {
- // 如果无bar类型的series
- if len(barSeries) == 0 {
- return nil
- }
- options, err := barChartRender(barChartOption{
- SeriesList: barSeries,
- Theme: opt.Theme,
- Font: opt.Font,
- }, result)
- if err != nil {
- return err
- }
- markPointRenderOptions = append(markPointRenderOptions, options...)
- return nil
- },
- // line render
- func() error {
- // 如果无line类型的series
- if len(lineSeries) == 0 {
- return nil
- }
- options, err := lineChartRender(lineChartOption{
- Theme: opt.Theme,
- SeriesList: lineSeries,
- Font: opt.Font,
- }, result)
- if err != nil {
- return err
- }
- markPointRenderOptions = append(markPointRenderOptions, options...)
- return nil
- },
- // legend需要在顶层,因此此处render
- func() error {
- _, err := NewLegend(result.d, opt.Legend).Render()
- return err
- },
- // mark point最后render
- func() error {
- // mark point render不会出错
- for _, opt := range markPointRenderOptions {
- markPointRender(opt)
- }
- return nil
- },
- }
-
- for _, fn := range fns {
- err = fn()
- if err != nil {
- return nil, err
- }
- }
- for _, child := range opt.Children {
- child.Parent = result.d
- if len(child.Theme) == 0 {
- child.Theme = opt.Theme
- }
- _, err = Render(child)
- if err != nil {
- return nil, err
- }
- }
- return result.d, nil
-}
-
-func chartBasicRender(opt *ChartOption) (*basicRenderResult, error) {
- d, err := NewDraw(
- DrawOption{
- Type: opt.Type,
- Parent: opt.Parent,
- Width: opt.getWidth(),
- Height: opt.getHeight(),
- },
- BoxOption(opt.Box),
- PaddingOption(opt.Padding),
- )
- if err != nil {
- return nil, err
- }
-
- if len(opt.YAxisList) > 2 {
- return nil, errors.New("y axis should not be gt 2")
- }
- if opt.Parent == nil {
- d.setBackground(opt.getWidth(), opt.getHeight(), opt.BackgroundColor)
- }
-
- // 标题
- titleBox, err := drawTitle(d, &opt.Title)
- if err != nil {
- return nil, err
- }
-
- xAxisHeight := 0
- var xRange *Range
-
- if !opt.XAxis.Hidden {
- // xAxis
- xAxisHeight, xRange, err = drawXAxis(d, &opt.XAxis, len(opt.YAxisList))
- if err != nil {
- return nil, err
- }
- }
-
- yRangeList := make([]*Range, len(opt.YAxisList))
-
- for index, yAxis := range opt.YAxisList {
- var yRange *Range
- if !yAxis.Hidden {
- yRange, err = drawYAxis(d, opt, index, xAxisHeight, chart.Box{
- Top: titleBox.Height(),
- })
- if err != nil {
- return nil, err
- }
- yRangeList[index] = yRange
- }
- }
- return &basicRenderResult{
- xRange: xRange,
- yRangeList: yRangeList,
- d: d,
- titleBox: titleBox,
- }, nil
-}
diff --git a/chart_option.go b/chart_option.go
deleted file mode 100644
index 5e25873..0000000
--- a/chart_option.go
+++ /dev/null
@@ -1,190 +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/wcharczuk/go-chart/v2"
- "github.com/wcharczuk/go-chart/v2/drawing"
-)
-
-// OptionFunc option function
-type OptionFunc func(opt *ChartOption)
-
-// PNGTypeOption set png type of chart's output
-func PNGTypeOption() OptionFunc {
- return TypeOptionFunc(ChartOutputPNG)
-}
-
-// TypeOptionFunc set type of chart's output
-func TypeOptionFunc(t string) OptionFunc {
- return func(opt *ChartOption) {
- opt.Type = t
- }
-}
-
-// FontFamilyOptionFunc set font family of chart
-func FontFamilyOptionFunc(fontFamily string) OptionFunc {
- return func(opt *ChartOption) {
- opt.FontFamily = fontFamily
- }
-}
-
-// ThemeOptionFunc set them of chart
-func ThemeOptionFunc(theme string) OptionFunc {
- return func(opt *ChartOption) {
- opt.Theme = theme
- }
-}
-
-// TitleOptionFunc set title of chart
-func TitleOptionFunc(title TitleOption) OptionFunc {
- return func(opt *ChartOption) {
- opt.Title = title
- }
-}
-
-// LegendOptionFunc set legend of chart
-func LegendOptionFunc(legend LegendOption) OptionFunc {
- return func(opt *ChartOption) {
- opt.Legend = legend
- }
-}
-
-// XAxisOptionFunc set x axis of chart
-func XAxisOptionFunc(xAxisOption XAxisOption) OptionFunc {
- return func(opt *ChartOption) {
- opt.XAxis = xAxisOption
- }
-}
-
-// YAxisOptionFunc set y axis of chart, support two y axis
-func YAxisOptionFunc(yAxisOption ...YAxisOption) OptionFunc {
- return func(opt *ChartOption) {
- opt.YAxisList = yAxisOption
- }
-}
-
-// WidthOptionFunc set width of chart
-func WidthOptionFunc(width int) OptionFunc {
- return func(opt *ChartOption) {
- opt.Width = width
- }
-}
-
-// HeightOptionFunc set height of chart
-func HeightOptionFunc(height int) OptionFunc {
- return func(opt *ChartOption) {
- opt.Height = height
- }
-}
-
-// PaddingOptionFunc set padding of chart
-func PaddingOptionFunc(padding chart.Box) OptionFunc {
- return func(opt *ChartOption) {
- opt.Padding = padding
- }
-}
-
-// BoxOptionFunc set box of chart
-func BoxOptionFunc(box chart.Box) OptionFunc {
- return func(opt *ChartOption) {
- opt.Box = box
- }
-}
-
-// ChildOptionFunc add child chart
-func ChildOptionFunc(child ...ChartOption) OptionFunc {
- return func(opt *ChartOption) {
- if opt.Children == nil {
- opt.Children = make([]ChartOption, 0)
- }
- opt.Children = append(opt.Children, child...)
- }
-}
-
-// RadarIndicatorOptionFunc set radar indicator of chart
-func RadarIndicatorOptionFunc(radarIndicator ...RadarIndicator) OptionFunc {
- return func(opt *ChartOption) {
- opt.RadarIndicators = radarIndicator
- }
-}
-
-// BackgroundColorOptionFunc set background color of chart
-func BackgroundColorOptionFunc(color drawing.Color) OptionFunc {
- return func(opt *ChartOption) {
- opt.BackgroundColor = color
- }
-}
-
-// LineRender line chart render
-func LineRender(values [][]float64, opts ...OptionFunc) (*Draw, error) {
- seriesList := make(SeriesList, len(values))
- for index, value := range values {
- seriesList[index] = NewSeriesFromValues(value, ChartTypeLine)
- }
- return Render(ChartOption{
- SeriesList: seriesList,
- }, opts...)
-}
-
-// BarRender bar chart render
-func BarRender(values [][]float64, opts ...OptionFunc) (*Draw, error) {
- seriesList := make(SeriesList, len(values))
- for index, value := range values {
- seriesList[index] = NewSeriesFromValues(value, ChartTypeBar)
- }
- return Render(ChartOption{
- SeriesList: seriesList,
- }, opts...)
-}
-
-// PieRender pie chart render
-func PieRender(values []float64, opts ...OptionFunc) (*Draw, error) {
- return Render(ChartOption{
- SeriesList: NewPieSeriesList(values),
- }, opts...)
-}
-
-// RadarRender radar chart render
-func RadarRender(values [][]float64, opts ...OptionFunc) (*Draw, error) {
- seriesList := make(SeriesList, len(values))
- for index, value := range values {
- seriesList[index] = NewSeriesFromValues(value, ChartTypeRadar)
- }
- return Render(ChartOption{
- SeriesList: seriesList,
- }, opts...)
-}
-
-// FunnelRender funnel chart render
-func FunnelRender(values []float64, opts ...OptionFunc) (*Draw, error) {
- seriesList := make(SeriesList, len(values))
- for index, value := range values {
- seriesList[index] = NewSeriesFromValues([]float64{
- value,
- }, ChartTypeFunnel)
- }
- return Render(ChartOption{
- SeriesList: seriesList,
- }, opts...)
-}
diff --git a/chart_option_test.go b/chart_option_test.go
deleted file mode 100644
index 41e8d50..0000000
--- a/chart_option_test.go
+++ /dev/null
@@ -1,238 +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 (
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/wcharczuk/go-chart/v2"
- "github.com/wcharczuk/go-chart/v2/drawing"
-)
-
-func TestOptionFunc(t *testing.T) {
- assert := assert.New(t)
-
- fns := []OptionFunc{
- TypeOptionFunc(ChartOutputPNG),
- FontFamilyOptionFunc("fontFamily"),
- ThemeOptionFunc("black"),
- TitleOptionFunc(TitleOption{
- Text: "title",
- }),
- LegendOptionFunc(LegendOption{
- Data: []string{
- "a",
- "b",
- },
- }),
- XAxisOptionFunc(NewXAxisOption([]string{
- "Mon",
- "Tue",
- })),
- YAxisOptionFunc(YAxisOption{
- Min: NewFloatPoint(0),
- Max: NewFloatPoint(100),
- }),
- WidthOptionFunc(400),
- HeightOptionFunc(300),
- PaddingOptionFunc(chart.Box{
- Top: 10,
- }),
- BoxOptionFunc(chart.Box{
- Left: 0,
- Right: 300,
- }),
- ChildOptionFunc(ChartOption{}),
- RadarIndicatorOptionFunc(RadarIndicator{
- Min: 0,
- Max: 10,
- }),
- BackgroundColorOptionFunc(drawing.ColorBlack),
- }
-
- opt := ChartOption{}
- for _, fn := range fns {
- fn(&opt)
- }
-
- assert.Equal("png", opt.Type)
- assert.Equal("fontFamily", opt.FontFamily)
- assert.Equal("black", opt.Theme)
- assert.Equal(TitleOption{
- Text: "title",
- }, opt.Title)
- assert.Equal(LegendOption{
- Data: []string{
- "a",
- "b",
- },
- }, opt.Legend)
- assert.Equal(NewXAxisOption([]string{
- "Mon",
- "Tue",
- }), opt.XAxis)
- assert.Equal([]YAxisOption{
- {
- Min: NewFloatPoint(0),
- Max: NewFloatPoint(100),
- },
- }, opt.YAxisList)
- assert.Equal(400, opt.Width)
- assert.Equal(300, opt.Height)
- assert.Equal(chart.Box{
- Top: 10,
- }, opt.Padding)
- assert.Equal(chart.Box{
- Left: 0,
- Right: 300,
- }, opt.Box)
- assert.Equal(1, len(opt.Children))
- assert.Equal([]RadarIndicator{
- {
- Min: 0,
- Max: 10,
- },
- }, opt.RadarIndicators)
- assert.Equal(drawing.ColorBlack, opt.BackgroundColor)
-}
-
-func TestLineRender(t *testing.T) {
- assert := assert.New(t)
-
- d, err := LineRender([][]float64{
- {
- 1,
- 2,
- 3,
- },
- {
- 1,
- 5,
- 2,
- },
- },
- XAxisOptionFunc(NewXAxisOption([]string{
- "01",
- "02",
- "03",
- })),
- )
- assert.Nil(err)
- data, err := d.Bytes()
- assert.Nil(err)
- assert.Equal("", string(data))
-}
-
-func TestBarRender(t *testing.T) {
- assert := assert.New(t)
-
- d, err := BarRender([][]float64{
- {
- 1,
- 2,
- 3,
- },
- {
- 1,
- 5,
- 2,
- },
- },
- XAxisOptionFunc(NewXAxisOption([]string{
- "01",
- "02",
- "03",
- })),
- )
- assert.Nil(err)
- data, err := d.Bytes()
- assert.Nil(err)
- assert.Equal("", string(data))
-}
-
-func TestPieRender(t *testing.T) {
- assert := assert.New(t)
-
- d, err := PieRender([]float64{
- 1,
- 3,
- 5,
- })
- assert.Nil(err)
- data, err := d.Bytes()
- assert.Nil(err)
- assert.Equal("", string(data))
-}
-
-func TestRadarRender(t *testing.T) {
- assert := assert.New(t)
- d, err := RadarRender([][]float64{
- {
- 1,
- 2,
- 3,
- },
- {
- 1,
- 5,
- 2,
- },
- },
- RadarIndicatorOptionFunc([]RadarIndicator{
- {
- Name: "A",
- Min: 0,
- Max: 10,
- },
- {
- Name: "B",
- Min: 0,
- Max: 10,
- },
- {
- Name: "C",
- Min: 0,
- Max: 10,
- },
- }...),
- )
- assert.Nil(err)
- data, err := d.Bytes()
- assert.Nil(err)
- assert.Equal("", string(data))
-}
-
-func TestFunnelRender(t *testing.T) {
- assert := assert.New(t)
-
- d, err := FunnelRender([]float64{
- 5,
- 3,
- 1,
- })
- assert.Nil(err)
- data, err := d.Bytes()
- assert.Nil(err)
- assert.Equal("", string(data))
-}
diff --git a/chart_test.go b/chart_test.go
deleted file mode 100644
index c73745e..0000000
--- a/chart_test.go
+++ /dev/null
@@ -1,567 +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 (
- "errors"
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/wcharczuk/go-chart/v2"
- "github.com/wcharczuk/go-chart/v2/drawing"
-)
-
-func TestChartSetDefaultWidthHeight(t *testing.T) {
- assert := assert.New(t)
-
- width := defaultChartWidth
- height := defaultChartHeight
- defer SetDefaultWidth(width)
- defer SetDefaultHeight(height)
-
- SetDefaultWidth(60)
- assert.Equal(60, defaultChartWidth)
- SetDefaultHeight(40)
- assert.Equal(40, defaultChartHeight)
-}
-
-func TestChartFillDefault(t *testing.T) {
- assert := assert.New(t)
- // default value
- opt := ChartOption{}
- opt.FillDefault("")
- // padding
- assert.Equal(chart.Box{
- Top: 10,
- Right: 10,
- Bottom: 10,
- Left: 10,
- }, opt.Padding)
- // background color
- assert.Equal(drawing.ColorWhite, opt.BackgroundColor)
- // title font color
- assert.Equal(drawing.Color{
- R: 70,
- G: 70,
- B: 70,
- A: 255,
- }, opt.Title.Style.FontColor)
- // title font size
- assert.Equal(float64(14), opt.Title.Style.FontSize)
- // sub title font color
- assert.Equal(drawing.Color{
- R: 70,
- G: 70,
- B: 70,
- A: 180,
- }, opt.Title.SubtextStyle.FontColor)
- // sub title font size
- assert.Equal(float64(10), opt.Title.SubtextStyle.FontSize)
- // legend font size
- assert.Equal(float64(10), opt.Legend.Style.FontSize)
- // legend position
- assert.Equal("center", opt.Legend.Left)
- assert.Equal(drawing.Color{
- R: 70,
- G: 70,
- B: 70,
- A: 255,
- }, opt.Legend.Style.FontColor)
-
- // y axis
- opt = ChartOption{
- SeriesList: SeriesList{
- {
- YAxisIndex: 1,
- },
- },
- }
- opt.FillDefault("")
- assert.Equal([]YAxisOption{
- {},
- {},
- }, opt.YAxisList)
- opt = ChartOption{}
- opt.FillDefault("")
- assert.Equal([]YAxisOption{
- {},
- }, opt.YAxisList)
-
- // legend get from series's name
-
- opt = ChartOption{
- SeriesList: SeriesList{
- {
- Name: "a",
- },
- {
- Name: "b",
- },
- },
- }
- opt.FillDefault("")
- assert.Equal([]string{
- "a",
- "b",
- }, opt.Legend.Data)
- // series name set by legend
- opt = ChartOption{
- Legend: LegendOption{
- Data: []string{
- "a",
- "b",
- },
- },
- SeriesList: SeriesList{
- {},
- {},
- },
- }
- opt.FillDefault("")
- assert.Equal("a", opt.SeriesList[0].Name)
- assert.Equal("b", opt.SeriesList[1].Name)
-}
-
-func TestChartGetWidthHeight(t *testing.T) {
- assert := assert.New(t)
-
- opt := ChartOption{
- Width: 10,
- }
- assert.Equal(10, opt.getWidth())
- opt.Width = 0
- assert.Equal(600, opt.getWidth())
- opt.Parent = &Draw{
- Box: chart.Box{
- Left: 10,
- Right: 50,
- },
- }
- assert.Equal(40, opt.getWidth())
-
- opt = ChartOption{
- Height: 20,
- }
- assert.Equal(20, opt.getHeight())
- opt.Height = 0
- assert.Equal(400, opt.getHeight())
- opt.Parent = &Draw{
- Box: chart.Box{
- Top: 20,
- Bottom: 80,
- },
- }
- assert.Equal(60, opt.getHeight())
-}
-
-func TestChartRender(t *testing.T) {
- assert := assert.New(t)
-
- d, err := Render(ChartOption{
- Width: 800,
- Height: 600,
- Legend: LegendOption{
- Top: "-90",
- Data: []string{
- "Milk Tea",
- "Matcha Latte",
- "Cheese Cocoa",
- "Walnut Brownie",
- },
- },
- Padding: chart.Box{
- Top: 100,
- },
- XAxis: NewXAxisOption([]string{
- "2012",
- "2013",
- "2014",
- "2015",
- "2016",
- "2017",
- }),
- YAxisList: []YAxisOption{
- {
-
- Min: NewFloatPoint(0),
- Max: NewFloatPoint(90),
- },
- },
- SeriesList: []Series{
- NewSeriesFromValues([]float64{
- 56.5,
- 82.1,
- 88.7,
- 70.1,
- 53.4,
- 85.1,
- }),
- NewSeriesFromValues([]float64{
- 51.1,
- 51.4,
- 55.1,
- 53.3,
- 73.8,
- 68.7,
- }),
- NewSeriesFromValues([]float64{
- 40.1,
- 62.2,
- 69.5,
- 36.4,
- 45.2,
- 32.5,
- }, ChartTypeBar),
- NewSeriesFromValues([]float64{
- 25.2,
- 37.1,
- 41.2,
- 18,
- 33.9,
- 49.1,
- }, ChartTypeBar),
- },
- Children: []ChartOption{
- {
- Legend: LegendOption{
- Show: FalseFlag(),
- Data: []string{
- "Milk Tea",
- "Matcha Latte",
- "Cheese Cocoa",
- "Walnut Brownie",
- },
- },
- Box: chart.Box{
- Top: 20,
- Left: 400,
- Right: 500,
- Bottom: 120,
- },
- SeriesList: NewPieSeriesList([]float64{
- 435.9,
- 354.3,
- 285.9,
- 204.5,
- }, PieSeriesOption{
- Label: SeriesLabel{
- Show: true,
- },
- Radius: "35%",
- }),
- },
- {
- Legend: NewLegendOption([]string{
- "Allocated Budget",
- "Actual Spending",
- }),
- Box: chart.Box{
- Top: 20,
- Left: 0,
- Right: 200,
- Bottom: 120,
- },
- RadarIndicators: []RadarIndicator{
- {
- Name: "Sales",
- Max: 6500,
- },
- {
- Name: "Administration",
- Max: 16000,
- },
- {
- Name: "Information Technology",
- Max: 30000,
- },
- {
- Name: "Customer Support",
- Max: 38000,
- },
- {
- Name: "Development",
- Max: 52000,
- },
- {
- Name: "Marketing",
- Max: 25000,
- },
- },
- SeriesList: SeriesList{
- {
- Type: ChartTypeRadar,
- Data: NewSeriesDataFromValues([]float64{
- 4200,
- 3000,
- 20000,
- 35000,
- 50000,
- 18000,
- }),
- },
- {
- Type: ChartTypeRadar,
- index: 1,
- Data: NewSeriesDataFromValues([]float64{
- 5000,
- 14000,
- 28000,
- 26000,
- 42000,
- 21000,
- }),
- },
- },
- },
- },
- })
- assert.Nil(err)
- data, err := d.Bytes()
- assert.Nil(err)
- assert.Equal("", string(data))
-}
-
-func BenchmarkMultiChartPNGRender(b *testing.B) {
- for i := 0; i < b.N; i++ {
- opt := ChartOption{
- Type: ChartOutputPNG,
- Legend: LegendOption{
- Top: "-90",
- Data: []string{
- "Milk Tea",
- "Matcha Latte",
- "Cheese Cocoa",
- "Walnut Brownie",
- },
- },
- Padding: chart.Box{
- Top: 100,
- Right: 10,
- Bottom: 10,
- Left: 10,
- },
- XAxis: NewXAxisOption([]string{
- "2012",
- "2013",
- "2014",
- "2015",
- "2016",
- "2017",
- }),
- YAxisList: []YAxisOption{
- {
-
- Min: NewFloatPoint(0),
- Max: NewFloatPoint(90),
- },
- },
- SeriesList: []Series{
- NewSeriesFromValues([]float64{
- 56.5,
- 82.1,
- 88.7,
- 70.1,
- 53.4,
- 85.1,
- }),
- NewSeriesFromValues([]float64{
- 51.1,
- 51.4,
- 55.1,
- 53.3,
- 73.8,
- 68.7,
- }),
- NewSeriesFromValues([]float64{
- 40.1,
- 62.2,
- 69.5,
- 36.4,
- 45.2,
- 32.5,
- }, ChartTypeBar),
- NewSeriesFromValues([]float64{
- 25.2,
- 37.1,
- 41.2,
- 18,
- 33.9,
- 49.1,
- }, ChartTypeBar),
- },
- Children: []ChartOption{
- {
- Legend: LegendOption{
- Show: FalseFlag(),
- Data: []string{
- "Milk Tea",
- "Matcha Latte",
- "Cheese Cocoa",
- "Walnut Brownie",
- },
- },
- Box: chart.Box{
- Top: 20,
- Left: 400,
- Right: 500,
- Bottom: 120,
- },
- SeriesList: NewPieSeriesList([]float64{
- 435.9,
- 354.3,
- 285.9,
- 204.5,
- }, PieSeriesOption{
- Label: SeriesLabel{
- Show: true,
- },
- Radius: "35%",
- }),
- },
- },
- }
- d, err := Render(opt)
- if err != nil {
- panic(err)
- }
- buf, err := d.Bytes()
- if err != nil {
- panic(err)
- }
- if len(buf) == 0 {
- panic(errors.New("data is nil"))
- }
- }
-}
-
-func BenchmarkMultiChartSVGRender(b *testing.B) {
- for i := 0; i < b.N; i++ {
- opt := ChartOption{
- Legend: LegendOption{
- Top: "-90",
- Data: []string{
- "Milk Tea",
- "Matcha Latte",
- "Cheese Cocoa",
- "Walnut Brownie",
- },
- },
- Padding: chart.Box{
- Top: 100,
- Right: 10,
- Bottom: 10,
- Left: 10,
- },
- XAxis: NewXAxisOption([]string{
- "2012",
- "2013",
- "2014",
- "2015",
- "2016",
- "2017",
- }),
- YAxisList: []YAxisOption{
- {
-
- Min: NewFloatPoint(0),
- Max: NewFloatPoint(90),
- },
- },
- SeriesList: []Series{
- NewSeriesFromValues([]float64{
- 56.5,
- 82.1,
- 88.7,
- 70.1,
- 53.4,
- 85.1,
- }),
- NewSeriesFromValues([]float64{
- 51.1,
- 51.4,
- 55.1,
- 53.3,
- 73.8,
- 68.7,
- }),
- NewSeriesFromValues([]float64{
- 40.1,
- 62.2,
- 69.5,
- 36.4,
- 45.2,
- 32.5,
- }, ChartTypeBar),
- NewSeriesFromValues([]float64{
- 25.2,
- 37.1,
- 41.2,
- 18,
- 33.9,
- 49.1,
- }, ChartTypeBar),
- },
- Children: []ChartOption{
- {
- Legend: LegendOption{
- Show: FalseFlag(),
- Data: []string{
- "Milk Tea",
- "Matcha Latte",
- "Cheese Cocoa",
- "Walnut Brownie",
- },
- },
- Box: chart.Box{
- Top: 20,
- Left: 400,
- Right: 500,
- Bottom: 120,
- },
- SeriesList: NewPieSeriesList([]float64{
- 435.9,
- 354.3,
- 285.9,
- 204.5,
- }, PieSeriesOption{
- Label: SeriesLabel{
- Show: true,
- },
- Radius: "35%",
- }),
- },
- },
- }
- d, err := Render(opt)
- if err != nil {
- panic(err)
- }
- buf, err := d.Bytes()
- if err != nil {
- panic(err)
- }
- if len(buf) == 0 {
- panic(errors.New("data is nil"))
- }
- }
-}
diff --git a/draw.go b/draw.go
deleted file mode 100644
index 1708662..0000000
--- a/draw.go
+++ /dev/null
@@ -1,372 +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 (
- "bytes"
- "errors"
- "math"
-
- "github.com/golang/freetype/truetype"
- "github.com/wcharczuk/go-chart/v2"
- "github.com/wcharczuk/go-chart/v2/drawing"
-)
-
-const (
- PositionLeft = "left"
- PositionRight = "right"
- PositionCenter = "center"
- PositionTop = "top"
- PositionBottom = "bottom"
-)
-
-const (
- OrientHorizontal = "horizontal"
- OrientVertical = "vertical"
-)
-
-type Draw struct {
- // Render
- Render chart.Renderer
- // The canvas box
- Box chart.Box
- // The font for draw
- Font *truetype.Font
- // The parent of draw
- parent *Draw
-}
-
-type DrawOption struct {
- // Draw type, "svg" or "png", default type is "svg"
- Type string
- // Parent of draw
- Parent *Draw
- // The width of draw canvas
- Width int
- // The height of draw canvas
- Height int
-}
-
-type Option func(*Draw) error
-
-// PaddingOption sets the padding of draw canvas
-func PaddingOption(padding chart.Box) Option {
- return func(d *Draw) error {
- d.Box.Left += padding.Left
- d.Box.Top += padding.Top
- d.Box.Right -= padding.Right
- d.Box.Bottom -= padding.Bottom
- return nil
- }
-}
-
-// BoxOption set the box of draw canvas
-func BoxOption(box chart.Box) Option {
- return func(d *Draw) error {
- if box.IsZero() {
- return nil
- }
- d.Box = box
- return nil
- }
-}
-
-// NewDraw returns a new draw canvas
-func NewDraw(opt DrawOption, opts ...Option) (*Draw, error) {
- if opt.Parent == nil && (opt.Width <= 0 || opt.Height <= 0) {
- return nil, errors.New("parent and width/height can not be nil")
- }
- font, _ := chart.GetDefaultFont()
- d := &Draw{
- Font: font,
- }
- width := opt.Width
- height := opt.Height
- if opt.Parent != nil {
- d.parent = opt.Parent
- d.Render = d.parent.Render
- d.Box = opt.Parent.Box.Clone()
- }
- if width != 0 && height != 0 {
- d.Box.Right = width + d.Box.Left
- d.Box.Bottom = height + d.Box.Top
- }
- // 创建render
- if d.parent == nil {
- fn := chart.SVG
- if opt.Type == ChartOutputPNG {
- fn = chart.PNG
- }
- r, err := fn(d.Box.Right, d.Box.Bottom)
- if err != nil {
- return nil, err
- }
- d.Render = r
- }
-
- for _, o := range opts {
- err := o(d)
- if err != nil {
- return nil, err
- }
- }
- return d, nil
-}
-
-// Parent returns the parent of draw
-func (d *Draw) Parent() *Draw {
- return d.parent
-}
-
-// Top returns the top parent of draw
-func (d *Draw) Top() *Draw {
- if d.parent == nil {
- return nil
- }
- t := d.parent
- // 限制最多查询次数,避免嵌套引用
- for i := 50; i > 0; i-- {
- if t.parent == nil {
- break
- }
- t = t.parent
- }
- return t
-}
-
-// Bytes returns the data of draw canvas
-func (d *Draw) Bytes() ([]byte, error) {
- buffer := bytes.Buffer{}
- err := d.Render.Save(&buffer)
- if err != nil {
- return nil, err
- }
- return buffer.Bytes(), err
-}
-
-func (d *Draw) moveTo(x, y int) {
- d.Render.MoveTo(x+d.Box.Left, y+d.Box.Top)
-}
-
-func (d *Draw) arcTo(cx, cy int, rx, ry, startAngle, delta float64) {
- d.Render.ArcTo(cx+d.Box.Left, cy+d.Box.Top, rx, ry, startAngle, delta)
-}
-
-func (d *Draw) lineTo(x, y int) {
- d.Render.LineTo(x+d.Box.Left, y+d.Box.Top)
-}
-
-func (d *Draw) pin(x, y, width int) {
- r := float64(width) / 2
- y -= width / 4
- angle := chart.DegreesToRadians(15)
-
- startAngle := math.Pi/2 + angle
- delta := 2*math.Pi - 2*angle
- d.arcTo(x, y, r, r, startAngle, delta)
- d.lineTo(x, y)
- d.Render.Close()
- d.Render.FillStroke()
-
- startX := x - int(r)
- startY := y
- endX := x + int(r)
- endY := y
- d.moveTo(startX, startY)
-
- left := d.Box.Left
- top := d.Box.Top
- cx := x
- cy := y + int(r*2.5)
- d.Render.QuadCurveTo(cx+left, cy+top, endX+left, endY+top)
- d.Render.Close()
- d.Render.Fill()
-}
-
-func (d *Draw) arrowLeft(x, y, width, height int) {
- d.arrow(x, y, width, height, PositionLeft)
-}
-
-func (d *Draw) arrowRight(x, y, width, height int) {
- d.arrow(x, y, width, height, PositionRight)
-}
-
-func (d *Draw) arrowTop(x, y, width, height int) {
- d.arrow(x, y, width, height, PositionTop)
-}
-func (d *Draw) arrowBottom(x, y, width, height int) {
- d.arrow(x, y, width, height, PositionBottom)
-}
-
-func (d *Draw) arrow(x, y, width, height int, direction string) {
- halfWidth := width >> 1
- halfHeight := height >> 1
- if direction == PositionTop || direction == PositionBottom {
- x0 := x - halfWidth
- x1 := x0 + width
- dy := -height / 3
- y0 := y
- y1 := y0 - height
- if direction == PositionBottom {
- y0 = y - height
- y1 = y
- dy = 2 * dy
- }
- d.moveTo(x0, y0)
- d.lineTo(x0+halfWidth, y1)
- d.lineTo(x1, y0)
- d.lineTo(x0+halfWidth, y+dy)
- d.lineTo(x0, y0)
- } else {
- x0 := x + width
- x1 := x0 - width
- y0 := y - halfHeight
- dx := -width / 3
- if direction == PositionRight {
- x0 = x - width
- dx = -dx
- x1 = x0 + width
- }
- d.moveTo(x0, y0)
- d.lineTo(x1, y0+halfHeight)
- d.lineTo(x0, y0+height)
- d.lineTo(x0+dx, y0+halfHeight)
- d.lineTo(x0, y0)
- }
- d.Render.FillStroke()
-}
-
-func (d *Draw) makeLine(x, y, width int) {
- arrowWidth := 16
- arrowHeight := 10
- endX := x + width
- d.circle(3, x, y)
- d.Render.Fill()
- d.moveTo(x+5, y)
- d.lineTo(endX-arrowWidth, y)
- d.Render.Stroke()
- d.Render.SetStrokeDashArray([]float64{})
- d.arrowRight(endX, y, arrowWidth, arrowHeight)
-}
-
-func (d *Draw) circle(radius float64, x, y int) {
- d.Render.Circle(radius, x+d.Box.Left, y+d.Box.Top)
-}
-
-func (d *Draw) text(body string, x, y int) {
- d.Render.Text(body, x+d.Box.Left, y+d.Box.Top)
-}
-
-func (d *Draw) textFit(body string, x, y, width int, style chart.Style) chart.Box {
- style.TextWrap = chart.TextWrapWord
- r := d.Render
- lines := chart.Text.WrapFit(r, body, width, style)
- style.WriteTextOptionsToRenderer(r)
- var output chart.Box
-
- for index, line := range lines {
- x0 := x
- y0 := y + output.Height()
- d.text(line, x0, y0)
- lineBox := r.MeasureText(line)
- output.Right = chart.MaxInt(lineBox.Right, output.Right)
- output.Bottom += lineBox.Height()
- if index < len(lines)-1 {
- output.Bottom += +style.GetTextLineSpacing()
- }
- }
- return output
-}
-
-func (d *Draw) measureTextFit(body string, x, y, width int, style chart.Style) chart.Box {
- style.TextWrap = chart.TextWrapWord
- r := d.Render
- lines := chart.Text.WrapFit(r, body, width, style)
- style.WriteTextOptionsToRenderer(r)
- return chart.Text.MeasureLines(r, lines, style)
-}
-
-func (d *Draw) lineStroke(points []Point, style LineStyle) {
- s := style.Style()
- if !s.ShouldDrawStroke() {
- return
- }
- r := d.Render
- s.GetStrokeOptions().WriteDrawingOptionsToRenderer(r)
- for index, point := range points {
- x := point.X
- y := point.Y
- if index == 0 {
- d.moveTo(x, y)
- } else {
- d.lineTo(x, y)
- }
- }
- r.Stroke()
-}
-
-func (d *Draw) setBackground(width, height int, color drawing.Color) {
- r := d.Render
- s := chart.Style{
- FillColor: color,
- }
- s.WriteToRenderer(r)
- r.MoveTo(0, 0)
- r.LineTo(width, 0)
- r.LineTo(width, height)
- r.LineTo(0, height)
- r.LineTo(0, 0)
- r.FillStroke()
-}
-
-func (d *Draw) polygon(center Point, radius float64, sides int) {
- points := getPolygonPoints(center, radius, sides)
- for i, p := range points {
- if i == 0 {
- d.moveTo(p.X, p.Y)
- } else {
- d.lineTo(p.X, p.Y)
- }
- }
- d.lineTo(points[0].X, points[0].Y)
- d.Render.Stroke()
-}
-
-func (d *Draw) fill(points []Point, s chart.Style) {
- if !s.ShouldDrawFill() {
- return
- }
- r := d.Render
- var x, y int
- s.GetFillOptions().WriteDrawingOptionsToRenderer(r)
- for index, point := range points {
- x = point.X
- y = point.Y
- if index == 0 {
- d.moveTo(x, y)
- } else {
- d.lineTo(x, y)
- }
- }
- r.Fill()
-}
diff --git a/draw_test.go b/draw_test.go
deleted file mode 100644
index f6a3dd1..0000000
--- a/draw_test.go
+++ /dev/null
@@ -1,507 +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 (
- "math"
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/wcharczuk/go-chart/v2"
- "github.com/wcharczuk/go-chart/v2/drawing"
-)
-
-func TestParentOption(t *testing.T) {
- assert := assert.New(t)
- p, err := NewDraw(DrawOption{
- Width: 400,
- Height: 300,
- })
- assert.Nil(err)
-
- d, err := NewDraw(DrawOption{
- Parent: p,
- })
- assert.Nil(err)
- assert.Equal(p, d.parent)
-}
-
-func TestWidthHeightOption(t *testing.T) {
- assert := assert.New(t)
-
- // no parent
- width := 300
- height := 200
- d, err := NewDraw(DrawOption{
- Width: width,
- Height: height,
- })
- assert.Nil(err)
- assert.Equal(chart.Box{
- Top: 0,
- Left: 0,
- Right: width,
- Bottom: height,
- }, d.Box)
-
- width = 500
- height = 600
- // with parent
- p, err := NewDraw(
- DrawOption{
- Width: width,
- Height: height,
- },
- PaddingOption(chart.NewBox(5, 5, 5, 5)),
- )
- assert.Nil(err)
- d, err = NewDraw(
- DrawOption{
- Parent: p,
- },
- PaddingOption(chart.NewBox(1, 2, 3, 4)),
- )
- assert.Nil(err)
- assert.Equal(chart.Box{
- Top: 6,
- Left: 7,
- Right: 492,
- Bottom: 591,
- }, d.Box)
-}
-
-func TestBoxOption(t *testing.T) {
- assert := assert.New(t)
-
- d, err := NewDraw(DrawOption{
- Width: 400,
- Height: 300,
- })
- assert.Nil(err)
-
- err = BoxOption(chart.Box{
- Left: 10,
- Top: 20,
- Right: 50,
- Bottom: 100,
- })(d)
- assert.Nil(err)
- assert.Equal(chart.Box{
- Left: 10,
- Top: 20,
- Right: 50,
- Bottom: 100,
- }, d.Box)
-
- // zero box will be ignored
- err = BoxOption(chart.Box{})(d)
- assert.Nil(err)
- assert.Equal(chart.Box{
- Left: 10,
- Top: 20,
- Right: 50,
- Bottom: 100,
- }, d.Box)
-}
-
-func TestPaddingOption(t *testing.T) {
- assert := assert.New(t)
-
- d, err := NewDraw(DrawOption{
- Width: 400,
- Height: 300,
- })
- assert.Nil(err)
-
- // 默认的box
- assert.Equal(chart.Box{
- Right: 400,
- Bottom: 300,
- }, d.Box)
-
- // 设置padding之后的box
- d, err = NewDraw(DrawOption{
- Width: 400,
- Height: 300,
- }, PaddingOption(chart.Box{
- Left: 1,
- Top: 2,
- Right: 3,
- Bottom: 4,
- }))
- assert.Nil(err)
- assert.Equal(chart.Box{
- Top: 2,
- Left: 1,
- Right: 397,
- Bottom: 296,
- }, d.Box)
-
- p := d
- // 设置父元素之后的box
- d, err = NewDraw(
- DrawOption{
- Parent: p,
- },
- PaddingOption(chart.Box{
- Left: 1,
- Top: 2,
- Right: 3,
- Bottom: 4,
- }),
- )
- assert.Nil(err)
- assert.Equal(chart.Box{
- Top: 4,
- Left: 2,
- Right: 394,
- Bottom: 292,
- }, d.Box)
-}
-
-func TestParentTop(t *testing.T) {
- assert := assert.New(t)
- d1, err := NewDraw(DrawOption{
- Width: 400,
- Height: 300,
- })
- assert.Nil(err)
-
- d2, err := NewDraw(DrawOption{
- Parent: d1,
- })
- assert.Nil(err)
-
- d3, err := NewDraw(DrawOption{
- Parent: d2,
- })
- assert.Nil(err)
-
- assert.Equal(d2, d3.Parent())
- assert.Equal(d1, d2.Parent())
- assert.Equal(d1, d3.Top())
- assert.Equal(d1, d2.Top())
-}
-
-func TestDraw(t *testing.T) {
- assert := assert.New(t)
-
- tests := []struct {
- fn func(d *Draw)
- result string
- }{
- // moveTo, lineTo
- {
- fn: func(d *Draw) {
- d.moveTo(1, 1)
- d.lineTo(2, 2)
- d.Render.Stroke()
- },
- result: "",
- },
- // circle
- {
- fn: func(d *Draw) {
- d.circle(5, 2, 3)
- },
- result: "",
- },
- // text
- {
- fn: func(d *Draw) {
- d.text("hello world!", 3, 6)
- },
- result: "",
- },
- // line stroke
- {
- fn: func(d *Draw) {
- d.lineStroke([]Point{
- {
- X: 1,
- Y: 2,
- },
- {
- X: 3,
- Y: 4,
- },
- }, LineStyle{
- StrokeColor: drawing.ColorBlack,
- StrokeWidth: 1,
- })
- },
- result: "",
- },
- // set background
- {
- fn: func(d *Draw) {
- d.setBackground(400, 300, chart.ColorWhite)
- },
- result: "",
- },
- // arcTo
- {
- fn: func(d *Draw) {
- chart.Style{
- StrokeWidth: 1,
- StrokeColor: drawing.ColorBlack,
- FillColor: drawing.ColorBlue,
- }.WriteToRenderer(d.Render)
- d.arcTo(100, 100, 100, 100, 0, math.Pi/2)
- d.Render.Close()
- d.Render.FillStroke()
- },
- result: "",
- },
- // pin
- {
- fn: func(d *Draw) {
- chart.Style{
- StrokeWidth: 1,
- StrokeColor: drawing.Color{
- R: 84,
- G: 112,
- B: 198,
- A: 255,
- },
- FillColor: drawing.Color{
- R: 84,
- G: 112,
- B: 198,
- A: 255,
- },
- }.WriteToRenderer(d.Render)
- d.pin(30, 30, 30)
- },
- result: "",
- },
- // arrow left
- {
- fn: func(d *Draw) {
- chart.Style{
- StrokeWidth: 1,
- StrokeColor: drawing.Color{
- R: 84,
- G: 112,
- B: 198,
- A: 255,
- },
- FillColor: drawing.Color{
- R: 84,
- G: 112,
- B: 198,
- A: 255,
- },
- }.WriteToRenderer(d.Render)
- d.arrowLeft(30, 30, 16, 10)
- },
- result: "",
- },
- // arrow right
- {
- fn: func(d *Draw) {
- chart.Style{
- StrokeWidth: 1,
- StrokeColor: drawing.Color{
- R: 84,
- G: 112,
- B: 198,
- A: 255,
- },
- FillColor: drawing.Color{
- R: 84,
- G: 112,
- B: 198,
- A: 255,
- },
- }.WriteToRenderer(d.Render)
- d.arrowRight(30, 30, 16, 10)
- },
- result: "",
- },
- // arrow top
- {
- fn: func(d *Draw) {
- chart.Style{
- StrokeWidth: 1,
- StrokeColor: drawing.Color{
- R: 84,
- G: 112,
- B: 198,
- A: 255,
- },
- FillColor: drawing.Color{
- R: 84,
- G: 112,
- B: 198,
- A: 255,
- },
- }.WriteToRenderer(d.Render)
- d.arrowTop(30, 30, 10, 16)
- },
- result: "",
- },
- // arrow bottom
- {
- fn: func(d *Draw) {
- chart.Style{
- StrokeWidth: 1,
- StrokeColor: drawing.Color{
- R: 84,
- G: 112,
- B: 198,
- A: 255,
- },
- FillColor: drawing.Color{
- R: 84,
- G: 112,
- B: 198,
- A: 255,
- },
- }.WriteToRenderer(d.Render)
- d.arrowBottom(30, 30, 10, 16)
- },
- result: "",
- },
- // mark line
- {
- fn: func(d *Draw) {
- chart.Style{
- StrokeWidth: 1,
- StrokeColor: drawing.Color{
- R: 84,
- G: 112,
- B: 198,
- A: 255,
- },
- FillColor: drawing.Color{
- R: 84,
- G: 112,
- B: 198,
- A: 255,
- },
- StrokeDashArray: []float64{
- 4,
- 2,
- },
- }.WriteToRenderer(d.Render)
- d.makeLine(0, 20, 300)
- },
- result: "",
- },
- // polygon
- {
- fn: func(d *Draw) {
- chart.Style{
- StrokeWidth: 1,
- StrokeColor: drawing.Color{
- R: 84,
- G: 112,
- B: 198,
- A: 255,
- },
- }.WriteToRenderer(d.Render)
- d.polygon(Point{
- X: 100,
- Y: 100,
- }, 50, 6)
- },
- result: "",
- },
- // fill
- {
- fn: func(d *Draw) {
- d.fill([]Point{
- {
- X: 0,
- Y: 0,
- },
- {
- X: 0,
- Y: 100,
- },
- {
- X: 100,
- Y: 100,
- },
- {
- X: 0,
- Y: 0,
- },
- }, chart.Style{
- FillColor: drawing.Color{
- R: 84,
- G: 112,
- B: 198,
- A: 255,
- },
- })
- },
- result: "",
- },
- }
- for _, tt := range tests {
- d, err := NewDraw(DrawOption{
- Width: 400,
- Height: 300,
- }, PaddingOption(chart.Box{
- Left: 5,
- Top: 10,
- }))
- assert.Nil(err)
- tt.fn(d)
- data, err := d.Bytes()
- assert.Nil(err)
- assert.Equal(tt.result, string(data))
- }
-}
-
-func TestDrawTextFit(t *testing.T) {
- assert := assert.New(t)
- d, err := NewDraw(DrawOption{
- Width: 400,
- Height: 300,
- })
- assert.Nil(err)
- f, _ := chart.GetDefaultFont()
- style := chart.Style{
- FontSize: 12,
- FontColor: chart.ColorBlack,
- Font: f,
- }
- box := d.textFit("Hello World!", 0, 20, 80, style)
- assert.Equal(chart.Box{
- Right: 45,
- Bottom: 35,
- }, box)
-
- box = d.textFit("Hello World!", 0, 100, 200, style)
- assert.Equal(chart.Box{
- Right: 84,
- Bottom: 15,
- }, box)
-
- buf, err := d.Bytes()
- assert.Nil(err)
- assert.Equal(``, string(buf))
-}
diff --git a/echarts.go b/echarts.go
deleted file mode 100644
index 4ebb9ad..0000000
--- a/echarts.go
+++ /dev/null
@@ -1,499 +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 (
- "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 EChartsSeriesDataValue struct {
- values []float64
-}
-
-func (value *EChartsSeriesDataValue) UnmarshalJSON(data []byte) error {
- data = convertToArray(data)
- return json.Unmarshal(data, &value.values)
-}
-func (value *EChartsSeriesDataValue) First() float64 {
- if len(value.values) == 0 {
- return 0
- }
- return value.values[0]
-}
-func NewEChartsSeriesDataValue(values ...float64) EChartsSeriesDataValue {
- return EChartsSeriesDataValue{
- values: values,
- }
-}
-
-type EChartsSeriesData struct {
- Value EChartsSeriesDataValue `json:"value"`
- Name string `json:"name"`
- ItemStyle EChartStyle `json:"itemStyle"`
-}
-type _EChartsSeriesData EChartsSeriesData
-
-var numericRep = regexp.MustCompile(`^[-+]?[0-9]+(?:\.[0-9]+)?$`)
-
-func (es *EChartsSeriesData) UnmarshalJSON(data []byte) error {
- data = bytes.TrimSpace(data)
- if len(data) == 0 {
- return nil
- }
- if numericRep.Match(data) {
- v, err := strconv.ParseFloat(string(data), 64)
- if err != nil {
- return err
- }
- es.Value = EChartsSeriesDataValue{
- values: []float64{
- 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 `json:"color"`
- } `json:"lineStyle"`
- } `json:"axisLine"`
-}
-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 EChartsMarkData struct {
- Type string `json:"type"`
-}
-type _EChartsMarkData EChartsMarkData
-
-func (emd *EChartsMarkData) UnmarshalJSON(data []byte) error {
- data = bytes.TrimSpace(data)
- if len(data) == 0 {
- return nil
- }
- data = convertToArray(data)
- ds := make([]*_EChartsMarkData, 0)
- err := json.Unmarshal(data, &ds)
- if err != nil {
- return err
- }
- for _, d := range ds {
- if d.Type != "" {
- emd.Type = d.Type
- }
- }
- return nil
-}
-
-type EChartsMarkPoint struct {
- SymbolSize int `json:"symbolSize"`
- Data []EChartsMarkData `json:"data"`
-}
-
-func (emp *EChartsMarkPoint) ToSeriesMarkPoint() SeriesMarkPoint {
- sp := SeriesMarkPoint{
- SymbolSize: emp.SymbolSize,
- }
- if len(emp.Data) == 0 {
- return sp
- }
- data := make([]SeriesMarkData, len(emp.Data))
- for index, item := range emp.Data {
- data[index].Type = item.Type
- }
- sp.Data = data
- return sp
-}
-
-type EChartsMarkLine struct {
- Data []EChartsMarkData `json:"data"`
-}
-
-func (eml *EChartsMarkLine) ToSeriesMarkLine() SeriesMarkLine {
- sl := SeriesMarkLine{}
- if len(eml.Data) == 0 {
- return sl
- }
- data := make([]SeriesMarkData, len(eml.Data))
- for index, item := range eml.Data {
- data[index].Type = item.Type
- }
- sl.Data = data
- return sl
-}
-
-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"`
- Max *float64 `json:"max"`
- Min *float64 `json:"min"`
-}
-type EChartsSeriesList []EChartsSeries
-
-func (esList EChartsSeriesList) ToSeriesList() SeriesList {
- seriesList := make(SeriesList, 0, len(esList))
- for _, item := range esList {
- // 如果是pie,则每个子荐生成一个series
- if item.Type == ChartTypePie {
- for _, dataItem := range item.Data {
- seriesList = append(seriesList, Series{
- Type: item.Type,
- Name: dataItem.Name,
- Label: SeriesLabel{
- Show: true,
- },
- Radius: item.Radius,
- Data: []SeriesData{
- {
- Value: dataItem.Value.First(),
- },
- },
- })
- }
- continue
- }
- // 如果是radar或funnel
- if item.Type == ChartTypeRadar ||
- item.Type == ChartTypeFunnel {
- for _, dataItem := range item.Data {
- seriesList = append(seriesList, Series{
- Name: dataItem.Name,
- Type: item.Type,
- Data: NewSeriesDataFromValues(dataItem.Value.values),
- Max: item.Max,
- Min: item.Min,
- })
- }
- continue
- }
- data := make([]SeriesData, len(item.Data))
- for j, dataItem := range item.Data {
- data[j] = SeriesData{
- Value: dataItem.Value.First(),
- Style: dataItem.ItemStyle.ToStyle(),
- }
- }
- seriesList = append(seriesList, 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,
- 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 {
- s := chart.Style{
- FontSize: et.FontSize,
- FontColor: parseColor(et.Color),
- }
- if et.FontFamily != "" {
- s.Font, _ = GetFont(et.FontFamily)
- }
- return s
-}
-
-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"`
- Radar struct {
- Indicator []RadarIndicator `json:"indicator"`
- } `json:"radar"`
- 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,
- },
- RadarIndicators: eo.Radar.Indicator,
- 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),
- }
- }
- o.YAxisList = yAxisOptions
-
- 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")
-}
diff --git a/echarts_test.go b/echarts_test.go
deleted file mode 100644
index 05c2a40..0000000
--- a/echarts_test.go
+++ /dev/null
@@ -1,592 +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 (
- "encoding/json"
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/wcharczuk/go-chart/v2"
- "github.com/wcharczuk/go-chart/v2/drawing"
-)
-
-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 TestEChartStyle(t *testing.T) {
- assert := assert.New(t)
-
- s := EChartStyle{
- Color: "#aaa",
- }
- r := drawing.Color{
- R: 170,
- G: 170,
- B: 170,
- A: 255,
- }
- assert.Equal(chart.Style{
- FillColor: r,
- FontColor: r,
- StrokeColor: r,
- }, s.ToStyle())
-}
-
-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{}
-
- err := ep.UnmarshalJSON([]byte(`10`))
- assert.Nil(err)
- assert.Equal(EChartsPadding{
- Box: chart.Box{
- Top: 10,
- Right: 10,
- Bottom: 10,
- Left: 10,
- },
- }, ep)
-
- ep = EChartsPadding{}
- err = ep.UnmarshalJSON([]byte(`[10, 20]`))
- assert.Nil(err)
- assert.Equal(EChartsPadding{
- Box: chart.Box{
- Top: 10,
- Right: 20,
- Bottom: 10,
- Left: 20,
- },
- }, ep)
-
- ep = EChartsPadding{}
- err = ep.UnmarshalJSON([]byte(`[10, 20, 30]`))
- assert.Nil(err)
- assert.Equal(EChartsPadding{
- Box: chart.Box{
- Top: 10,
- Right: 20,
- Bottom: 30,
- Left: 20,
- },
- }, ep)
-
- ep = EChartsPadding{}
- err = ep.UnmarshalJSON([]byte(`[10, 20, 30, 40]`))
- assert.Nil(err)
- 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: NewEChartsSeriesDataValue(123),
- }, esd)
-
- esd = EChartsSeriesData{}
- err = esd.UnmarshalJSON([]byte(`2.1`))
- assert.Nil(err)
- assert.Equal(EChartsSeriesData{
- Value: NewEChartsSeriesDataValue(2.1),
- }, esd)
-
- esd = EChartsSeriesData{}
- err = esd.UnmarshalJSON([]byte(`{
- "value": 123.12,
- "name": "test",
- "itemStyle": {
- "color": "#aaa"
- }
- }`))
- assert.Nil(err)
- assert.Equal(EChartsSeriesData{
- Value: NewEChartsSeriesDataValue(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: NewEChartsSeriesDataValue(120),
- },
- {
- Value: NewEChartsSeriesDataValue(132),
- },
- },
- },
- {
- Name: "Union Ads",
- Type: "bar",
- Data: []EChartsSeriesData{
- {
- Value: NewEChartsSeriesDataValue(220),
- },
- {
- Value: NewEChartsSeriesDataValue(182),
- },
- },
- },
- }, esList)
-}
-
-func TestEChartsMarkData(t *testing.T) {
- assert := assert.New(t)
-
- emd := EChartsMarkData{}
- err := emd.UnmarshalJSON([]byte(`{"type": "average"}`))
- assert.Nil(err)
- assert.Equal("average", emd.Type)
-
- emd = EChartsMarkData{}
- err = emd.UnmarshalJSON([]byte(`[{}, {"type": "average"}]`))
- assert.Nil(err)
- assert.Equal("average", emd.Type)
-}
-
-func TestEChartsMarkPoint(t *testing.T) {
- assert := assert.New(t)
-
- p := EChartsMarkPoint{}
-
- err := json.Unmarshal([]byte(`{
- "symbolSize": 30,
- "data": [
- {
- "type": "max"
- },
- {
- "type": "min"
- }
- ]
- }`), &p)
- assert.Nil(err)
- assert.Equal(SeriesMarkPoint{
- SymbolSize: 30,
- Data: []SeriesMarkData{
- {
- Type: "max",
- },
- {
- Type: "min",
- },
- },
- }, p.ToSeriesMarkPoint())
-}
-
-func TestEChartsMarkLine(t *testing.T) {
- assert := assert.New(t)
- l := EChartsMarkLine{}
-
- err := json.Unmarshal([]byte(`{
- "data": [
- {
- "type": "max"
- },
- {
- "type": "min"
- }
- ]
- }`), &l)
- assert.Nil(err)
- assert.Equal(SeriesMarkLine{
- Data: []SeriesMarkData{
- {
- Type: "max",
- },
- {
- Type: "min",
- },
- },
- }, l.ToSeriesMarkLine())
-}
-
-func TestEChartsTextStyle(t *testing.T) {
- assert := assert.New(t)
-
- s := EChartsTextStyle{
- Color: "#aaa",
- FontFamily: "test",
- FontSize: 14,
- }
- assert.Equal(chart.Style{
- FontColor: drawing.Color{
- R: 170,
- G: 170,
- B: 170,
- A: 255,
- },
- FontSize: 14,
- }, s.ToStyle())
-}
-
-func TestEChartsSeriesList(t *testing.T) {
- assert := assert.New(t)
-
- // pie
- es := EChartsSeriesList{
- {
- Type: ChartTypePie,
- Radius: "30%",
- Data: []EChartsSeriesData{
- {
- Name: "1",
- Value: EChartsSeriesDataValue{
- values: []float64{
- 1,
- },
- },
- },
- {
- Name: "2",
- Value: EChartsSeriesDataValue{
- values: []float64{
- 2,
- },
- },
- },
- },
- },
- }
- assert.Equal(SeriesList{
- {
- Type: ChartTypePie,
- Name: "1",
- Label: SeriesLabel{
- Show: true,
- },
- Radius: "30%",
- Data: []SeriesData{
- {
- Value: 1,
- },
- },
- },
- {
- Type: ChartTypePie,
- Name: "2",
- Label: SeriesLabel{
- Show: true,
- },
- Radius: "30%",
- Data: []SeriesData{
- {
- Value: 2,
- },
- },
- },
- }, es.ToSeriesList())
-
- es = EChartsSeriesList{
- {
- Type: ChartTypeBar,
- Data: []EChartsSeriesData{
- {
- Value: NewEChartsSeriesDataValue(1),
- ItemStyle: EChartStyle{
- Color: "#aaa",
- },
- },
- {
- Value: NewEChartsSeriesDataValue(2),
- },
- },
- YAxisIndex: 1,
- },
- {
- Data: []EChartsSeriesData{
- {
- Value: NewEChartsSeriesDataValue(3),
- },
- {
- Value: NewEChartsSeriesDataValue(4),
- },
- },
- ItemStyle: EChartStyle{
- Color: "#ccc",
- },
- Label: EChartsLabelOption{
- Color: "#ddd",
- Show: true,
- Distance: 5,
- },
- },
- }
- assert.Equal(SeriesList{
- {
- Type: ChartTypeBar,
- Data: []SeriesData{
- {
- Value: 1,
- Style: chart.Style{
- FontColor: drawing.Color{
- R: 170,
- G: 170,
- B: 170,
- A: 255,
- },
- StrokeColor: drawing.Color{
- R: 170,
- G: 170,
- B: 170,
- A: 255,
- },
- FillColor: drawing.Color{
- R: 170,
- G: 170,
- B: 170,
- A: 255,
- },
- },
- },
- {
- Value: 2,
- },
- },
- YAxisIndex: 1,
- },
- {
- Data: []SeriesData{
- {
- Value: 3,
- },
- {
- Value: 4,
- },
- },
- Style: chart.Style{
- FontColor: drawing.Color{
- R: 204,
- G: 204,
- B: 204,
- A: 255,
- },
- StrokeColor: drawing.Color{
- R: 204,
- G: 204,
- B: 204,
- A: 255,
- },
- FillColor: drawing.Color{
- R: 204,
- G: 204,
- B: 204,
- A: 255,
- },
- },
- Label: SeriesLabel{
- Color: drawing.Color{
- R: 221,
- G: 221,
- B: 221,
- A: 255,
- },
- Show: true,
- Distance: 5,
- },
- MarkPoint: SeriesMarkPoint{},
- MarkLine: SeriesMarkLine{},
- },
- }, es.ToSeriesList())
-
-}
diff --git a/examples/basic/main.go b/examples/basic/main.go
deleted file mode 100644
index 1e7af8d..0000000
--- a/examples/basic/main.go
+++ /dev/null
@@ -1,94 +0,0 @@
-package main
-
-import (
- "io/ioutil"
- "os"
- "path/filepath"
-
- charts "github.com/vicanso/go-charts"
-)
-
-func writeFile(file string, buf []byte) error {
- tmpPath := "./tmp"
- err := os.MkdirAll(tmpPath, 0700)
- if err != nil {
- return err
- }
-
- file = filepath.Join(tmpPath, file)
- err = ioutil.WriteFile(file, buf, 0600)
- if err != nil {
- return err
- }
- return nil
-}
-
-func chartsRender() ([]byte, error) {
- d, err := charts.LineRender([][]float64{
- {
- 150,
- 230,
- 224,
- 218,
- 135,
- 147,
- 260,
- },
- },
- // output type
- charts.PNGTypeOption(),
- // title
- charts.TitleOptionFunc(charts.TitleOption{
- Text: "Line",
- }),
- // x axis
- charts.XAxisOptionFunc(charts.NewXAxisOption([]string{
- "Mon",
- "Tue",
- "Wed",
- "Thu",
- "Fri",
- "Sat",
- "Sun",
- })),
- )
- if err != nil {
- return nil, err
- }
- return d.Bytes()
-}
-
-func echartsRender() ([]byte, error) {
- return charts.RenderEChartsToPNG(`{
- "title": {
- "text": "Line"
- },
- "xAxis": {
- "data": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
- },
- "series": [
- {
- "data": [150, 230, 224, 218, 135, 147, 260]
- }
- ]
- }`)
-}
-
-type Render func() ([]byte, error)
-
-func main() {
- m := map[string]Render{
- "charts-line.png": chartsRender,
- "echarts-line.png": echartsRender,
- }
- for name, fn := range m {
- buf, err := fn()
- if err != nil {
- panic(err)
- }
- err = writeFile(name, buf)
- if err != nil {
- panic(err)
- }
- }
-}
diff --git a/examples/charts/main.go b/examples/charts/main.go
deleted file mode 100644
index fddbe6d..0000000
--- a/examples/charts/main.go
+++ /dev/null
@@ -1,1809 +0,0 @@
-package main
-
-import (
- "bytes"
- "net/http"
- "strconv"
-
- charts "github.com/vicanso/go-charts"
- "github.com/wcharczuk/go-chart/v2"
- "github.com/wcharczuk/go-chart/v2/drawing"
-)
-
-var html = `
-
-
-
-
-
-
- go-charts
-
-
- {{body}}
-
-
-`
-
-func handler(w http.ResponseWriter, req *http.Request, chartOptions []charts.ChartOption, echartsOptions []string) {
- if req.URL.Path != "/" &&
- req.URL.Path != "/echarts" {
- return
- }
- query := req.URL.Query()
- theme := query.Get("theme")
- width, _ := strconv.Atoi(query.Get("width"))
- height, _ := strconv.Atoi(query.Get("height"))
- charts.SetDefaultWidth(width)
- charts.SetDefaultWidth(height)
- bytesList := make([][]byte, 0)
- for _, opt := range chartOptions {
- opt.Theme = theme
- d, err := charts.Render(opt)
- if err != nil {
- panic(err)
- }
- buf, err := d.Bytes()
- if err != nil {
- panic(err)
- }
- bytesList = append(bytesList, buf)
- }
- for _, opt := range echartsOptions {
- buf, err := charts.RenderEChartsToSVG(opt)
- if err != nil {
- panic(err)
- }
- bytesList = append(bytesList, buf)
- }
-
- data := bytes.ReplaceAll([]byte(html), []byte("{{body}}"), bytes.Join(bytesList, []byte("")))
- w.Header().Set("Content-Type", "text/html")
- w.Write(data)
-}
-
-func indexHandler(w http.ResponseWriter, req *http.Request) {
- chartOptions := []charts.ChartOption{
- // 普通折线图
- {
- Title: charts.TitleOption{
- Text: "Line",
- },
- Legend: charts.NewLegendOption([]string{
- "Email",
- "Union Ads",
- "Video Ads",
- "Direct",
- "Search Engine",
- }),
- XAxis: charts.NewXAxisOption([]string{
- "Mon",
- "Tue",
- "Wed",
- "Thu",
- "Fri",
- "Sat",
- "Sun",
- }),
- SeriesList: []charts.Series{
- charts.NewSeriesFromValues([]float64{
- 120,
- 132,
- 101,
- 134,
- 90,
- 230,
- 210,
- }),
- charts.NewSeriesFromValues([]float64{
- 220,
- 182,
- 191,
- 234,
- 290,
- 330,
- 310,
- }),
- charts.NewSeriesFromValues([]float64{
- 150,
- 232,
- 201,
- 154,
- 190,
- 330,
- 410,
- }),
- charts.NewSeriesFromValues([]float64{
- 320,
- 332,
- 301,
- 334,
- 390,
- 330,
- 320,
- }),
- charts.NewSeriesFromValues([]float64{
- 820,
- 932,
- 901,
- 934,
- 1290,
- 1330,
- 1320,
- }),
- },
- },
- // 温度折线图
- {
- Title: charts.TitleOption{
- Text: "Temperature Change in the Coming Week",
- },
- Padding: chart.Box{
- Top: 20,
- Left: 20,
- Right: 30,
- Bottom: 20,
- },
- Legend: charts.NewLegendOption([]string{
- "Highest",
- "Lowest",
- }, charts.PositionRight),
- XAxis: charts.NewXAxisOption([]string{
- "Mon",
- "Tue",
- "Wed",
- "Thu",
- "Fri",
- "Sat",
- "Sun",
- }, charts.FalseFlag()),
- SeriesList: []charts.Series{
- {
- Data: charts.NewSeriesDataFromValues([]float64{
- 14,
- 11,
- 13,
- 11,
- 12,
- 12,
- 7,
- }),
- MarkPoint: charts.NewMarkPoint(charts.SeriesMarkDataTypeMax, charts.SeriesMarkDataTypeMin),
- MarkLine: charts.NewMarkLine(charts.SeriesMarkDataTypeAverage),
- },
- {
- Data: charts.NewSeriesDataFromValues([]float64{
- 1,
- -2,
- 2,
- 5,
- 3,
- 2,
- 0,
- }),
- MarkLine: charts.NewMarkLine(charts.SeriesMarkDataTypeAverage),
- },
- },
- },
- // 柱状图
- {
- Title: charts.TitleOption{
- Text: "Bar",
- },
- XAxis: charts.NewXAxisOption([]string{
- "Mon",
- "Tue",
- "Wed",
- "Thu",
- "Fri",
- "Sat",
- "Sun",
- }),
- Legend: charts.LegendOption{
- Data: []string{
- "Rainfall",
- "Evaporation",
- },
- Icon: charts.LegendIconRect,
- },
- SeriesList: []charts.Series{
- charts.NewSeriesFromValues([]float64{
- 120,
- 200,
- 150,
- 80,
- 70,
- 110,
- 130,
- }, charts.ChartTypeBar),
- {
- Type: charts.ChartTypeBar,
- Data: []charts.SeriesData{
- {
- Value: 100,
- },
- {
- Value: 190,
- Style: chart.Style{
- FillColor: drawing.Color{
- R: 169,
- G: 0,
- B: 0,
- A: 255,
- },
- },
- },
- {
- Value: 230,
- },
- {
- Value: 140,
- },
- {
- Value: 100,
- },
- {
- Value: 200,
- },
- {
- Value: 180,
- },
- },
- },
- },
- },
- // 柱状图+mark
- {
- Title: charts.TitleOption{
- Text: "Rainfall vs Evaporation",
- Subtext: "Fake Data",
- },
- Padding: chart.Box{
- Top: 20,
- Right: 20,
- Bottom: 20,
- Left: 20,
- },
- XAxis: charts.NewXAxisOption([]string{
- "Jan",
- "Feb",
- "Mar",
- "Apr",
- "May",
- "Jun",
- "Jul",
- "Aug",
- "Sep",
- "Oct",
- "Nov",
- "Dec",
- }),
- Legend: charts.NewLegendOption([]string{
- "Rainfall",
- "Evaporation",
- }, charts.PositionRight),
- SeriesList: []charts.Series{
- {
- Type: charts.ChartTypeBar,
- Data: charts.NewSeriesDataFromValues([]float64{
- 2.0,
- 4.9,
- 7.0,
- 23.2,
- 25.6,
- 76.7,
- 135.6,
- 162.2,
- 32.6,
- 20.0,
- 6.4,
- 3.3,
- }),
- MarkPoint: charts.NewMarkPoint(
- charts.SeriesMarkDataTypeMax,
- charts.SeriesMarkDataTypeMin,
- ),
- MarkLine: charts.NewMarkLine(
- charts.SeriesMarkDataTypeAverage,
- ),
- },
- {
- Type: charts.ChartTypeBar,
- Data: charts.NewSeriesDataFromValues([]float64{
- 2.6,
- 5.9,
- 9.0,
- 26.4,
- 28.7,
- 70.7,
- 175.6,
- 182.2,
- 48.7,
- 18.8,
- 6.0,
- 2.3,
- }),
- MarkPoint: charts.NewMarkPoint(
- charts.SeriesMarkDataTypeMax,
- charts.SeriesMarkDataTypeMin,
- ),
- MarkLine: charts.NewMarkLine(
- charts.SeriesMarkDataTypeAverage,
- ),
- },
- },
- },
- // 双Y轴示例
- {
- XAxis: charts.NewXAxisOption([]string{
- "Jan",
- "Feb",
- "Mar",
- "Apr",
- "May",
- "Jun",
- "Jul",
- "Aug",
- "Sep",
- "Oct",
- "Nov",
- "Dec",
- }),
- Legend: charts.NewLegendOption([]string{
- "Evaporation",
- "Precipitation",
- "Temperature",
- }),
- YAxisList: []charts.YAxisOption{
- {
- Formatter: "{value}°C",
- Color: drawing.Color{
- R: 250,
- G: 200,
- B: 88,
- A: 255,
- },
- },
- {
- Formatter: "{value}ml",
- Color: drawing.Color{
- R: 84,
- G: 112,
- B: 198,
- A: 255,
- },
- },
- },
- SeriesList: []charts.Series{
- {
- Type: charts.ChartTypeBar,
- Data: charts.NewSeriesDataFromValues([]float64{
- 2.0,
- 4.9,
- 7.0,
- 23.2,
- 25.6,
- 76.7,
- 135.6,
- 162.2,
- 32.6,
- 20.0,
- 6.4,
- 3.3,
- 10.2,
- }),
- YAxisIndex: 1,
- },
- {
- Type: charts.ChartTypeBar,
- Data: charts.NewSeriesDataFromValues([]float64{
- 2.6,
- 5.9,
- 9.0,
- 26.4,
- 28.7,
- 70.7,
- 175.6,
- 182.2,
- 48.7,
- 18.8,
- 6.0,
- 2.3,
- 20.2,
- }),
- YAxisIndex: 1,
- },
- {
- Data: charts.NewSeriesDataFromValues([]float64{
- 2.0,
- 2.2,
- 3.3,
- 4.5,
- 6.3,
- 10.2,
- 20.3,
- 23.4,
- 23.0,
- 16.5,
- 12.0,
- 6.2,
- 30.3,
- }),
- },
- },
- },
- // 饼图
- {
- Title: charts.TitleOption{
- Text: "Referer of a Website",
- Subtext: "Fake Data",
- Left: charts.PositionCenter,
- },
- Legend: charts.LegendOption{
- Orient: charts.OrientVertical,
- Data: []string{
- "Search Engine",
- "Direct",
- "Email",
- "Union Ads",
- "Video Ads",
- },
- Left: charts.PositionLeft,
- },
- SeriesList: charts.NewPieSeriesList([]float64{
- 1048,
- 735,
- 580,
- 484,
- 300,
- }, charts.PieSeriesOption{
- Label: charts.SeriesLabel{
- Show: true,
- },
- Radius: "35%",
- }),
- },
- // 雷达图
- {
- Title: charts.TitleOption{
- Text: "Basic Radar Chart",
- },
- Legend: charts.NewLegendOption([]string{
- "Allocated Budget",
- "Actual Spending",
- }),
- RadarIndicators: []charts.RadarIndicator{
- {
- Name: "Sales",
- Max: 6500,
- },
- {
- Name: "Administration",
- Max: 16000,
- },
- {
- Name: "Information Technology",
- Max: 30000,
- },
- {
- Name: "Customer Support",
- Max: 38000,
- },
- {
- Name: "Development",
- Max: 52000,
- },
- {
- Name: "Marketing",
- Max: 25000,
- },
- },
- SeriesList: charts.SeriesList{
- {
- Type: charts.ChartTypeRadar,
- Data: charts.NewSeriesDataFromValues([]float64{
- 4200,
- 3000,
- 20000,
- 35000,
- 50000,
- 18000,
- }),
- },
- {
- Type: charts.ChartTypeRadar,
- Data: charts.NewSeriesDataFromValues([]float64{
- 5000,
- 14000,
- 28000,
- 26000,
- 42000,
- 21000,
- }),
- },
- },
- },
- // 漏斗图
- {
- Title: charts.TitleOption{
- Text: "Funnel",
- },
- Legend: charts.NewLegendOption([]string{
- "Show",
- "Click",
- "Visit",
- "Inquiry",
- "Order",
- }),
- SeriesList: []charts.Series{
- {
- Type: charts.ChartTypeFunnel,
- Name: "Visit",
- Data: charts.NewSeriesDataFromValues([]float64{
- 60,
- }),
- },
- {
- Type: charts.ChartTypeFunnel,
- Name: "Inquiry",
- Data: charts.NewSeriesDataFromValues([]float64{
- 40,
- }),
- },
- {
- Type: charts.ChartTypeFunnel,
- Name: "Order",
- Data: charts.NewSeriesDataFromValues([]float64{
- 20,
- }),
- },
- {
- Type: charts.ChartTypeFunnel,
- Name: "Click",
- Data: charts.NewSeriesDataFromValues([]float64{
- 80,
- }),
- },
- {
- Type: charts.ChartTypeFunnel,
- Name: "Show",
- Data: charts.NewSeriesDataFromValues([]float64{
- 100,
- }),
- },
- },
- },
- // 多图展示
- {
- Legend: charts.LegendOption{
- Top: "-90",
- Data: []string{
- "Milk Tea",
- "Matcha Latte",
- "Cheese Cocoa",
- "Walnut Brownie",
- },
- },
- Padding: chart.Box{
- Top: 100,
- Right: 10,
- Bottom: 10,
- Left: 10,
- },
- XAxis: charts.NewXAxisOption([]string{
- "2012",
- "2013",
- "2014",
- "2015",
- "2016",
- "2017",
- }),
- YAxisList: []charts.YAxisOption{
- {
-
- Min: charts.NewFloatPoint(0),
- Max: charts.NewFloatPoint(90),
- },
- },
- SeriesList: []charts.Series{
- charts.NewSeriesFromValues([]float64{
- 56.5,
- 82.1,
- 88.7,
- 70.1,
- 53.4,
- 85.1,
- }),
- charts.NewSeriesFromValues([]float64{
- 51.1,
- 51.4,
- 55.1,
- 53.3,
- 73.8,
- 68.7,
- }),
- charts.NewSeriesFromValues([]float64{
- 40.1,
- 62.2,
- 69.5,
- 36.4,
- 45.2,
- 32.5,
- }, charts.ChartTypeBar),
- charts.NewSeriesFromValues([]float64{
- 25.2,
- 37.1,
- 41.2,
- 18,
- 33.9,
- 49.1,
- }, charts.ChartTypeBar),
- },
- Children: []charts.ChartOption{
- {
- Legend: charts.LegendOption{
- Show: charts.FalseFlag(),
- Data: []string{
- "Milk Tea",
- "Matcha Latte",
- "Cheese Cocoa",
- "Walnut Brownie",
- },
- },
- Box: chart.Box{
- Top: 20,
- Left: 400,
- Right: 500,
- Bottom: 120,
- },
- SeriesList: charts.NewPieSeriesList([]float64{
- 435.9,
- 354.3,
- 285.9,
- 204.5,
- }, charts.PieSeriesOption{
- Label: charts.SeriesLabel{
- Show: true,
- },
- Radius: "35%",
- }),
- },
- },
- },
- }
- handler(w, req, chartOptions, nil)
-}
-
-func echartsHandler(w http.ResponseWriter, req *http.Request) {
- echartsOptions := []string{
- `{
- "xAxis": {
- "type": "category",
- "data": [
- "Mon",
- "Tue",
- "Wed",
- "Thu",
- "Fri",
- "Sat",
- "Sun"
- ]
- },
- "yAxis": {
- "type": "value"
- },
- "series": [
- {
- "data": [
- 150,
- 230,
- 224,
- 218,
- 135,
- 147,
- 260
- ],
- "type": "line"
- }
- ]
- }`,
- `{
- "title": {
- "text": "Multiple Line"
- },
- "tooltip": {
- "trigger": "axis"
- },
- "legend": {
- "left": "right",
- "data": [
- "Email",
- "Union Ads",
- "Video Ads",
- "Direct",
- "Search Engine"
- ]
- },
- "grid": {
- "left": "3%",
- "right": "4%",
- "bottom": "3%",
- "containLabel": true
- },
- "toolbox": {
- "feature": {
- "saveAsImage": {}
- }
- },
- "xAxis": {
- "type": "category",
- "boundaryGap": false,
- "data": [
- "Mon",
- "Tue",
- "Wed",
- "Thu",
- "Fri",
- "Sat",
- "Sun"
- ]
- },
- "yAxis": {
- "type": "value"
- },
- "series": [
- {
- "name": "Email",
- "type": "line",
- "data": [
- 120,
- 132,
- 101,
- 134,
- 90,
- 230,
- 210
- ]
- },
- {
- "name": "Union Ads",
- "type": "line",
- "data": [
- 220,
- 182,
- 191,
- 234,
- 290,
- 330,
- 310
- ]
- },
- {
- "name": "Video Ads",
- "type": "line",
- "data": [
- 150,
- 232,
- 201,
- 154,
- 190,
- 330,
- 410
- ]
- },
- {
- "name": "Direct",
- "type": "line",
- "data": [
- 320,
- 332,
- 301,
- 334,
- 390,
- 330,
- 320
- ]
- },
- {
- "name": "Search Engine",
- "type": "line",
- "data": [
- 820,
- 932,
- 901,
- 934,
- 1290,
- 1330,
- 1320
- ]
- }
- ]
- }`,
- `{
- "title": {
- "text": "Temperature Change in the Coming Week"
- },
- "legend": {
- "left": "right"
- },
- "padding": [10, 30, 10, 10],
- "xAxis": {
- "type": "category",
- "boundaryGap": false,
- "data": [
- "Mon",
- "Tue",
- "Wed",
- "Thu",
- "Fri",
- "Sat",
- "Sun"
- ]
- },
- "yAxis": {
- "axisLabel": {
- "formatter": "{value} °C"
- }
- },
- "series": [
- {
- "name": "Highest",
- "type": "line",
- "data": [
- 10,
- 11,
- 13,
- 11,
- 12,
- 12,
- 9
- ],
- "markPoint": {
- "data": [
- {
- "type": "max"
- },
- {
- "type": "min"
- }
- ]
- },
- "markLine": {
- "data": [
- {
- "type": "average"
- }
- ]
- }
- },
- {
- "name": "Lowest",
- "type": "line",
- "data": [
- 1,
- -2,
- 2,
- 5,
- 3,
- 2,
- 0
- ],
- "markPoint": {
- "data": [
- {
- "type": "min"
- }
- ]
- },
- "markLine": {
- "data": [
- {
- "type": "average"
- },
- {
- "type": "max"
- }
- ]
- }
- }
- ]
- }`,
- `{
- "xAxis": {
- "type": "category",
- "data": [
- "Mon",
- "Tue",
- "Wed",
- "Thu",
- "Fri",
- "Sat",
- "Sun"
- ]
- },
- "yAxis": {
- "type": "value"
- },
- "series": [
- {
- "data": [
- 120,
- 200,
- 150,
- 80,
- 70,
- 110,
- 130
- ],
- "type": "bar"
- }
- ]
- }`,
- `{
- "xAxis": {
- "type": "category",
- "data": [
- "Mon",
- "Tue",
- "Wed",
- "Thu",
- "Fri",
- "Sat",
- "Sun"
- ]
- },
- "yAxis": {
- "type": "value"
- },
- "series": [
- {
- "data": [
- 120,
- {
- "value": 200,
- "itemStyle": {
- "color": "#a90000"
- }
- },
- 150,
- 80,
- 70,
- 110,
- 130
- ],
- "type": "bar"
- }
- ]
- }`,
- `{
- "title": {
- "text": "Rainfall vs Evaporation",
- "subtext": "Fake Data"
- },
- "legend": {
- "data": [
- "Rainfall",
- "Evaporation"
- ]
- },
- "padding": [10, 30, 10, 10],
- "xAxis": [
- {
- "type": "category",
- "data": [
- "Jan",
- "Feb",
- "Mar",
- "Apr",
- "May",
- "Jun",
- "Jul",
- "Aug",
- "Sep",
- "Oct",
- "Nov",
- "Dec"
- ]
- }
- ],
- "series": [
- {
- "name": "Rainfall",
- "type": "bar",
- "data": [
- 2,
- 4.9,
- 7,
- 23.2,
- 25.6,
- 76.7,
- 135.6,
- 162.2,
- 32.6,
- 20,
- 6.4,
- 3.3
- ],
- "markPoint": {
- "data": [
- {
- "type": "max"
- },
- {
- "type": "min"
- }
- ]
- },
- "markLine": {
- "data": [
- {
- "type": "average"
- }
- ]
- }
- },
- {
- "name": "Evaporation",
- "type": "bar",
- "data": [
- 2.6,
- 5.9,
- 9,
- 26.4,
- 28.7,
- 70.7,
- 175.6,
- 182.2,
- 48.7,
- 18.8,
- 6,
- 2.3
- ],
- "markPoint": {
- "data": [
- {
- "type": "max"
- },
- {
- "type": "min"
- }
- ]
- },
- "markLine": {
- "data": [
- {
- "type": "average"
- }
- ]
- }
- }
- ]
- }`,
- `{
- "legend": {
- "data": [
- "Evaporation",
- "Precipitation",
- "Temperature"
- ]
- },
- "xAxis": [
- {
- "type": "category",
- "data": [
- "Mon",
- "Tue",
- "Wed",
- "Thu",
- "Fri",
- "Sat",
- "Sun"
- ],
- "axisPointer": {
- "type": "shadow"
- }
- }
- ],
- "yAxis": [
- {
- "type": "value",
- "name": "Precipitation",
- "min": 0,
- "max": 240,
- "axisLabel": {
- "formatter": "{value} ml"
- }
- },
- {
- "type": "value",
- "name": "Temperature",
- "min": 0,
- "max": 24,
- "axisLabel": {
- "formatter": "{value} °C"
- }
- }
- ],
- "series": [
- {
- "name": "Evaporation",
- "type": "bar",
- "tooltip": {},
- "data": [
- 2,
- 4.9,
- 7,
- 23.2,
- 25.6,
- 76.7,
- 135.6,
- 162.2,
- 32.6,
- 20,
- 6.4,
- 3.3
- ]
- },
- {
- "name": "Precipitation",
- "type": "bar",
- "tooltip": {},
- "data": [
- 2.6,
- 5.9,
- 9,
- 26.4,
- 28.7,
- 70.7,
- 175.6,
- 182.2,
- 48.7,
- 18.8,
- 6,
- 2.3
- ]
- },
- {
- "name": "Temperature",
- "type": "line",
- "yAxisIndex": 1,
- "tooltip": {},
- "data": [
- 2,
- 2.2,
- 3.3,
- 4.5,
- 6.3,
- 10.2,
- 20.3,
- 23.4,
- 23,
- 16.5,
- 12,
- 6.2
- ]
- }
- ]
- }`,
- `{
- "tooltip": {
- "trigger": "axis",
- "axisPointer": {
- "type": "cross"
- }
- },
- "grid": {
- "right": "20%"
- },
- "toolbox": {
- "feature": {
- "dataView": {
- "show": true,
- "readOnly": false
- },
- "restore": {
- "show": true
- },
- "saveAsImage": {
- "show": true
- }
- }
- },
- "legend": {
- "data": [
- "Evaporation",
- "Precipitation",
- "Temperature"
- ]
- },
- "xAxis": [
- {
- "type": "category",
- "axisTick": {
- "alignWithLabel": true
- },
- "data": [
- "Jan",
- "Feb",
- "Mar",
- "Apr",
- "May",
- "Jun",
- "Jul",
- "Aug",
- "Sep",
- "Oct",
- "Nov",
- "Dec"
- ]
- }
- ],
- "yAxis": [
- {
- "type": "value",
- "name": "温度",
- "position": "left",
- "alignTicks": true,
- "axisLine": {
- "show": true,
- "lineStyle": {
- "color": "#EE6666"
- }
- },
- "axisLabel": {
- "formatter": "{value} °C"
- }
- },
- {
- "type": "value",
- "name": "Evaporation",
- "position": "right",
- "alignTicks": true,
- "axisLine": {
- "show": true,
- "lineStyle": {
- "color": "#5470C6"
- }
- },
- "axisLabel": {
- "formatter": "{value} ml"
- }
- }
- ],
- "series": [
- {
- "name": "Evaporation",
- "type": "bar",
- "yAxisIndex": 1,
- "data": [
- 2,
- 4.9,
- 7,
- 23.2,
- 25.6,
- 76.7,
- 135.6,
- 162.2,
- 32.6,
- 20,
- 6.4,
- 3.3
- ]
- },
- {
- "name": "Precipitation",
- "type": "bar",
- "yAxisIndex": 1,
- "data": [
- 2.6,
- 5.9,
- 9,
- 26.4,
- 28.7,
- 70.7,
- 175.6,
- 182.2,
- 48.7,
- 18.8,
- 6,
- 2.3
- ]
- },
- {
- "name": "Temperature",
- "type": "line",
- "data": [
- 2,
- 2.2,
- 3.3,
- 4.5,
- 6.3,
- 10.2,
- 20.3,
- 23.4,
- 23,
- 16.5,
- 12,
- 6.2
- ]
- }
- ]
- }`,
- `{
- "title": {
- "text": "Referer of a Website",
- "subtext": "Fake Data",
- "left": "center"
- },
- "tooltip": {
- "trigger": "item"
- },
- "legend": {
- "orient": "vertical",
- "left": "left"
- },
- "series": [
- {
- "name": "Access From",
- "type": "pie",
- "radius": "50%",
- "data": [
- {
- "value": 1048,
- "name": "Search Engine"
- },
- {
- "value": 735,
- "name": "Direct"
- },
- {
- "value": 580,
- "name": "Email"
- },
- {
- "value": 484,
- "name": "Union Ads"
- },
- {
- "value": 300,
- "name": "Video Ads"
- }
- ]
- }
- ]
- }`,
- `{
- "title": {
- "text": "Rainfall"
- },
- "padding": [10, 10, 10, 30],
- "legend": {
- "data": [
- "GZ",
- "SH"
- ]
- },
- "xAxis": {
- "type": "category",
- "splitNumber": 6,
- "data": [
- "01-01",
- "01-02",
- "01-03",
- "01-04",
- "01-05",
- "01-06",
- "01-07",
- "01-08",
- "01-09",
- "01-10",
- "01-11",
- "01-12",
- "01-13",
- "01-14",
- "01-15",
- "01-16",
- "01-17",
- "01-18",
- "01-19",
- "01-20",
- "01-21",
- "01-22",
- "01-23",
- "01-24",
- "01-25",
- "01-26",
- "01-27",
- "01-28",
- "01-29",
- "01-30",
- "01-31"
- ]
- },
- "yAxis": {
- "axisLabel": {
- "formatter": "{value} mm"
- }
- },
- "series": [
- {
- "type": "bar",
- "data": [
- 928,
- 821,
- 889,
- 600,
- 547,
- 783,
- 197,
- 853,
- 430,
- 346,
- 63,
- 465,
- 309,
- 334,
- 141,
- 538,
- 792,
- 58,
- 922,
- 807,
- 298,
- 243,
- 744,
- 885,
- 812,
- 231,
- 330,
- 220,
- 984,
- 221,
- 429
- ]
- },
- {
- "type": "bar",
- "data": [
- 749,
- 201,
- 296,
- 579,
- 255,
- 159,
- 902,
- 246,
- 149,
- 158,
- 507,
- 776,
- 186,
- 79,
- 390,
- 222,
- 601,
- 367,
- 221,
- 411,
- 714,
- 620,
- 966,
- 73,
- 203,
- 631,
- 833,
- 610,
- 487,
- 677,
- 596
- ]
- }
- ]
- }`,
- `{
- "title": {
- "text": "Basic Radar Chart"
- },
- "legend": {
- "data": [
- "Allocated Budget",
- "Actual Spending"
- ]
- },
- "radar": {
- "indicator": [
- {
- "name": "Sales",
- "max": 6500
- },
- {
- "name": "Administration",
- "max": 16000
- },
- {
- "name": "Information Technology",
- "max": 30000
- },
- {
- "name": "Customer Support",
- "max": 38000
- },
- {
- "name": "Development",
- "max": 52000
- },
- {
- "name": "Marketing",
- "max": 25000
- }
- ]
- },
- "series": [
- {
- "name": "Budget vs spending",
- "type": "radar",
- "data": [
- {
- "value": [
- 4200,
- 3000,
- 20000,
- 35000,
- 50000,
- 18000
- ],
- "name": "Allocated Budget"
- },
- {
- "value": [
- 5000,
- 14000,
- 28000,
- 26000,
- 42000,
- 21000
- ],
- "name": "Actual Spending"
- }
- ]
- }
- ]
- }`,
- `{
- "title": {
- "text": "Funnel"
- },
- "tooltip": {
- "trigger": "item",
- "formatter": "{a}
{b} : {c}%"
- },
- "toolbox": {
- "feature": {
- "dataView": {
- "readOnly": false
- },
- "restore": {},
- "saveAsImage": {}
- }
- },
- "legend": {
- "data": [
- "Show",
- "Click",
- "Visit",
- "Inquiry",
- "Order"
- ]
- },
- "series": [
- {
- "name": "Funnel",
- "type": "funnel",
- "left": "10%",
- "top": 60,
- "bottom": 60,
- "width": "80%",
- "min": 0,
- "max": 100,
- "minSize": "0%",
- "maxSize": "100%",
- "sort": "descending",
- "gap": 2,
- "label": {
- "show": true,
- "position": "inside"
- },
- "labelLine": {
- "length": 10,
- "lineStyle": {
- "width": 1,
- "type": "solid"
- }
- },
- "itemStyle": {
- "borderColor": "#fff",
- "borderWidth": 1
- },
- "emphasis": {
- "label": {
- "fontSize": 20
- }
- },
- "data": [
- {
- "value": 60,
- "name": "Visit"
- },
- {
- "value": 40,
- "name": "Inquiry"
- },
- {
- "value": 20,
- "name": "Order"
- },
- {
- "value": 80,
- "name": "Click"
- },
- {
- "value": 100,
- "name": "Show"
- }
- ]
- }
- ]
- }`,
- `{
- "legend": {
- "top": "-140",
- "data": [
- "Milk Tea",
- "Matcha Latte",
- "Cheese Cocoa",
- "Walnut Brownie"
- ]
- },
- "padding": [
- 150,
- 10,
- 10,
- 10
- ],
- "xAxis": [
- {
- "data": [
- "2012",
- "2013",
- "2014",
- "2015",
- "2016",
- "2017"
- ]
- }
- ],
- "series": [
- {
- "data": [
- 56.5,
- 82.1,
- 88.7,
- 70.1,
- 53.4,
- 85.1
- ]
- },
- {
- "data": [
- 51.1,
- 51.4,
- 55.1,
- 53.3,
- 73.8,
- 68.7
- ]
- },
- {
- "data": [
- 40.1,
- 62.2,
- 69.5,
- 36.4,
- 45.2,
- 32.5
- ]
- },
- {
- "data": [
- 25.2,
- 37.1,
- 41.2,
- 18,
- 33.9,
- 49.1
- ]
- }
- ],
- "children": [
- {
- "box": {
- "left": 0,
- "top": 30,
- "right": 600,
- "bottom": 150
- },
- "legend": {
- "show": false
- },
- "series": [
- {
- "type": "pie",
- "radius": "50%",
- "data": [
- {
- "value": 435.9,
- "name": "Milk Tea"
- },
- {
- "value": 354.3,
- "name": "Matcha Latte"
- },
- {
- "value": 285.9,
- "name": "Cheese Cocoa"
- },
- {
- "value": 204.5,
- "name": "Walnut Brownie"
- }
- ]
- }
- ]
- }
- ]
- }`,
- }
- handler(w, req, nil, echartsOptions)
-}
-
-func main() {
- http.HandleFunc("/", indexHandler)
- http.HandleFunc("/echarts", echartsHandler)
- http.ListenAndServe(":3012", nil)
-}
diff --git a/funnel.go b/funnel.go
deleted file mode 100644
index f083306..0000000
--- a/funnel.go
+++ /dev/null
@@ -1,141 +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 (
- "fmt"
- "sort"
-
- "github.com/dustin/go-humanize"
- "github.com/golang/freetype/truetype"
- "github.com/wcharczuk/go-chart/v2"
-)
-
-type funnelChartOption struct {
- Theme string
- Font *truetype.Font
- SeriesList SeriesList
-}
-
-func funnelChartRender(opt funnelChartOption, result *basicRenderResult) error {
- d, err := NewDraw(DrawOption{
- Parent: result.d,
- }, PaddingOption(chart.Box{
- Top: result.titleBox.Height(),
- }))
- if err != nil {
- return err
- }
- seriesList := make([]Series, len(opt.SeriesList))
- copy(seriesList, opt.SeriesList)
- sort.Slice(seriesList, func(i, j int) bool {
- // 大的数据在前
- return seriesList[i].Data[0].Value > seriesList[j].Data[0].Value
- })
- max := seriesList[0].Data[0].Value
- min := float64(0)
- for _, item := range seriesList {
- if item.Max != nil {
- max = *item.Max
- }
- if item.Min != nil {
- min = *item.Min
- }
- }
-
- theme := NewTheme(opt.Theme)
- gap := 2
- height := d.Box.Height()
- width := d.Box.Width()
- count := len(seriesList)
-
- h := (height - gap*(count-1)) / count
-
- y := 0
- widthList := make([]int, len(seriesList))
- textList := make([]string, len(seriesList))
- for index, item := range seriesList {
- value := item.Data[0].Value
- widthPercent := (value - min) / (max - min)
- w := int(widthPercent * float64(width))
- widthList[index] = w
- p := humanize.CommafWithDigits(value/max*100, 2) + "%"
- textList[index] = fmt.Sprintf("%s(%s)", item.Name, p)
- }
-
- for index, w := range widthList {
- series := seriesList[index]
- nextWidth := 0
- if index+1 < len(widthList) {
- nextWidth = widthList[index+1]
- }
- topStartX := (width - w) >> 1
- topEndX := topStartX + w
- bottomStartX := (width - nextWidth) >> 1
- bottomEndX := bottomStartX + nextWidth
- points := []Point{
- {
- X: topStartX,
- Y: y,
- },
- {
- X: topEndX,
- Y: y,
- },
- {
- X: bottomEndX,
- Y: y + h,
- },
- {
- X: bottomStartX,
- Y: y + h,
- },
- {
- X: topStartX,
- Y: y,
- },
- }
- color := theme.GetSeriesColor(series.index)
- d.fill(points, chart.Style{
- FillColor: color,
- })
-
- // 文本
- text := textList[index]
- r := d.Render
- textStyle := chart.Style{
- FontColor: theme.GetTextColor(),
- FontSize: labelFontSize,
- Font: opt.Font,
- }
- textStyle.GetTextOptions().WriteToRenderer(r)
- textBox := r.MeasureText(text)
- textX := width>>1 - textBox.Width()>>1
- textY := y + h>>1
- d.text(text, textX, textY)
-
- y += (h + gap)
- }
-
- return nil
-}
diff --git a/funnel_test.go b/funnel_test.go
deleted file mode 100644
index 530fa53..0000000
--- a/funnel_test.go
+++ /dev/null
@@ -1,91 +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 (
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/wcharczuk/go-chart/v2"
-)
-
-func TestFunnelChartRender(t *testing.T) {
- assert := assert.New(t)
-
- d, err := NewDraw(DrawOption{
- Width: 250,
- Height: 150,
- })
- assert.Nil(err)
- f, _ := chart.GetDefaultFont()
- err = funnelChartRender(funnelChartOption{
- Font: f,
- SeriesList: []Series{
- {
- Type: ChartTypeFunnel,
- Name: "Visit",
- Data: NewSeriesDataFromValues([]float64{
- 60,
- }),
- },
- {
- Type: ChartTypeFunnel,
- Name: "Inquiry",
- Data: NewSeriesDataFromValues([]float64{
- 40,
- }),
- index: 1,
- },
- {
- Type: ChartTypeFunnel,
- Name: "Order",
- Data: NewSeriesDataFromValues([]float64{
- 20,
- }),
- index: 2,
- },
- {
- Type: ChartTypeFunnel,
- Name: "Click",
- Data: NewSeriesDataFromValues([]float64{
- 80,
- }),
- index: 3,
- },
- {
- Type: ChartTypeFunnel,
- Name: "Show",
- Data: NewSeriesDataFromValues([]float64{
- 100,
- }),
- index: 4,
- },
- },
- }, &basicRenderResult{
- d: d,
- })
- assert.Nil(err)
- data, err := d.Bytes()
- assert.Nil(err)
- assert.Equal("", string(data))
-}
diff --git a/legend.go b/legend.go
deleted file mode 100644
index df72757..0000000
--- a/legend.go
+++ /dev/null
@@ -1,226 +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 (
- "strconv"
- "strings"
-
- "github.com/wcharczuk/go-chart/v2"
-)
-
-type LegendOption struct {
- theme string
- // Legend show flag, if nil or true, the legend will be shown
- Show *bool
- // Legend text style
- Style chart.Style
- // Text array of legend
- Data []string
- // Distance between legend component and the left side of the container.
- // It can be pixel value: 20, percentage value: 20%,
- // or position value: right, center.
- Left string
- // Distance between legend component and the top side of the container.
- // It can be pixel value: 20.
- Top string
- // Legend marker and text aligning, it can be left or right, default is left.
- Align string
- // The layout orientation of legend, it can be horizontal or vertical, default is horizontal.
- Orient string
- // Icon of the legend.
- Icon string
-}
-
-const (
- LegendIconRect = "rect"
-)
-
-// NewLegendOption creates a new legend option by legend text list
-func NewLegendOption(data []string, position ...string) LegendOption {
- opt := LegendOption{
- Data: data,
- }
- if len(position) != 0 {
- opt.Left = position[0]
- }
- return opt
-}
-
-type legend struct {
- d *Draw
- opt *LegendOption
-}
-
-func NewLegend(d *Draw, opt LegendOption) *legend {
- return &legend{
- d: d,
- opt: &opt,
- }
-}
-
-func (l *legend) Render() (chart.Box, error) {
- d := l.d
- opt := l.opt
- if len(opt.Data) == 0 || isFalse(opt.Show) {
- return chart.BoxZero, nil
- }
- theme := NewTheme(opt.theme)
- padding := opt.Style.Padding
- legendDraw, err := NewDraw(DrawOption{
- Parent: d,
- }, PaddingOption(padding))
- if err != nil {
- return chart.BoxZero, err
- }
- r := legendDraw.Render
- opt.Style.GetTextOptions().WriteToRenderer(r)
-
- x := 0
- y := 0
- top := 0
- // TODO TOP 暂只支持数值
- if opt.Top != "" {
- top, _ = strconv.Atoi(opt.Top)
- y += top
- }
- legendWidth := 30
- legendDotHeight := 5
- textPadding := 5
- legendMargin := 10
- // 往下移2倍dot的高度
- y += 2 * legendDotHeight
-
- widthCount := 0
- maxTextWidth := 0
- // 文本宽度
- for _, text := range opt.Data {
- b := r.MeasureText(text)
- if b.Width() > maxTextWidth {
- maxTextWidth = b.Width()
- }
- widthCount += b.Width()
- }
- if opt.Orient == OrientVertical {
- widthCount = maxTextWidth + legendWidth + textPadding
- } else {
- // 加上标记
- widthCount += legendWidth * len(opt.Data)
- // 文本的padding
- widthCount += 2 * textPadding * len(opt.Data)
- // margin的宽度
- widthCount += legendMargin * (len(opt.Data) - 1)
- }
-
- left := 0
- switch opt.Left {
- case PositionRight:
- left = legendDraw.Box.Width() - widthCount
- case PositionCenter:
- left = (legendDraw.Box.Width() - widthCount) >> 1
- default:
- if strings.HasSuffix(opt.Left, "%") {
- value, _ := strconv.Atoi(strings.ReplaceAll(opt.Left, "%", ""))
- left = legendDraw.Box.Width() * value / 100
- } else {
- value, _ := strconv.Atoi(opt.Left)
- left = value
- }
- }
- x = left
- for index, text := range opt.Data {
- textBox := r.MeasureText(text)
- var renderText func()
- if opt.Orient == OrientVertical {
- // 垂直
- // 重置x的位置
- x = left
- renderText = func() {
- x += textPadding
- legendDraw.text(text, x, y+legendDotHeight)
- x += textBox.Width()
- y += (2*legendDotHeight + legendMargin)
- }
-
- } else {
- // 水平
- if index != 0 {
- x += legendMargin
- }
- renderText = func() {
- x += textPadding
- legendDraw.text(text, x, y+legendDotHeight)
- x += textBox.Width()
- x += textPadding
- }
- }
- if opt.Align == PositionRight {
- renderText()
- }
- seriesColor := theme.GetSeriesColor(index)
- fillColor := seriesColor
- if !theme.IsDark() {
- fillColor = theme.GetBackgroundColor()
- }
- style := chart.Style{
- StrokeColor: seriesColor,
- FillColor: fillColor,
- StrokeWidth: 3,
- }
- if opt.Icon == LegendIconRect {
- style.FillColor = seriesColor
- style.StrokeWidth = 1
- }
- style.GetFillAndStrokeOptions().WriteDrawingOptionsToRenderer(r)
-
- if opt.Icon == LegendIconRect {
- legendDraw.moveTo(x, y-legendDotHeight)
- legendDraw.lineTo(x+legendWidth, y-legendDotHeight)
- legendDraw.lineTo(x+legendWidth, y+legendDotHeight)
- legendDraw.lineTo(x, y+legendDotHeight)
- legendDraw.lineTo(x, y-legendDotHeight)
- r.FillStroke()
- } else {
- legendDraw.moveTo(x, y)
- legendDraw.lineTo(x+legendWidth, y)
- r.Stroke()
- legendDraw.circle(float64(legendDotHeight), x+legendWidth>>1, y)
- r.FillStroke()
- }
- x += legendWidth
-
- if opt.Align != PositionRight {
- renderText()
- }
- }
- legendBox := padding.Clone()
- // 计算展示区域
- if opt.Orient == OrientVertical {
- legendBox.Right = legendBox.Left + left + maxTextWidth + legendWidth + textPadding
- legendBox.Bottom = legendBox.Top + y
- } else {
- legendBox.Right = legendBox.Left + x
- legendBox.Bottom = legendBox.Top + 2*legendDotHeight + top + textPadding
- }
- return legendBox, nil
-}
diff --git a/legend_test.go b/legend_test.go
deleted file mode 100644
index c5d7e50..0000000
--- a/legend_test.go
+++ /dev/null
@@ -1,185 +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 (
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/wcharczuk/go-chart/v2"
- "github.com/wcharczuk/go-chart/v2/drawing"
-)
-
-func TestNewLegendOption(t *testing.T) {
- assert := assert.New(t)
-
- opt := NewLegendOption([]string{
- "a",
- "b",
- }, PositionRight)
- assert.Equal(LegendOption{
- Data: []string{
- "a",
- "b",
- },
- Left: PositionRight,
- }, opt)
-}
-
-func TestLegendRender(t *testing.T) {
- assert := assert.New(t)
-
- newDraw := func() *Draw {
- d, _ := NewDraw(DrawOption{
- Width: 400,
- Height: 300,
- })
- return d
- }
- style := chart.Style{
- FontSize: 10,
- FontColor: drawing.ColorBlack,
- }
- style.Font, _ = chart.GetDefaultFont()
-
- tests := []struct {
- newDraw func() *Draw
- newLegend func(*Draw) *legend
- box chart.Box
- result string
- }{
- {
- newDraw: newDraw,
- newLegend: func(d *Draw) *legend {
- return NewLegend(d, LegendOption{
- Top: "10",
- Data: []string{
- "Mon",
- "Tue",
- "Wed",
- },
- Style: style,
- })
- },
- result: "",
- box: chart.Box{
- Right: 214,
- Bottom: 25,
- },
- },
- {
- newDraw: newDraw,
- newLegend: func(d *Draw) *legend {
- return NewLegend(d, LegendOption{
- Top: "10",
- Left: PositionRight,
- Align: PositionRight,
- Data: []string{
- "Mon",
- "Tue",
- "Wed",
- },
- Style: style,
- })
- },
- result: "",
- box: chart.Box{
- Right: 400,
- Bottom: 25,
- },
- },
- {
- newDraw: newDraw,
- newLegend: func(d *Draw) *legend {
- return NewLegend(d, LegendOption{
- Top: "10",
- Left: PositionCenter,
- Data: []string{
- "Mon",
- "Tue",
- "Wed",
- },
- Style: style,
- })
- },
- result: "",
- box: chart.Box{
- Right: 307,
- Bottom: 25,
- },
- },
- {
- newDraw: newDraw,
- newLegend: func(d *Draw) *legend {
- return NewLegend(d, LegendOption{
- Top: "10",
- Left: PositionLeft,
- Data: []string{
- "Mon",
- "Tue",
- "Wed",
- },
- Style: style,
- Orient: OrientVertical,
- })
- },
- result: "",
- box: chart.Box{
- Right: 61,
- Bottom: 80,
- },
- },
- {
- newDraw: newDraw,
- newLegend: func(d *Draw) *legend {
- return NewLegend(d, LegendOption{
- Top: "10",
- Left: "10%",
- Data: []string{
- "Mon",
- "Tue",
- "Wed",
- },
- Style: style,
- Orient: OrientVertical,
- })
- },
- box: chart.Box{
- Right: 101,
- Bottom: 80,
- },
- result: "",
- },
- }
-
- for _, tt := range tests {
- d := tt.newDraw()
- b, err := tt.newLegend(d).Render()
- assert.Nil(err)
- assert.Equal(tt.box, b)
- data, err := d.Bytes()
- assert.Nil(err)
- assert.NotEmpty(data)
- assert.Equal(tt.result, string(data))
- }
-}
diff --git a/line.go b/line.go
index 15ab575..e4b1f18 100644
--- a/line.go
+++ b/line.go
@@ -22,10 +22,6 @@
package charts
-import (
- "github.com/wcharczuk/go-chart/v2"
-)
-
type LineStyle struct {
ClassName string
StrokeDashArray []float64
@@ -37,8 +33,8 @@ type LineStyle struct {
DotFillColor Color
}
-func (ls *LineStyle) Style() chart.Style {
- return chart.Style{
+func (ls *LineStyle) Style() Style {
+ return Style{
ClassName: ls.ClassName,
StrokeDashArray: ls.StrokeDashArray,
StrokeColor: ls.StrokeColor,
@@ -48,55 +44,3 @@ func (ls *LineStyle) Style() chart.Style {
DotColor: ls.DotColor,
}
}
-
-func (d *Draw) lineFill(points []Point, style LineStyle) {
- s := style.Style()
- if !(s.ShouldDrawStroke() && s.ShouldDrawFill()) {
- return
- }
-
- newPoints := make([]Point, len(points))
- copy(newPoints, points)
- x0 := points[0].X
- y0 := points[0].Y
- height := d.Box.Height()
- newPoints = append(newPoints, Point{
- X: points[len(points)-1].X,
- Y: height,
- }, Point{
- X: x0,
- Y: height,
- }, Point{
- X: x0,
- Y: y0,
- })
- d.fill(newPoints, style.Style())
-}
-
-func (d *Draw) lineDot(points []Point, style LineStyle) {
- s := style.Style()
- if !s.ShouldDrawDot() {
- return
- }
- r := d.Render
- dotWith := s.GetDotWidth()
-
- s.GetDotOptions().WriteDrawingOptionsToRenderer(r)
- for _, point := range points {
- if !style.DotFillColor.IsZero() {
- r.SetFillColor(style.DotFillColor)
- }
- r.SetStrokeColor(s.DotColor)
- d.circle(dotWith, point.X, point.Y)
- r.FillStroke()
- }
-}
-
-func (d *Draw) Line(points []Point, style LineStyle) {
- if len(points) == 0 {
- return
- }
- d.lineFill(points, style)
- d.lineStroke(points, style)
- d.lineDot(points, style)
-}
diff --git a/line_chart.go b/line_chart.go
deleted file mode 100644
index ac9091c..0000000
--- a/line_chart.go
+++ /dev/null
@@ -1,131 +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"
- "github.com/wcharczuk/go-chart/v2"
- "github.com/wcharczuk/go-chart/v2/drawing"
-)
-
-type lineChartOption struct {
- Theme string
- SeriesList SeriesList
- Font *truetype.Font
-}
-
-func lineChartRender(opt lineChartOption, result *basicRenderResult) ([]markPointRenderOption, error) {
-
- theme := NewTheme(opt.Theme)
-
- d, err := NewDraw(DrawOption{
- Parent: result.d,
- }, PaddingOption(chart.Box{
- Top: result.titleBox.Height(),
- Left: YAxisWidth,
- }))
- if err != nil {
- return nil, err
- }
- seriesNames := opt.SeriesList.Names()
-
- r := d.Render
- xRange := result.xRange
- markPointRenderOptions := make([]markPointRenderOption, 0)
- for i, s := range opt.SeriesList {
- // 由于series是for range,为同一个数据,因此需要clone
- // 后续需要使用,如mark point
- series := s
- index := series.index
- if index == 0 {
- index = i
- }
- seriesColor := theme.GetSeriesColor(index)
-
- yRange := result.getYRange(series.YAxisIndex)
- points := make([]Point, 0, len(series.Data))
- // mark line
- markLineRender(markLineRenderOption{
- Draw: d,
- FillColor: seriesColor,
- FontColor: theme.GetTextColor(),
- StrokeColor: seriesColor,
- Font: opt.Font,
- Series: &series,
- Range: yRange,
- })
-
- for j, item := range series.Data {
- if j >= xRange.divideCount {
- continue
- }
- y := yRange.getRestHeight(item.Value)
- x := xRange.getWidth(float64(j))
- points = append(points, Point{
- Y: y,
- X: x,
- })
- 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(),
- FontSize: labelFontSize,
- Font: opt.Font,
- }
- if !series.Label.Color.IsZero() {
- labelStyle.FontColor = series.Label.Color
- }
- labelStyle.GetTextOptions().WriteToRenderer(r)
- textBox := r.MeasureText(text)
- d.text(text, x-textBox.Width()>>1, y-distance)
- }
-
- dotFillColor := drawing.ColorWhite
- if theme.IsDark() {
- dotFillColor = seriesColor
- }
- d.Line(points, LineStyle{
- StrokeColor: seriesColor,
- StrokeWidth: 2,
- DotColor: seriesColor,
- DotWidth: defaultDotWidth,
- DotFillColor: dotFillColor,
- })
- // draw mark point
- markPointRenderOptions = append(markPointRenderOptions, markPointRenderOption{
- Draw: d,
- FillColor: seriesColor,
- Font: opt.Font,
- Points: points,
- Series: &series,
- })
- }
-
- return markPointRenderOptions, nil
-}
diff --git a/line_chart_test.go b/line_chart_test.go
deleted file mode 100644
index 9f5d9af..0000000
--- a/line_chart_test.go
+++ /dev/null
@@ -1,97 +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 (
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/wcharczuk/go-chart/v2"
- "github.com/wcharczuk/go-chart/v2/drawing"
-)
-
-func TestLineChartRender(t *testing.T) {
- assert := assert.New(t)
-
- width := 400
- height := 300
- d, err := NewDraw(DrawOption{
- Width: width,
- Height: height,
- })
- assert.Nil(err)
-
- result := basicRenderResult{
- xRange: &Range{
- Min: 0,
- Max: 4,
- divideCount: 4,
- Size: width,
- Boundary: true,
- },
- yRangeList: []*Range{
- {
- divideCount: 6,
- Max: 100,
- Min: 0,
- Size: height,
- },
- },
- d: d,
- }
- f, _ := chart.GetDefaultFont()
- _, err = lineChartRender(lineChartOption{
- Font: f,
- SeriesList: SeriesList{
- {
- Label: SeriesLabel{
- Show: true,
- Color: drawing.ColorBlue,
- },
- MarkLine: NewMarkLine(
- SeriesMarkDataTypeAverage,
- ),
- Data: []SeriesData{
- {
- Value: 20,
- },
- {
- Value: 60,
- },
- {
- Value: 90,
- },
- },
- },
- NewSeriesFromValues([]float64{
- 40,
- 60,
- 70,
- }),
- },
- }, &result)
- assert.Nil(err)
- data, err := d.Bytes()
- assert.Nil(err)
- assert.Equal("", string(data))
-}
diff --git a/line_test.go b/line_test.go
deleted file mode 100644
index e10b806..0000000
--- a/line_test.go
+++ /dev/null
@@ -1,165 +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 (
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/wcharczuk/go-chart/v2"
- "github.com/wcharczuk/go-chart/v2/drawing"
-)
-
-func TestLineStyle(t *testing.T) {
- assert := assert.New(t)
-
- ls := LineStyle{
- ClassName: "test",
- StrokeDashArray: []float64{
- 1.0,
- },
- StrokeColor: drawing.ColorBlack,
- StrokeWidth: 1,
- FillColor: drawing.ColorBlack.WithAlpha(60),
- DotWidth: 2,
- DotColor: drawing.ColorBlack,
- DotFillColor: drawing.ColorWhite,
- }
-
- assert.Equal(chart.Style{
- ClassName: "test",
- StrokeDashArray: []float64{
- 1.0,
- },
- StrokeColor: drawing.ColorBlack,
- StrokeWidth: 1,
- FillColor: drawing.ColorBlack.WithAlpha(60),
- DotWidth: 2,
- DotColor: drawing.ColorBlack,
- }, ls.Style())
-}
-
-func TestDrawLineFill(t *testing.T) {
- assert := assert.New(t)
-
- ls := LineStyle{
- StrokeColor: drawing.ColorBlack,
- StrokeWidth: 1,
- FillColor: drawing.ColorBlack.WithAlpha(60),
- DotWidth: 2,
- DotColor: drawing.ColorBlack,
- DotFillColor: drawing.ColorWhite,
- }
- d, err := NewDraw(DrawOption{
- Width: 400,
- Height: 300,
- })
- assert.Nil(err)
- d.lineFill([]Point{
- {
- X: 0,
- Y: 0,
- },
- {
- X: 10,
- Y: 20,
- },
- {
- X: 50,
- Y: 60,
- },
- }, ls)
- data, err := d.Bytes()
- assert.Nil(err)
- assert.Equal("", string(data))
-}
-
-func TestDrawLineDot(t *testing.T) {
- assert := assert.New(t)
-
- ls := LineStyle{
- StrokeColor: drawing.ColorBlack,
- StrokeWidth: 1,
- FillColor: drawing.ColorBlack.WithAlpha(60),
- DotWidth: 2,
- DotColor: drawing.ColorBlack,
- DotFillColor: drawing.ColorWhite,
- }
- d, err := NewDraw(DrawOption{
- Width: 400,
- Height: 300,
- })
- assert.Nil(err)
- d.lineDot([]Point{
- {
- X: 0,
- Y: 0,
- },
- {
- X: 10,
- Y: 20,
- },
- {
- X: 50,
- Y: 60,
- },
- }, ls)
- data, err := d.Bytes()
- assert.Nil(err)
- assert.Equal("", string(data))
-}
-
-func TestDrawLine(t *testing.T) {
- assert := assert.New(t)
-
- ls := LineStyle{
- StrokeColor: drawing.ColorBlack,
- StrokeWidth: 1,
- FillColor: drawing.ColorBlack.WithAlpha(60),
- DotWidth: 2,
- DotColor: drawing.ColorBlack,
- DotFillColor: drawing.ColorWhite,
- }
- d, err := NewDraw(DrawOption{
- Width: 400,
- Height: 300,
- })
- assert.Nil(err)
- d.Line([]Point{
- {
- X: 0,
- Y: 0,
- },
- {
- X: 10,
- Y: 20,
- },
- {
- X: 50,
- Y: 60,
- },
- }, ls)
- data, err := d.Bytes()
- assert.Nil(err)
- assert.Equal("", string(data))
-}
diff --git a/mark_line.go b/mark_line.go
deleted file mode 100644
index 464fe71..0000000
--- a/mark_line.go
+++ /dev/null
@@ -1,92 +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"
- "github.com/wcharczuk/go-chart/v2"
- "github.com/wcharczuk/go-chart/v2/drawing"
-)
-
-func NewMarkLine(markLineTypes ...string) SeriesMarkLine {
- data := make([]SeriesMarkData, len(markLineTypes))
- for index, t := range markLineTypes {
- data[index] = SeriesMarkData{
- Type: t,
- }
- }
- return SeriesMarkLine{
- Data: data,
- }
-}
-
-type markLineRenderOption struct {
- Draw *Draw
- FillColor drawing.Color
- FontColor drawing.Color
- StrokeColor drawing.Color
- Font *truetype.Font
- Series *Series
- Range *Range
-}
-
-func markLineRender(opt markLineRenderOption) {
- d := opt.Draw
- s := opt.Series
- if len(s.MarkLine.Data) == 0 {
- return
- }
- r := d.Render
- summary := s.Summary()
- for _, markLine := range s.MarkLine.Data {
- // 由于mark line会修改style,因此每次重新设置
- chart.Style{
- FillColor: opt.FillColor,
- FontColor: opt.FontColor,
- FontSize: labelFontSize,
- StrokeColor: opt.StrokeColor,
- StrokeWidth: 1,
- Font: opt.Font,
- StrokeDashArray: []float64{
- 4,
- 2,
- },
- }.WriteToRenderer(r)
- value := float64(0)
- switch markLine.Type {
- case SeriesMarkDataTypeMax:
- value = summary.MaxValue
- case SeriesMarkDataTypeMin:
- value = summary.MinValue
- default:
- value = summary.AverageValue
- }
- y := opt.Range.getRestHeight(value)
- width := d.Box.Width()
- text := commafWithDigits(value)
- textBox := r.MeasureText(text)
- d.makeLine(0, y, width-2)
- d.text(text, width, y+textBox.Height()>>1-2)
- }
-
-}
diff --git a/mark_line_test.go b/mark_line_test.go
deleted file mode 100644
index abb3308..0000000
--- a/mark_line_test.go
+++ /dev/null
@@ -1,99 +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 (
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/wcharczuk/go-chart/v2"
- "github.com/wcharczuk/go-chart/v2/drawing"
-)
-
-func TestNewMarkLine(t *testing.T) {
- assert := assert.New(t)
-
- markLine := NewMarkLine(
- SeriesMarkDataTypeMax,
- SeriesMarkDataTypeMin,
- SeriesMarkDataTypeAverage,
- )
-
- assert.Equal(SeriesMarkLine{
- Data: []SeriesMarkData{
- {
- Type: SeriesMarkDataTypeMax,
- },
- {
- Type: SeriesMarkDataTypeMin,
- },
- {
- Type: SeriesMarkDataTypeAverage,
- },
- },
- }, markLine)
-}
-
-func TestMarkLineRender(t *testing.T) {
- assert := assert.New(t)
-
- d, err := NewDraw(DrawOption{
- Width: 400,
- Height: 300,
- }, PaddingOption(chart.Box{
- Left: 20,
- Right: 20,
- }))
- assert.Nil(err)
- f, _ := chart.GetDefaultFont()
-
- markLineRender(markLineRenderOption{
- Draw: d,
- FillColor: drawing.ColorBlack,
- FontColor: drawing.ColorBlack,
- StrokeColor: drawing.ColorBlack,
- Font: f,
- Series: &Series{
- MarkLine: NewMarkLine(
- SeriesMarkDataTypeMax,
- SeriesMarkDataTypeMin,
- SeriesMarkDataTypeAverage,
- ),
- Data: NewSeriesDataFromValues([]float64{
- 1,
- 3,
- 5,
- 7,
- 9,
- }),
- },
- Range: &Range{
- Min: 0,
- Max: 10,
- Size: 200,
- },
- })
- data, err := d.Bytes()
- assert.Nil(err)
- assert.Equal("", string(data))
-}
diff --git a/mark_point.go b/mark_point.go
deleted file mode 100644
index 5fd34c4..0000000
--- a/mark_point.go
+++ /dev/null
@@ -1,89 +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"
- "github.com/wcharczuk/go-chart/v2"
- "github.com/wcharczuk/go-chart/v2/drawing"
-)
-
-func NewMarkPoint(markPointTypes ...string) SeriesMarkPoint {
- data := make([]SeriesMarkData, len(markPointTypes))
- for index, t := range markPointTypes {
- data[index] = SeriesMarkData{
- Type: t,
- }
- }
- return SeriesMarkPoint{
- Data: data,
- }
-}
-
-type markPointRenderOption struct {
- Draw *Draw
- FillColor drawing.Color
- Font *truetype.Font
- Series *Series
- Points []Point
-}
-
-func markPointRender(opt markPointRenderOption) {
- d := opt.Draw
- s := opt.Series
- if len(s.MarkPoint.Data) == 0 {
- return
- }
- points := opt.Points
- summary := s.Summary()
- symbolSize := s.MarkPoint.SymbolSize
- if symbolSize == 0 {
- symbolSize = 30
- }
- r := d.Render
- // 设置填充样式
- chart.Style{
- FillColor: opt.FillColor,
- }.WriteToRenderer(r)
- // 设置文本样式
- chart.Style{
- FontColor: NewTheme(ThemeDark).GetTextColor(),
- FontSize: labelFontSize,
- StrokeWidth: 1,
- Font: opt.Font,
- }.WriteTextOptionsToRenderer(r)
- for _, markPointData := range s.MarkPoint.Data {
- p := points[summary.MinIndex]
- value := summary.MinValue
- switch markPointData.Type {
- case SeriesMarkDataTypeMax:
- p = points[summary.MaxIndex]
- value = summary.MaxValue
- }
-
- d.pin(p.X, p.Y-symbolSize>>1, symbolSize)
- text := commafWithDigits(value)
- textBox := r.MeasureText(text)
- d.text(text, p.X-textBox.Width()>>1, p.Y-symbolSize>>1-2)
- }
-}
diff --git a/mark_point_test.go b/mark_point_test.go
deleted file mode 100644
index 2cd8fdd..0000000
--- a/mark_point_test.go
+++ /dev/null
@@ -1,103 +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 (
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/wcharczuk/go-chart/v2"
- "github.com/wcharczuk/go-chart/v2/drawing"
-)
-
-func TestNewMarkPoint(t *testing.T) {
- assert := assert.New(t)
-
- markPoint := NewMarkPoint(
- SeriesMarkDataTypeMax,
- SeriesMarkDataTypeMin,
- SeriesMarkDataTypeAverage,
- )
-
- assert.Equal(SeriesMarkPoint{
- Data: []SeriesMarkData{
- {
- Type: SeriesMarkDataTypeMax,
- },
- {
- Type: SeriesMarkDataTypeMin,
- },
- {
- Type: SeriesMarkDataTypeAverage,
- },
- },
- }, markPoint)
-}
-
-func TestMarkPointRender(t *testing.T) {
- assert := assert.New(t)
-
- d, err := NewDraw(DrawOption{
- Width: 400,
- Height: 300,
- }, PaddingOption(chart.Box{
- Left: 20,
- Right: 20,
- }))
- assert.Nil(err)
- f, _ := chart.GetDefaultFont()
-
- markPointRender(markPointRenderOption{
- Draw: d,
- FillColor: drawing.ColorBlack,
- Font: f,
- Series: &Series{
- MarkPoint: NewMarkPoint(
- SeriesMarkDataTypeMax,
- SeriesMarkDataTypeMin,
- ),
- Data: NewSeriesDataFromValues([]float64{
- 1,
- 3,
- 5,
- }),
- },
- Points: []Point{
- {
- X: 1,
- Y: 50,
- },
- {
- X: 100,
- Y: 100,
- },
- {
- X: 200,
- Y: 200,
- },
- },
- })
- data, err := d.Bytes()
- assert.Nil(err)
- assert.Equal("", string(data))
-}
diff --git a/painter.go b/painter.go
index 639371e..d762e86 100644
--- a/painter.go
+++ b/painter.go
@@ -135,6 +135,8 @@ func NewPainter(opts PainterOptions, opt ...PainterOption) (*Painter, error) {
if err != nil {
return nil, err
}
+ r.SetFont(font)
+
p := &Painter{
render: r,
box: Box{
@@ -167,6 +169,9 @@ func (p *Painter) Child(opt ...PainterOption) *Painter {
}
func (p *Painter) SetStyle(style Style) {
+ if style.Font == nil {
+ style.Font = p.font
+ }
p.previousStyle = p.style
p.style = style
style.WriteToRenderer(p.render)
@@ -179,6 +184,9 @@ func (p *Painter) SetDrawingStyle(style Style) {
}
func (p *Painter) SetTextStyle(style Style) {
+ if style.Font == nil {
+ style.Font = p.font
+ }
p.previousStyle = p.style
p.style = style
style.WriteTextOptionsToRenderer(p.render)
diff --git a/painter_test.go b/painter_test.go
index 425dbbe..1cc08be 100644
--- a/painter_test.go
+++ b/painter_test.go
@@ -94,7 +94,7 @@ func TestPainter(t *testing.T) {
fn: func(p *Painter) {
p.Text("hello world!", 3, 6)
},
- result: "",
+ result: "",
},
// line stroke
{
diff --git a/pie_chart.go b/pie_chart.go
deleted file mode 100644
index 099a91c..0000000
--- a/pie_chart.go
+++ /dev/null
@@ -1,159 +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 (
- "errors"
- "math"
-
- "github.com/golang/freetype/truetype"
- "github.com/wcharczuk/go-chart/v2"
-)
-
-func getPieStyle(theme *Theme, index int) chart.Style {
- seriesColor := theme.GetSeriesColor(index)
- return chart.Style{
- StrokeColor: seriesColor,
- StrokeWidth: 1,
- FillColor: seriesColor,
- }
-}
-
-type pieChartOption struct {
- Theme string
- Font *truetype.Font
- SeriesList SeriesList
-}
-
-func pieChartRender(opt pieChartOption, result *basicRenderResult) error {
- d, err := NewDraw(DrawOption{
- Parent: result.d,
- }, PaddingOption(chart.Box{
- Top: result.titleBox.Height(),
- }))
- if err != nil {
- return err
- }
-
- values := make([]float64, len(opt.SeriesList))
- total := float64(0)
- radiusValue := ""
- for index, series := range opt.SeriesList {
- if len(series.Radius) != 0 {
- radiusValue = series.Radius
- }
- value := float64(0)
- for _, item := range series.Data {
- value += item.Value
- }
- values[index] = value
- total += value
- }
- if total <= 0 {
- return errors.New("The sum value of pie chart should gt 0")
- }
- r := d.Render
- theme := NewTheme(opt.Theme)
-
- box := d.Box
- cx := box.Width() >> 1
- cy := box.Height() >> 1
-
- diameter := chart.MinInt(box.Width(), box.Height())
- radius := getRadius(float64(diameter), radiusValue)
-
- labelLineWidth := 15
- if radius < 50 {
- labelLineWidth = 10
- }
- labelRadius := radius + float64(labelLineWidth)
-
- seriesNames := opt.SeriesList.Names()
-
- if len(values) == 1 {
- getPieStyle(theme, 0).WriteToRenderer(r)
- d.moveTo(cx, cy)
- d.circle(radius, cx, cy)
- } else {
- currentValue := float64(0)
- for index, v := range values {
-
- pieStyle := getPieStyle(theme, index)
- pieStyle.WriteToRenderer(r)
- d.moveTo(cx, cy)
- start := chart.PercentToRadians(currentValue/total) - math.Pi/2
- currentValue += v
- percent := (v / total)
- delta := chart.PercentToRadians(percent)
- d.arcTo(cx, cy, radius, radius, start, delta)
- d.lineTo(cx, cy)
- r.Close()
- r.FillStroke()
-
- series := opt.SeriesList[index]
- // 是否显示label
- showLabel := series.Label.Show
- if !showLabel {
- continue
- }
-
- // label的角度为饼块中间
- angle := start + delta/2
- startx := cx + int(radius*math.Cos(angle))
- starty := cy + int(radius*math.Sin(angle))
-
- endx := cx + int(labelRadius*math.Cos(angle))
- endy := cy + int(labelRadius*math.Sin(angle))
- d.moveTo(startx, starty)
- d.lineTo(endx, endy)
- offset := labelLineWidth
- if endx < cx {
- offset *= -1
- }
- d.moveTo(endx, endy)
- endx += offset
- d.lineTo(endx, endy)
- r.Stroke()
- textStyle := chart.Style{
- FontColor: theme.GetTextColor(),
- FontSize: labelFontSize,
- Font: opt.Font,
- }
- if !series.Label.Color.IsZero() {
- textStyle.FontColor = series.Label.Color
- }
- textStyle.GetTextOptions().WriteToRenderer(r)
- text := NewPieLabelFormatter(seriesNames, series.Label.Formatter)(index, v, percent)
- textBox := r.MeasureText(text)
- textMargin := 3
- x := endx + textMargin
- y := endy + textBox.Height()>>1 - 1
- if offset < 0 {
- textWidth := textBox.Width()
- x = endx - textWidth - textMargin
- }
- d.text(text, x, y)
- }
- }
- return nil
-}
diff --git a/pie_chart_test.go b/pie_chart_test.go
deleted file mode 100644
index 84072be..0000000
--- a/pie_chart_test.go
+++ /dev/null
@@ -1,69 +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 (
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/wcharczuk/go-chart/v2"
- "github.com/wcharczuk/go-chart/v2/drawing"
-)
-
-func TestPieChartRender(t *testing.T) {
- assert := assert.New(t)
-
- d, err := NewDraw(DrawOption{
- Width: 250,
- Height: 150,
- })
- assert.Nil(err)
-
- f, _ := chart.GetDefaultFont()
-
- err = pieChartRender(pieChartOption{
- Font: f,
- SeriesList: NewPieSeriesList([]float64{
- 5,
- 10,
- 0,
- }, PieSeriesOption{
- Names: []string{
- "a",
- "b",
- "c",
- },
- Label: SeriesLabel{
- Show: true,
- Color: drawing.ColorRed,
- },
- Radius: "20%",
- }),
- }, &basicRenderResult{
- d: d,
- })
- assert.Nil(err)
- data, err := d.Bytes()
- assert.Nil(err)
- assert.Equal("", string(data))
-}
diff --git a/radar_chart.go b/radar_chart.go
deleted file mode 100644
index 364213d..0000000
--- a/radar_chart.go
+++ /dev/null
@@ -1,193 +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 (
- "errors"
-
- "github.com/golang/freetype/truetype"
- "github.com/wcharczuk/go-chart/v2"
- "github.com/wcharczuk/go-chart/v2/drawing"
-)
-
-type RadarIndicator struct {
- // Indicator's name
- Name string
- // The maximum value of indicator
- Max float64
- // The minimum value of indicator
- Min float64
-}
-
-type radarChartOption struct {
- Theme string
- Font *truetype.Font
- SeriesList SeriesList
- Indicators []RadarIndicator
-}
-
-func radarChartRender(opt radarChartOption, result *basicRenderResult) error {
- sides := len(opt.Indicators)
- if sides < 3 {
- return errors.New("The count of indicator should be >= 3")
- }
- maxValues := make([]float64, len(opt.Indicators))
- for _, series := range opt.SeriesList {
- for index, item := range series.Data {
- if index < len(maxValues) && item.Value > maxValues[index] {
- maxValues[index] = item.Value
- }
- }
- }
- for index, indicator := range opt.Indicators {
- if indicator.Max <= 0 {
- opt.Indicators[index].Max = maxValues[index]
- }
- }
- d, err := NewDraw(DrawOption{
- Parent: result.d,
- }, PaddingOption(chart.Box{
- Top: result.titleBox.Height(),
- }))
- if err != nil {
- return err
- }
- radiusValue := ""
- for _, series := range opt.SeriesList {
- if len(series.Radius) != 0 {
- radiusValue = series.Radius
- }
- }
-
- box := d.Box
- cx := box.Width() >> 1
- cy := box.Height() >> 1
- diameter := chart.MinInt(box.Width(), box.Height())
- radius := getRadius(float64(diameter), radiusValue)
-
- theme := NewTheme(opt.Theme)
-
- divideCount := 5
- divideRadius := float64(int(radius / float64(divideCount)))
- radius = divideRadius * float64(divideCount)
-
- style := chart.Style{
- StrokeColor: theme.GetAxisSplitLineColor(),
- StrokeWidth: 1,
- }
- r := d.Render
- style.WriteToRenderer(r)
- center := Point{
- X: cx,
- Y: cy,
- }
- for i := 0; i < divideCount; i++ {
- d.polygon(center, divideRadius*float64(i+1), sides)
- }
- points := getPolygonPoints(center, radius, sides)
- for _, p := range points {
- d.moveTo(center.X, center.Y)
- d.lineTo(p.X, p.Y)
- d.Render.Stroke()
- }
- // 文本
- textStyle := chart.Style{
- FontColor: theme.GetTextColor(),
- FontSize: labelFontSize,
- Font: opt.Font,
- }
- textStyle.GetTextOptions().WriteToRenderer(r)
- offset := 5
- // 文本生成
- for index, p := range points {
- name := opt.Indicators[index].Name
- b := r.MeasureText(name)
- isXCenter := p.X == center.X
- isYCenter := p.Y == center.Y
- isRight := p.X > center.X
- isLeft := p.X < center.X
- isTop := p.Y < center.Y
- isBottom := p.Y > center.Y
- x := p.X
- y := p.Y
- if isXCenter {
- x -= b.Width() >> 1
- if isTop {
- y -= b.Height()
- } else {
- y += b.Height()
- }
- }
- if isYCenter {
- y += b.Height() >> 1
- }
- if isTop {
- y += offset
- }
- if isBottom {
- y += offset
- }
- if isRight {
- x += offset
- }
- if isLeft {
- x -= (b.Width() + offset)
- }
- d.text(name, x, y)
- }
-
- // 雷达图
- angles := getPolygonPointAngles(sides)
- maxCount := len(opt.Indicators)
- for _, series := range opt.SeriesList {
- linePoints := make([]Point, 0, maxCount)
- for j, item := range series.Data {
- if j >= maxCount {
- continue
- }
- indicator := opt.Indicators[j]
- percent := (item.Value - indicator.Min) / (indicator.Max - indicator.Min)
- r := percent * radius
- p := getPolygonPoint(center, r, angles[j])
- linePoints = append(linePoints, p)
- }
- color := theme.GetSeriesColor(series.index)
- dotFillColor := drawing.ColorWhite
- if theme.IsDark() {
- dotFillColor = color
- }
- linePoints = append(linePoints, linePoints[0])
- s := LineStyle{
- StrokeColor: color,
- StrokeWidth: defaultStrokeWidth,
- DotWidth: defaultDotWidth,
- DotColor: color,
- DotFillColor: dotFillColor,
- FillColor: color.WithAlpha(20),
- }
- d.lineStroke(linePoints, s)
- d.fill(linePoints, s.Style())
- d.lineDot(linePoints[0:len(linePoints)-1], s)
- }
- return nil
-}
diff --git a/radar_chart_test.go b/radar_chart_test.go
deleted file mode 100644
index c5d2aa9..0000000
--- a/radar_chart_test.go
+++ /dev/null
@@ -1,102 +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 (
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/wcharczuk/go-chart/v2"
-)
-
-func TestRadarChartRender(t *testing.T) {
- assert := assert.New(t)
-
- d, err := NewDraw(DrawOption{
- Width: 250,
- Height: 150,
- })
- assert.Nil(err)
-
- f, _ := chart.GetDefaultFont()
- err = radarChartRender(radarChartOption{
- Font: f,
- Indicators: []RadarIndicator{
- {
- Name: "Sales",
- Max: 6500,
- },
- {
- Name: "Administration",
- Max: 16000,
- },
- {
- Name: "Information Technology",
- Max: 30000,
- },
- {
- Name: "Customer Support",
- Max: 38000,
- },
- {
- Name: "Development",
- Max: 52000,
- },
- {
- Name: "Marketing",
- Max: 25000,
- },
- },
- SeriesList: SeriesList{
- {
- Type: ChartTypeRadar,
- Data: NewSeriesDataFromValues([]float64{
- 4200,
- 3000,
- 20000,
- 35000,
- 50000,
- 18000,
- }),
- },
- {
- Type: ChartTypeRadar,
- index: 1,
- Data: NewSeriesDataFromValues([]float64{
- 5000,
- 14000,
- 28000,
- 26000,
- 42000,
- 21000,
- }),
- },
- },
- }, &basicRenderResult{
- d: d,
- })
- assert.Nil(err)
- data, err := d.Bytes()
- assert.Nil(err)
- assert.Equal("", string(data))
-}
diff --git a/range.go b/range.go
deleted file mode 100644
index 255a51b..0000000
--- a/range.go
+++ /dev/null
@@ -1,109 +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 (
- "math"
-)
-
-type Range struct {
- divideCount int
- Min float64
- Max float64
- Size int
- Boundary bool
-}
-
-func NewRange(min, max float64, divideCount int) Range {
- r := math.Abs(max - min)
-
- // 最小单位计算
- unit := 2
- if r > 10 {
- unit = 4
- }
- if r > 30 {
- unit = 5
- }
- if r > 100 {
- unit = 10
- }
- if r > 200 {
- unit = 20
- }
- unit = int((r/float64(divideCount))/float64(unit))*unit + unit
-
- if min != 0 {
- isLessThanZero := min < 0
- min = float64(int(min/float64(unit)) * unit)
- // 如果是小于0,int的时候向上取整了,因此调整
- if min < 0 ||
- (isLessThanZero && min == 0) {
- min -= float64(unit)
- }
- }
- max = min + float64(unit*divideCount)
- return Range{
- Min: min,
- Max: max,
- divideCount: divideCount,
- }
-}
-
-func (r Range) Values() []string {
- offset := (r.Max - r.Min) / float64(r.divideCount)
- values := make([]string, 0)
- for i := 0; i <= r.divideCount; i++ {
- v := r.Min + float64(i)*offset
- value := commafWithDigits(v)
- values = append(values, value)
- }
- return values
-}
-
-func (r *Range) getHeight(value float64) int {
- v := (value - r.Min) / (r.Max - r.Min)
- return int(v * float64(r.Size))
-}
-
-func (r *Range) getRestHeight(value float64) int {
- return r.Size - r.getHeight(value)
-}
-
-func (r *Range) GetRange(index int) (float64, float64) {
- unit := float64(r.Size) / float64(r.divideCount)
- return unit * float64(index), unit * float64(index+1)
-}
-func (r *Range) AutoDivide() []int {
- return autoDivide(r.Size, r.divideCount)
-}
-
-func (r *Range) getWidth(value float64) int {
- v := value / (r.Max - r.Min)
- // 移至居中
- if r.Boundary &&
- r.divideCount != 0 {
- v += 1 / float64(r.divideCount*2)
- }
- return int(v * float64(r.Size))
-}
diff --git a/range_test.go b/range_test.go
deleted file mode 100644
index d1aea8f..0000000
--- a/range_test.go
+++ /dev/null
@@ -1,94 +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 (
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestRange(t *testing.T) {
- assert := assert.New(t)
-
- r := NewRange(0, 8, 6)
- assert.Equal(0.0, r.Min)
- assert.Equal(12.0, r.Max)
-
- r = NewRange(0, 12, 6)
- assert.Equal(0.0, r.Min)
- assert.Equal(24.0, r.Max)
-
- r = NewRange(-13, 18, 6)
- assert.Equal(-20.0, r.Min)
- assert.Equal(40.0, r.Max)
-
- r = NewRange(0, 150, 6)
- assert.Equal(0.0, r.Min)
- assert.Equal(180.0, r.Max)
-
- r = NewRange(0, 400, 6)
- assert.Equal(0.0, r.Min)
- assert.Equal(480.0, r.Max)
-}
-
-func TestRangeHeightWidth(t *testing.T) {
- assert := assert.New(t)
- r := NewRange(0, 8, 6)
- r.Size = 100
-
- assert.Equal(33, r.getHeight(4))
- assert.Equal(67, r.getRestHeight(4))
-
- assert.Equal(33, r.getWidth(4))
- r.Boundary = true
- assert.Equal(41, r.getWidth(4))
-}
-
-func TestRangeGetRange(t *testing.T) {
- assert := assert.New(t)
- r := NewRange(0, 8, 6)
- r.Size = 120
-
- f1, f2 := r.GetRange(0)
- assert.Equal(0.0, f1)
- assert.Equal(20.0, f2)
-
- f1, f2 = r.GetRange(2)
- assert.Equal(40.0, f1)
- assert.Equal(60.0, f2)
-}
-
-func TestRangeAutoDivide(t *testing.T) {
- assert := assert.New(t)
-
- r := Range{
- Size: 120,
- divideCount: 6,
- }
-
- assert.Equal([]int{0, 20, 40, 60, 80, 100, 120}, r.AutoDivide())
-
- r.Size = 130
- assert.Equal([]int{0, 22, 44, 66, 88, 109, 130}, r.AutoDivide())
-}
diff --git a/series.go b/series.go
deleted file mode 100644
index 14227d1..0000000
--- a/series.go
+++ /dev/null
@@ -1,233 +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 (
- "math"
- "strings"
-
- "github.com/dustin/go-humanize"
- "github.com/wcharczuk/go-chart/v2"
- "github.com/wcharczuk/go-chart/v2/drawing"
-)
-
-type SeriesData struct {
- // The value of series data
- Value float64
- // The style of series data
- Style chart.Style
-}
-
-func NewSeriesFromValues(values []float64, chartType ...string) Series {
- s := Series{
- Data: NewSeriesDataFromValues(values),
- }
- if len(chartType) != 0 {
- s.Type = chartType[0]
- }
- return s
-}
-
-func NewSeriesDataFromValues(values []float64) []SeriesData {
- data := make([]SeriesData, len(values))
- for index, value := range values {
- data[index] = SeriesData{
- Value: value,
- }
- }
- return data
-}
-
-type SeriesLabel struct {
- // Data label formatter, which supports string template.
- // {b}: the name of a data item.
- // {c}: the value of a data item.
- // {d}: the percent of a data item(pie chart).
- Formatter string
- // The color for label
- Color drawing.Color
- // Show flag for label
- Show bool
- // Distance to the host graphic element.
- Distance int
-}
-
-const (
- SeriesMarkDataTypeMax = "max"
- SeriesMarkDataTypeMin = "min"
- SeriesMarkDataTypeAverage = "average"
-)
-
-type SeriesMarkData struct {
- // The mark data type, it can be "max", "min", "average".
- // The "average" is only for mark line
- Type string
-}
-type SeriesMarkPoint struct {
- // The width of symbol, default value is 30
- SymbolSize int
- // The mark data of series mark point
- Data []SeriesMarkData
-}
-type SeriesMarkLine struct {
- // The mark data of series mark line
- Data []SeriesMarkData
-}
-type Series struct {
- index int
- // The type of series, it can be "line", "bar" or "pie".
- // Default value is "line"
- Type string
- // The data list of series
- Data []SeriesData
- // The Y axis index, it should be 0 or 1.
- // Default value is 1
- YAxisIndex int
- // The style for series
- Style chart.Style
- // The label for series
- Label SeriesLabel
- // The name of series
- Name string
- // Radius for Pie chart, e.g.: 40%, default is "40%"
- Radius string
- // Mark point for series
- MarkPoint SeriesMarkPoint
- // Make line for series
- MarkLine SeriesMarkLine
- // Max value of series
- Min *float64
- // Min value of series
- Max *float64
-}
-type SeriesList []Series
-
-type PieSeriesOption struct {
- Radius string
- Label SeriesLabel
- Names []string
-}
-
-func NewPieSeriesList(values []float64, opts ...PieSeriesOption) SeriesList {
- result := make([]Series, len(values))
- var opt PieSeriesOption
- if len(opts) != 0 {
- opt = opts[0]
- }
- for index, v := range values {
- name := ""
- if index < len(opt.Names) {
- name = opt.Names[index]
- }
- s := Series{
- Type: ChartTypePie,
- Data: []SeriesData{
- {
- Value: v,
- },
- },
- Radius: opt.Radius,
- Label: opt.Label,
- Name: name,
- }
- result[index] = s
- }
- return result
-}
-
-type seriesSummary struct {
- MaxIndex int
- MaxValue float64
- MinIndex int
- MinValue float64
- AverageValue float64
-}
-
-func (s *Series) Summary() seriesSummary {
- minIndex := -1
- maxIndex := -1
- minValue := math.MaxFloat64
- maxValue := -math.MaxFloat64
- sum := float64(0)
- for j, item := range s.Data {
- if item.Value < minValue {
- minIndex = j
- minValue = item.Value
- }
- if item.Value > maxValue {
- maxIndex = j
- maxValue = item.Value
- }
- sum += item.Value
- }
- return seriesSummary{
- MaxIndex: maxIndex,
- MaxValue: maxValue,
- MinIndex: minIndex,
- MinValue: minValue,
- AverageValue: sum / float64(len(s.Data)),
- }
-}
-
-func (sl SeriesList) Names() []string {
- names := make([]string, len(sl))
- for index, s := range sl {
- names[index] = s.Name
- }
- return names
-}
-
-type LabelFormatter func(index int, value float64, percent float64) string
-
-func NewPieLabelFormatter(seriesNames []string, layout string) LabelFormatter {
- if len(layout) == 0 {
- layout = "{b}: {d}"
- }
- return NewLabelFormatter(seriesNames, layout)
-}
-
-func NewValueLabelFormater(seriesNames []string, layout string) LabelFormatter {
- if len(layout) == 0 {
- layout = "{c}"
- }
- return NewLabelFormatter(seriesNames, layout)
-}
-
-func NewLabelFormatter(seriesNames []string, layout string) LabelFormatter {
- return func(index int, value, percent float64) string {
- // 如果无percent的则设置为<0
- percentText := ""
- if percent >= 0 {
- percentText = humanize.FtoaWithDigits(percent*100, 2) + "%"
- }
- valueText := humanize.FtoaWithDigits(value, 2)
- name := ""
- if len(seriesNames) > index {
- name = seriesNames[index]
- }
- text := strings.ReplaceAll(layout, "{c}", valueText)
- text = strings.ReplaceAll(text, "{d}", percentText)
- text = strings.ReplaceAll(text, "{b}", name)
- return text
- }
-}
diff --git a/series_test.go b/series_test.go
deleted file mode 100644
index 1460180..0000000
--- a/series_test.go
+++ /dev/null
@@ -1,166 +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 (
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestNewSeriesFromValues(t *testing.T) {
- assert := assert.New(t)
-
- assert.Equal(Series{
- Data: []SeriesData{
- {
- Value: 1,
- },
- {
- Value: 2,
- },
- },
- Type: ChartTypeBar,
- }, NewSeriesFromValues([]float64{
- 1,
- 2,
- }, ChartTypeBar))
-}
-
-func TestNewSeriesDataFromValues(t *testing.T) {
- assert := assert.New(t)
-
- assert.Equal([]SeriesData{
- {
- Value: 1,
- },
- {
- Value: 2,
- },
- }, NewSeriesDataFromValues([]float64{
- 1,
- 2,
- }))
-}
-
-func TestNewPieSeriesList(t *testing.T) {
- assert := assert.New(t)
-
- assert.Equal(SeriesList{
- {
- Type: ChartTypePie,
- Name: "a",
- Label: SeriesLabel{
- Show: true,
- },
- Radius: "30%",
- Data: []SeriesData{
- {
- Value: 1,
- },
- },
- },
- {
- Type: ChartTypePie,
- Name: "b",
- Label: SeriesLabel{
- Show: true,
- },
- Radius: "30%",
- Data: []SeriesData{
- {
- Value: 2,
- },
- },
- },
- }, NewPieSeriesList([]float64{
- 1,
- 2,
- }, PieSeriesOption{
- Radius: "30%",
- Label: SeriesLabel{
- Show: true,
- },
- Names: []string{
- "a",
- "b",
- },
- }))
-}
-
-func TestSeriesSummary(t *testing.T) {
- assert := assert.New(t)
-
- s := Series{
- Data: NewSeriesDataFromValues([]float64{
- 1,
- 3,
- 5,
- 7,
- 9,
- }),
- }
- assert.Equal(seriesSummary{
- MaxIndex: 4,
- MaxValue: 9,
- MinIndex: 0,
- MinValue: 1,
- AverageValue: 5,
- }, s.Summary())
-}
-
-func TestGetSeriesNames(t *testing.T) {
- assert := assert.New(t)
-
- sl := SeriesList{
- {
- Name: "a",
- },
- {
- Name: "b",
- },
- }
- assert.Equal([]string{
- "a",
- "b",
- }, sl.Names())
-}
-
-func TestNewPieLabelFormatter(t *testing.T) {
- assert := assert.New(t)
-
- fn := NewPieLabelFormatter([]string{
- "a",
- "b",
- }, "")
- assert.Equal("a: 35%", fn(0, 1.2, 0.35))
-}
-
-func TestNewValueLabelFormater(t *testing.T) {
- assert := assert.New(t)
- fn := NewValueLabelFormater([]string{
- "a",
- "b",
- }, "")
- assert.Equal("1.2", fn(0, 1.2, 0.35))
-}
diff --git a/table.go b/table.go
deleted file mode 100644
index 9cfc6b1..0000000
--- a/table.go
+++ /dev/null
@@ -1,145 +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 (
- "errors"
-
- "github.com/wcharczuk/go-chart/v2"
-)
-
-type TableOption struct {
- // draw
- Draw *Draw
- // The width of table
- Width int
- // The header of table
- Header []string
- // The style of table
- Style chart.Style
- ColumnWidths []float64
- // 是否仅测量高度
- measurement bool
-}
-
-var ErrTableColumnWidthInvalid = errors.New("table column width is invalid")
-
-func tableDivideWidth(width, size int, columnWidths []float64) ([]int, error) {
- widths := make([]int, size)
-
- autoFillCount := size
- restWidth := width
- if len(columnWidths) != 0 {
- for index, v := range columnWidths {
- if v <= 0 {
- continue
- }
- autoFillCount--
- // 小于1的表示占比
- if v < 1 {
- widths[index] = int(v * float64(width))
- } else {
- widths[index] = int(v)
- }
- restWidth -= widths[index]
- }
- }
- if restWidth < 0 {
- return nil, ErrTableColumnWidthInvalid
- }
- // 填充其它未指定的宽度
- if autoFillCount > 0 {
- autoWidth := restWidth / autoFillCount
- for index, v := range widths {
- if v == 0 {
- widths[index] = autoWidth
- }
- }
- }
- widthSum := 0
- for _, v := range widths {
- widthSum += v
- }
- if widthSum > width {
- return nil, ErrTableColumnWidthInvalid
- }
- return widths, nil
-}
-
-func TableMeasure(opt TableOption) (chart.Box, error) {
- d, err := NewDraw(DrawOption{
- Width: opt.Width,
- Height: 600,
- })
- if err != nil {
- return chart.BoxZero, err
- }
- opt.Draw = d
- opt.measurement = true
- return tableRender(opt)
-}
-
-func tableRender(opt TableOption) (chart.Box, error) {
- if opt.Draw == nil {
- return chart.BoxZero, errors.New("draw can not be nil")
- }
- if len(opt.Header) == 0 {
- return chart.BoxZero, errors.New("header can not be nil")
- }
- width := opt.Width
- if width == 0 {
- width = opt.Draw.Box.Width()
- }
-
- columnWidths, err := tableDivideWidth(width, len(opt.Header), opt.ColumnWidths)
- if err != nil {
- return chart.BoxZero, err
- }
-
- d := opt.Draw
- style := opt.Style
- y := 0
- x := 0
-
- headerMaxHeight := 0
- for index, text := range opt.Header {
- var box chart.Box
- w := columnWidths[index]
- y0 := y + int(opt.Style.FontSize)
- if opt.measurement {
- box = d.measureTextFit(text, x, y0, w, style)
- } else {
- box = d.textFit(text, x, y0, w, style)
- }
- if box.Height() > headerMaxHeight {
- headerMaxHeight = box.Height()
- }
- x += w
- }
- y += headerMaxHeight
-
- return chart.Box{
- Right: width,
- Bottom: y,
- }, nil
-}
diff --git a/theme.go b/theme.go
index e3f9773..88c73df 100644
--- a/theme.go
+++ b/theme.go
@@ -22,10 +22,6 @@
package charts
-import (
- "github.com/wcharczuk/go-chart/v2/drawing"
-)
-
const ThemeDark = "dark"
const ThemeLight = "light"
const ThemeGrafana = "grafana"
@@ -37,198 +33,9 @@ type Theme struct {
type themeColorPalette struct {
isDarkMode bool
- axisStrokeColor drawing.Color
- axisSplitLineColor drawing.Color
- backgroundColor drawing.Color
- textColor drawing.Color
- seriesColors []drawing.Color
-}
-
-var palettes = map[string]*themeColorPalette{}
-
-func init() {
- echartSeriesColors := []drawing.Color{
- parseColor("#5470c6"),
- parseColor("#91cc75"),
- parseColor("#fac858"),
- parseColor("#ee6666"),
- parseColor("#73c0de"),
- parseColor("#3ba272"),
- parseColor("#fc8452"),
- parseColor("#9a60b4"),
- parseColor("#ea7ccc"),
- }
- grafanaSeriesColors := []drawing.Color{
- parseColor("#7EB26D"),
- parseColor("#EAB839"),
- parseColor("#6ED0E0"),
- parseColor("#EF843C"),
- parseColor("#E24D42"),
- parseColor("#1F78C1"),
- parseColor("#705DA0"),
- parseColor("#508642"),
- }
- antSeriesColors := []drawing.Color{
- parseColor("#5b8ff9"),
- parseColor("#5ad8a6"),
- parseColor("#5d7092"),
- parseColor("#f6bd16"),
- parseColor("#6f5ef9"),
- parseColor("#6dc8ec"),
- parseColor("#945fb9"),
- parseColor("#ff9845"),
- }
- AddTheme(
- ThemeDark,
- true,
- drawing.Color{
- R: 185,
- G: 184,
- B: 206,
- A: 255,
- },
- drawing.Color{
- R: 72,
- G: 71,
- B: 83,
- A: 255,
- },
- drawing.Color{
- R: 16,
- G: 12,
- B: 42,
- A: 255,
- },
- drawing.Color{
- R: 238,
- G: 238,
- B: 238,
- A: 255,
- },
- echartSeriesColors,
- )
-
- AddTheme(
- ThemeLight,
- false,
- drawing.Color{
- R: 110,
- G: 112,
- B: 121,
- A: 255,
- },
- drawing.Color{
- R: 224,
- G: 230,
- B: 242,
- A: 255,
- },
- drawing.ColorWhite,
- drawing.Color{
- R: 70,
- G: 70,
- B: 70,
- A: 255,
- },
- echartSeriesColors,
- )
- AddTheme(
- ThemeAnt,
- false,
- drawing.Color{
- R: 110,
- G: 112,
- B: 121,
- A: 255,
- },
- drawing.Color{
- R: 224,
- G: 230,
- B: 242,
- A: 255,
- },
- drawing.ColorWhite,
- drawing.Color{
- R: 70,
- G: 70,
- B: 70,
- A: 255,
- },
- antSeriesColors,
- )
- AddTheme(
- ThemeGrafana,
- true,
- drawing.Color{
- R: 185,
- G: 184,
- B: 206,
- A: 255,
- },
- drawing.Color{
- R: 68,
- G: 67,
- B: 67,
- A: 255,
- },
- drawing.Color{
- R: 31,
- G: 29,
- B: 29,
- A: 255,
- },
- drawing.Color{
- R: 216,
- G: 217,
- B: 218,
- A: 255,
- },
- grafanaSeriesColors,
- )
-}
-
-func AddTheme(name string, isDarkMode bool, axisStrokeColor, axisSplitLineColor, backgroundColor, textColor drawing.Color, seriesColors []drawing.Color) {
- palettes[name] = &themeColorPalette{
- isDarkMode: isDarkMode,
- axisStrokeColor: axisStrokeColor,
- axisSplitLineColor: axisSplitLineColor,
- backgroundColor: backgroundColor,
- textColor: textColor,
- seriesColors: seriesColors,
- }
-}
-
-func NewTheme(name string) *Theme {
- p, ok := palettes[name]
- if !ok {
- p = palettes[ThemeLight]
- }
- return &Theme{
- palette: p,
- }
-}
-
-func (t *Theme) IsDark() bool {
- return t.palette.isDarkMode
-}
-
-func (t *Theme) GetAxisStrokeColor() drawing.Color {
- return t.palette.axisStrokeColor
-}
-
-func (t *Theme) GetAxisSplitLineColor() drawing.Color {
- return t.palette.axisSplitLineColor
-}
-
-func (t *Theme) GetSeriesColor(index int) drawing.Color {
- colors := t.palette.seriesColors
- return colors[index%len(colors)]
-}
-
-func (t *Theme) GetBackgroundColor() drawing.Color {
- return t.palette.backgroundColor
-}
-
-func (t *Theme) GetTextColor() drawing.Color {
- return t.palette.textColor
+ axisStrokeColor Color
+ axisSplitLineColor Color
+ backgroundColor Color
+ textColor Color
+ seriesColors []Color
}
diff --git a/theme_test.go b/theme_test.go
deleted file mode 100644
index bf22afd..0000000
--- a/theme_test.go
+++ /dev/null
@@ -1,87 +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 (
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/wcharczuk/go-chart/v2/drawing"
-)
-
-func TestTheme(t *testing.T) {
- assert := assert.New(t)
-
- darkTheme := NewTheme(ThemeDark)
- lightTheme := NewTheme(ThemeLight)
-
- assert.True(darkTheme.IsDark())
- assert.False(lightTheme.IsDark())
-
- assert.Equal(drawing.Color{
- R: 185,
- G: 184,
- B: 206,
- A: 255,
- }, darkTheme.GetAxisStrokeColor())
- assert.Equal(drawing.Color{
- R: 110,
- G: 112,
- B: 121,
- A: 255,
- }, lightTheme.GetAxisStrokeColor())
-
- assert.Equal(drawing.Color{
- R: 72,
- G: 71,
- B: 83,
- A: 255,
- }, darkTheme.GetAxisSplitLineColor())
- assert.Equal(drawing.Color{
- R: 224,
- G: 230,
- B: 242,
- A: 255,
- }, lightTheme.GetAxisSplitLineColor())
-
- assert.Equal(drawing.Color{
- R: 16,
- G: 12,
- B: 42,
- A: 255,
- }, darkTheme.GetBackgroundColor())
- assert.Equal(drawing.ColorWhite, lightTheme.GetBackgroundColor())
-
- assert.Equal(drawing.Color{
- R: 238,
- G: 238,
- B: 238,
- A: 255,
- }, darkTheme.GetTextColor())
- assert.Equal(drawing.Color{
- R: 70,
- G: 70,
- B: 70,
- A: 255,
- }, lightTheme.GetTextColor())
-}
diff --git a/title.go b/title.go
deleted file mode 100644
index 07a2eef..0000000
--- a/title.go
+++ /dev/null
@@ -1,155 +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 (
- "strconv"
- "strings"
-
- "github.com/wcharczuk/go-chart/v2"
-)
-
-type TitleOption struct {
- // Title text, support \n for new line
- Text string
- // Subtitle text, support \n for new line
- Subtext string
- // Title style
- Style chart.Style
- // Subtitle style
- SubtextStyle chart.Style
- // Distance between title component and the left side of the container.
- // It can be pixel value: 20, percentage value: 20%,
- // or position value: right, center.
- Left string
- // Distance between title component and the top side of the container.
- // It can be pixel value: 20.
- Top string
-}
-type titleMeasureOption struct {
- width int
- height int
- text string
- style chart.Style
-}
-
-func splitTitleText(text string) []string {
- arr := strings.Split(text, "\n")
- result := make([]string, 0)
- for _, v := range arr {
- v = strings.TrimSpace(v)
- if v == "" {
- continue
- }
- result = append(result, v)
- }
- return result
-}
-
-func drawTitle(p *Draw, opt *TitleOption) (chart.Box, error) {
- if len(opt.Text) == 0 {
- return chart.BoxZero, nil
- }
-
- padding := opt.Style.Padding
- d, err := NewDraw(DrawOption{
- Parent: p,
- }, PaddingOption(padding))
- if err != nil {
- return chart.BoxZero, err
- }
-
- r := d.Render
-
- measureOptions := make([]titleMeasureOption, 0)
-
- // 主标题
- for _, v := range splitTitleText(opt.Text) {
- measureOptions = append(measureOptions, titleMeasureOption{
- text: v,
- style: opt.Style.GetTextOptions(),
- })
- }
- // 副标题
- for _, v := range splitTitleText(opt.Subtext) {
- measureOptions = append(measureOptions, titleMeasureOption{
- text: v,
- style: opt.SubtextStyle.GetTextOptions(),
- })
- }
-
- textMaxWidth := 0
- textMaxHeight := 0
- width := 0
- for index, item := range measureOptions {
- item.style.WriteTextOptionsToRenderer(r)
- textBox := r.MeasureText(item.text)
-
- w := textBox.Width()
- h := textBox.Height()
- if w > textMaxWidth {
- textMaxWidth = w
- }
- if h > textMaxHeight {
- textMaxHeight = h
- }
- measureOptions[index].height = h
- measureOptions[index].width = w
- }
- width = textMaxWidth
- titleX := 0
- b := d.Box
- switch opt.Left {
- case PositionRight:
- titleX = b.Width() - textMaxWidth
- case PositionCenter:
- titleX = b.Width()>>1 - (textMaxWidth >> 1)
- default:
- if strings.HasSuffix(opt.Left, "%") {
- value, _ := strconv.Atoi(strings.ReplaceAll(opt.Left, "%", ""))
- titleX = b.Width() * value / 100
- } else {
- value, _ := strconv.Atoi(opt.Left)
- titleX = value
- }
- }
- titleY := 0
- // TODO TOP 暂只支持数值
- if opt.Top != "" {
- value, _ := strconv.Atoi(opt.Top)
- titleY += value
- }
- for _, item := range measureOptions {
- item.style.WriteTextOptionsToRenderer(r)
- x := titleX + (textMaxWidth-item.width)>>1
- y := titleY + item.height
- d.text(item.text, x, y)
- titleY += item.height
- }
- height := titleY + padding.Top + padding.Bottom
- box := padding.Clone()
- box.Right = box.Left + titleX + width
- box.Bottom = box.Top + height
-
- return box, nil
-}
diff --git a/title_test.go b/title_test.go
deleted file mode 100644
index 23573c3..0000000
--- a/title_test.go
+++ /dev/null
@@ -1,142 +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 (
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/wcharczuk/go-chart/v2"
- "github.com/wcharczuk/go-chart/v2/drawing"
-)
-
-func TestSplitTitleText(t *testing.T) {
- assert := assert.New(t)
-
- assert.Equal([]string{
- "a",
- "b",
- }, splitTitleText("a\nb"))
- assert.Equal([]string{
- "a",
- }, splitTitleText("a\n "))
-}
-
-func TestDrawTitle(t *testing.T) {
- assert := assert.New(t)
-
- newOption := func() *TitleOption {
- f, _ := chart.GetDefaultFont()
- return &TitleOption{
- Text: "title\nHello",
- Subtext: "subtitle\nWorld!",
- Style: chart.Style{
- FontSize: 14,
- Font: f,
- FontColor: drawing.ColorBlack,
- },
- SubtextStyle: chart.Style{
- FontSize: 10,
- Font: f,
- FontColor: drawing.ColorBlue,
- },
- }
- }
- newDraw := func() *Draw {
- d, _ := NewDraw(DrawOption{
- Width: 400,
- Height: 300,
- })
- return d
- }
-
- tests := []struct {
- newDraw func() *Draw
- newOption func() *TitleOption
- result string
- box chart.Box
- }{
- {
- newDraw: newDraw,
- newOption: newOption,
- result: "",
- box: chart.Box{
- Right: 43,
- Bottom: 58,
- },
- },
- {
- newDraw: newDraw,
- newOption: func() *TitleOption {
- opt := newOption()
- opt.Left = PositionRight
- opt.Top = "50"
- return opt
- },
- result: "",
- box: chart.Box{
- Right: 400,
- Bottom: 108,
- },
- },
- {
- newDraw: newDraw,
- newOption: func() *TitleOption {
- opt := newOption()
- opt.Left = PositionCenter
- opt.Top = "10"
- return opt
- },
- result: "",
- box: chart.Box{
- Right: 222,
- Bottom: 68,
- },
- },
- {
- newDraw: newDraw,
- newOption: func() *TitleOption {
- opt := newOption()
- opt.Left = "10%"
- opt.Top = "10"
- return opt
- },
- result: "",
- box: chart.Box{
- Right: 83,
- Bottom: 68,
- },
- },
- }
- for _, tt := range tests {
- d := tt.newDraw()
- o := tt.newOption()
- b, err := drawTitle(d, o)
- assert.Nil(err)
- assert.Equal(tt.box, b)
- data, err := d.Bytes()
- assert.Nil(err)
- assert.NotEmpty(data)
- assert.Equal(tt.result, string(data))
- }
-}
diff --git a/util.go b/util.go
index d35b4b0..5fee163 100644
--- a/util.go
+++ b/util.go
@@ -134,8 +134,8 @@ func commafWithDigits(value float64) string {
return humanize.CommafWithDigits(value, decimals)
}
-func parseColor(color string) drawing.Color {
- c := drawing.Color{}
+func parseColor(color string) Color {
+ c := Color{}
if color == "" {
return c
}
diff --git a/util_test.go b/util_test.go
index 6489ab3..fefbabc 100644
--- a/util_test.go
+++ b/util_test.go
@@ -80,13 +80,15 @@ func TestGetRadius(t *testing.T) {
func TestMeasureTextMaxWidthHeight(t *testing.T) {
assert := assert.New(t)
- r, err := chart.SVG(400, 300)
+ p, err := NewPainter(PainterOptions{
+ Width: 400,
+ Height: 300,
+ })
assert.Nil(err)
style := chart.Style{
FontSize: 10,
}
- style.Font, _ = chart.GetDefaultFont()
- style.WriteToRenderer(r)
+ p.SetStyle(style)
maxWidth, maxHeight := measureTextMaxWidthHeight([]string{
"Mon",
@@ -96,7 +98,7 @@ func TestMeasureTextMaxWidthHeight(t *testing.T) {
"Fri",
"Sat",
"Sun",
- }, r)
+ }, p)
assert.Equal(26, maxWidth)
assert.Equal(12, maxHeight)
}
diff --git a/xaxis.go b/xaxis.go
deleted file mode 100644
index d79f40e..0000000
--- a/xaxis.go
+++ /dev/null
@@ -1,93 +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"
-)
-
-type XAxisOption struct {
- Font *truetype.Font
- // The boundary gap on both sides of a coordinate axis.
- // Nil or *true means the center part of two axis ticks
- BoundaryGap *bool
- // The data value of x axis
- Data []string
- // The theme of chart
- Theme string
- // Hidden x axis
- Hidden bool
- // Number of segments that the axis is split into. Note that this number serves only as a recommendation.
- SplitNumber int
-}
-
-func NewXAxisOption(data []string, boundaryGap ...*bool) XAxisOption {
- opt := XAxisOption{
- Data: data,
- }
- if len(boundaryGap) != 0 {
- opt.BoundaryGap = boundaryGap[0]
- }
- return opt
-}
-
-// drawXAxis draws x axis, and returns the height, range of if.
-func drawXAxis(p *Painter, opt *XAxisOption, yAxisCount int) (int, *Range, error) {
- if opt.Hidden {
- return 0, nil, nil
- }
- left := YAxisWidth
- right := (yAxisCount - 1) * YAxisWidth
- pXAxis := p.Child(
- PainterPaddingOption(Box{
- Left: left,
- Right: right,
- }),
- PainterFontOption(opt.Font),
- )
- theme := NewTheme(opt.Theme)
- data := NewAxisDataListFromStringList(opt.Data)
- style := AxisOption{
- BoundaryGap: opt.BoundaryGap,
- StrokeColor: theme.GetAxisStrokeColor(),
- FontColor: theme.GetAxisStrokeColor(),
- StrokeWidth: 1,
- SplitNumber: opt.SplitNumber,
- }
-
- boundary := true
- max := float64(len(opt.Data))
- if isFalse(opt.BoundaryGap) {
- boundary = false
- max--
- }
- axis := NewAxis(pXAxis, data, style)
- axis.Render()
- return axis.measure().Height, &Range{
- divideCount: len(opt.Data),
- Min: 0,
- Max: max,
- Size: pXAxis.Width(),
- Boundary: boundary,
- }, nil
-}
diff --git a/xaxis_test.go b/xaxis_test.go
deleted file mode 100644
index 267cdb1..0000000
--- a/xaxis_test.go
+++ /dev/null
@@ -1,108 +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 (
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestNewXAxisOption(t *testing.T) {
- assert := assert.New(t)
-
- opt := NewXAxisOption([]string{
- "a",
- "b",
- }, FalseFlag())
-
- assert.Equal(XAxisOption{
- Data: []string{
- "a",
- "b",
- },
- BoundaryGap: FalseFlag(),
- }, opt)
-
-}
-func TestDrawXAxis(t *testing.T) {
- assert := assert.New(t)
-
- newDraw := func() *Draw {
- d, _ := NewDraw(DrawOption{
- Width: 400,
- Height: 300,
- })
- return d
- }
-
- tests := []struct {
- newDraw func() *Draw
- newOption func() *XAxisOption
- result string
- }{
- {
- newDraw: newDraw,
- newOption: func() *XAxisOption {
- return &XAxisOption{
- BoundaryGap: FalseFlag(),
- Data: []string{
- "Mon",
- "Tue",
- },
- }
- },
- result: "",
- },
- {
- newDraw: newDraw,
- newOption: func() *XAxisOption {
- return &XAxisOption{
- Data: []string{
- "01-01",
- "01-02",
- "01-03",
- "01-04",
- "01-05",
- "01-06",
- "01-07",
- "01-08",
- "01-09",
- },
- SplitNumber: 3,
- }
- },
- result: "",
- },
- }
-
- for _, tt := range tests {
- d := tt.newDraw()
- height, _, err := drawXAxis(d, tt.newOption(), 1)
- assert.Nil(err)
- assert.Equal(25, height)
- data, err := d.Bytes()
- assert.Nil(err)
- assert.Equal(tt.result, string(data))
- }
-}
diff --git a/yaxis.go b/yaxis.go
deleted file mode 100644
index 5d55440..0000000
--- a/yaxis.go
+++ /dev/null
@@ -1,95 +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 (
- "strings"
-
- "github.com/wcharczuk/go-chart/v2"
- "github.com/wcharczuk/go-chart/v2/drawing"
-)
-
-type YAxisOption struct {
- // The minimun value of axis.
- Min *float64
- // The maximum value of axis.
- Max *float64
- // Hidden y axis
- Hidden bool
- // Formatter for y axis text value
- Formatter string
- // Color for y axis
- Color drawing.Color
-}
-
-// TODO 长度是否可以变化
-const YAxisWidth = 40
-
-func drawYAxis(p *Painter, opt *ChartOption, axisIndex, xAxisHeight int, padding chart.Box) (*Range, error) {
- theme := NewTheme(opt.Theme)
- yRange := opt.newYRange(axisIndex)
- values := yRange.Values()
- yAxis := opt.YAxisList[axisIndex]
- formatter := yAxis.Formatter
- if len(formatter) != 0 {
- for index, text := range values {
- values[index] = strings.ReplaceAll(formatter, "{value}", text)
- }
- }
-
- data := NewAxisDataListFromStringList(values)
- style := AxisOption{
- Position: PositionLeft,
- BoundaryGap: FalseFlag(),
- FontColor: theme.GetAxisStrokeColor(),
- TickShow: FalseFlag(),
- StrokeWidth: 1,
- SplitLineColor: theme.GetAxisSplitLineColor(),
- SplitLineShow: true,
- }
- if !yAxis.Color.IsZero() {
- style.FontColor = yAxis.Color
- style.StrokeColor = yAxis.Color
- }
- width := NewAxis(p, data, style).measure().Width
-
- yAxisCount := len(opt.YAxisList)
- boxWidth := p.Width()
- if axisIndex > 0 {
- style.SplitLineShow = false
- style.Position = PositionRight
- padding.Right += (axisIndex - 1) * YAxisWidth
- } else {
- boxWidth = p.Width() - (yAxisCount-1)*YAxisWidth
- padding.Left += (YAxisWidth - width)
- }
-
- pYAxis := p.Child(
- PainterWidthHeightOption(boxWidth, p.Height()-xAxisHeight),
- PainterPaddingOption(padding),
- PainterFontOption(opt.Font),
- )
- NewAxis(pYAxis, data, style).Render()
- yRange.Size = pYAxis.Height()
- return &yRange, nil
-}
diff --git a/yaxis_test.go b/yaxis_test.go
deleted file mode 100644
index 0bbef7a..0000000
--- a/yaxis_test.go
+++ /dev/null
@@ -1,119 +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 (
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/wcharczuk/go-chart/v2"
-)
-
-func TestDrawYAxis(t *testing.T) {
- assert := assert.New(t)
- newDraw := func() *Draw {
- d, _ := NewDraw(DrawOption{
- Width: 400,
- Height: 300,
- })
- return d
- }
-
- tests := []struct {
- newDraw func() *Draw
- newOption func() *ChartOption
- axisIndex int
- xAxisHeight int
- result string
- }{
- {
- newDraw: newDraw,
- newOption: func() *ChartOption {
- return &ChartOption{
- YAxisList: []YAxisOption{
- {
- Max: NewFloatPoint(20),
- },
- },
- SeriesList: []Series{
- {
- Data: []SeriesData{
- {
- Value: 1,
- },
- {
- Value: 2,
- },
- },
- },
- },
- }
- },
- result: "",
- },
- {
- newDraw: newDraw,
- newOption: func() *ChartOption {
- return &ChartOption{
- YAxisList: []YAxisOption{
- {},
- {
- Max: NewFloatPoint(20),
- Formatter: "{value} C",
- },
- },
- SeriesList: []Series{
- {
- YAxisIndex: 1,
- Data: []SeriesData{
- {
- Value: 1,
- },
- {
- Value: 2,
- },
- },
- },
- },
- }
- },
- axisIndex: 1,
- result: "",
- },
- }
-
- for _, tt := range tests {
- d := tt.newDraw()
- r, err := drawYAxis(d, tt.newOption(), tt.axisIndex, tt.xAxisHeight, chart.NewBox(10, 10, 10, 10))
- assert.Nil(err)
- assert.Equal(&Range{
- divideCount: 6,
- Max: 20,
- Size: 280,
- }, r)
-
- data, err := d.Bytes()
- assert.Nil(err)
- assert.Equal(tt.result, string(data))
- }
-}