diff --git a/axis.go b/axis.go
index dc8844a..46292e4 100644
--- a/axis.go
+++ b/axis.go
@@ -73,6 +73,7 @@ type axisMeasurement struct {
Height int
}
+// NewAxis creates a new axis with data and style options
func NewAxis(d *Draw, data AxisDataList, option AxisOption) *axis {
return &axis{
d: d,
@@ -112,6 +113,7 @@ func (as *AxisOption) Style(f *truetype.Font) chart.Style {
}
type AxisData struct {
+ // The text value of axis
Text string
}
type AxisDataList []AxisData
diff --git a/bar_chart.go b/bar_chart.go
index ac5a0a9..11e9d5a 100644
--- a/bar_chart.go
+++ b/bar_chart.go
@@ -28,17 +28,20 @@ import (
)
type barChartOption struct {
+ // The series list fo bar chart
SeriesList SeriesList
- Theme string
- Font *truetype.Font
+ // 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(),
+ Top: result.titleBox.Height(),
+ // TODO 后续考虑是否需要根据左侧是否展示y轴再生成对应的left
Left: YAxisWidth,
}))
if err != nil {
@@ -118,6 +121,7 @@ func barChartRender(opt barChartOption, result *basicRenderResult) ([]markPointR
X: x + barWidth>>1,
Y: top,
}
+ // 如果label不需要展示,则返回
if !series.Label.Show {
continue
}
@@ -135,6 +139,7 @@ func barChartRender(opt barChartOption, result *basicRenderResult) ([]markPointR
d.text(text, x+(barWidth-textBox.Width())>>1, barMaxHeight-h-5)
}
+ // 生成mark point的参数
markPointRenderOptions = append(markPointRenderOptions, markPointRenderOption{
Draw: d,
FillColor: seriesColor,
diff --git a/chart.go b/chart.go
index 1f31252..44bee0c 100644
--- a/chart.go
+++ b/chart.go
@@ -44,24 +44,45 @@ type Point struct {
const labelFontSize = 10
+var defaultChartWidth = 600
+var defaultChartHeight = 400
+
type ChartOption struct {
- Type string
- Font *truetype.Font
- Theme string
- Title TitleOption
- Legend LegendOption
- XAxis XAxisOption
- YAxisList []YAxisOption
- Width int
- Height int
- Parent *Draw
- Padding chart.Box
- Box chart.Box
- SeriesList SeriesList
+ // 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 background color of chart
BackgroundColor drawing.Color
- Children []ChartOption
+ // The child charts
+ Children []ChartOption
}
+// FillDefault fills the default value for chart option
func (o *ChartOption) FillDefault(theme string) {
t := NewTheme(theme)
// 如果为空,初始化
@@ -83,7 +104,7 @@ func (o *ChartOption) FillDefault(theme string) {
}
if o.Padding.IsZero() {
o.Padding = chart.Box{
- Top: 20,
+ Top: 10,
Right: 10,
Bottom: 10,
Left: 10,
@@ -102,10 +123,7 @@ func (o *ChartOption) FillDefault(theme string) {
}
if o.Title.Style.Padding.IsZero() {
o.Title.Style.Padding = chart.Box{
- Left: 5,
- Top: 5,
- Right: 5,
- Bottom: 5,
+ Bottom: 10,
}
}
// 副标题
@@ -113,7 +131,7 @@ func (o *ChartOption) FillDefault(theme string) {
o.Title.SubtextStyle.FontColor = o.Title.Style.FontColor.WithAlpha(180)
}
if o.Title.SubtextStyle.FontSize == 0 {
- o.Title.SubtextStyle.FontSize = 10
+ o.Title.SubtextStyle.FontSize = labelFontSize
}
if o.Title.SubtextStyle.Font == nil {
o.Title.SubtextStyle.Font = o.Font
@@ -121,7 +139,7 @@ func (o *ChartOption) FillDefault(theme string) {
o.Legend.theme = theme
if o.Legend.Style.FontSize == 0 {
- o.Legend.Style.FontSize = 10
+ o.Legend.Style.FontSize = labelFontSize
}
if o.Legend.Left == "" {
o.Legend.Left = PositionCenter
@@ -155,7 +173,18 @@ func (o *ChartOption) getWidth() int {
if o.Parent != nil {
return o.Parent.Box.Width()
}
- return 600
+ 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 {
@@ -166,7 +195,7 @@ func (o *ChartOption) getHeight() int {
if o.Parent != nil {
return o.Parent.Box.Height()
}
- return 400
+ return defaultChartHeight
}
func (o *ChartOption) newYRange(axisIndex int) Range {
@@ -227,10 +256,18 @@ func (r *basicRenderResult) getYRange(index int) *Range {
return r.yRangeList[index]
}
+// Render renders the chart by option
func Render(opt ChartOption) (*Draw, error) {
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)
diff --git a/chart_test.go b/chart_test.go
index 06370c8..ee768ec 100644
--- a/chart_test.go
+++ b/chart_test.go
@@ -30,6 +30,20 @@ import (
"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
@@ -37,7 +51,7 @@ func TestChartFillDefault(t *testing.T) {
opt.FillDefault("")
// padding
assert.Equal(chart.Box{
- Top: 20,
+ Top: 10,
Right: 10,
Bottom: 10,
Left: 10,
@@ -53,13 +67,6 @@ func TestChartFillDefault(t *testing.T) {
}, opt.Title.Style.FontColor)
// title font size
assert.Equal(float64(14), opt.Title.Style.FontSize)
- // title padding
- assert.Equal(chart.Box{
- Left: 5,
- Top: 5,
- Right: 5,
- Bottom: 5,
- }, opt.Title.Style.Padding)
// sub title font color
assert.Equal(drawing.Color{
R: 70,
@@ -267,5 +274,5 @@ func TestChartRender(t *testing.T) {
assert.Nil(err)
data, err := d.Bytes()
assert.Nil(err)
- assert.Equal("", string(data))
+ assert.Equal("", string(data))
}
diff --git a/draw.go b/draw.go
index 0dbcf83..228f622 100644
--- a/draw.go
+++ b/draw.go
@@ -46,21 +46,30 @@ const (
)
type Draw struct {
+ // Render
Render chart.Renderer
- Box chart.Box
- Font *truetype.Font
+ // The canvas box
+ Box chart.Box
+ // The font for draw
+ Font *truetype.Font
+ // The parent of draw
parent *Draw
}
type DrawOption struct {
- Type string
+ // Draw type, "svg" or "png", default type is "svg"
+ Type string
+ // Parent of draw
Parent *Draw
- Width int
+ // 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
@@ -71,6 +80,7 @@ func PaddingOption(padding chart.Box) Option {
}
}
+// BoxOption set the box of draw canvas
func BoxOption(box chart.Box) Option {
return func(d *Draw) error {
if box.IsZero() {
@@ -81,6 +91,7 @@ func BoxOption(box chart.Box) Option {
}
}
+// 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")
@@ -122,10 +133,12 @@ func NewDraw(opt DrawOption, opts ...Option) (*Draw, error) {
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
@@ -141,6 +154,7 @@ func (d *Draw) Top() *Draw {
return t
}
+// Bytes returns the data of draw canvas
func (d *Draw) Bytes() ([]byte, error) {
buffer := bytes.Buffer{}
err := d.Render.Save(&buffer)
diff --git a/examples/demo/main.go b/examples/demo/main.go
index 5d58cda..7916b4d 100644
--- a/examples/demo/main.go
+++ b/examples/demo/main.go
@@ -3,6 +3,7 @@ package main
import (
"bytes"
"net/http"
+ "strconv"
charts "github.com/vicanso/go-charts"
"github.com/wcharczuk/go-chart/v2"
@@ -65,7 +66,12 @@ func indexHandler(w http.ResponseWriter, req *http.Request) {
if req.URL.Path != "/" {
return
}
- theme := req.URL.Query().Get("theme")
+ 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)
chartOptions := []charts.ChartOption{
// 普通折线图
{
diff --git a/font.go b/font.go
new file mode 100644
index 0000000..c40b51e
--- /dev/null
+++ b/font.go
@@ -0,0 +1,61 @@
+// 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"
+ "sync"
+
+ "github.com/golang/freetype/truetype"
+ "github.com/wcharczuk/go-chart/v2/roboto"
+)
+
+var fonts = sync.Map{}
+var ErrFontNotExists = errors.New("font is not exists")
+
+func init() {
+ _ = InstallFont("roboto", roboto.Roboto)
+}
+
+// InstallFont installs the font for charts
+func InstallFont(fontFamily string, data []byte) error {
+ font, err := truetype.Parse(data)
+ if err != nil {
+ return err
+ }
+ fonts.Store(fontFamily, font)
+ return nil
+}
+
+// GetFont get the font by font family
+func GetFont(fontFamily string) (*truetype.Font, error) {
+ value, ok := fonts.Load(fontFamily)
+ if !ok {
+ return nil, ErrFontNotExists
+ }
+ f, ok := value.(*truetype.Font)
+ if !ok {
+ return nil, ErrFontNotExists
+ }
+ return f, nil
+}
diff --git a/font_test.go b/font_test.go
new file mode 100644
index 0000000..9dc731c
--- /dev/null
+++ b/font_test.go
@@ -0,0 +1,42 @@
+// 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/roboto"
+)
+
+func TestInstallFont(t *testing.T) {
+ assert := assert.New(t)
+
+ fontFamily := "test"
+ err := InstallFont(fontFamily, roboto.Roboto)
+ assert.Nil(err)
+
+ font, err := GetFont(fontFamily)
+ assert.Nil(err)
+ assert.NotNil(font)
+}
diff --git a/legend.go b/legend.go
index f11b50f..7eb33b3 100644
--- a/legend.go
+++ b/legend.go
@@ -50,6 +50,7 @@ type LegendOption struct {
Orient string
}
+// NewLegendOption creates a new legend option by legend text list
func NewLegendOption(data []string, position ...string) LegendOption {
opt := LegendOption{
Data: data,
@@ -101,6 +102,8 @@ func (l *legend) Render() (chart.Box, error) {
legendDotHeight := 5
textPadding := 5
legendMargin := 10
+ // 往下移2倍dot的高度
+ y += 2 * legendDotHeight
widthCount := 0
maxTextWidth := 0
diff --git a/legend_test.go b/legend_test.go
index 0c3a0c9..2dc286f 100644
--- a/legend_test.go
+++ b/legend_test.go
@@ -23,6 +23,7 @@
package charts
import (
+ "fmt"
"testing"
"github.com/stretchr/testify/assert"
@@ -81,7 +82,7 @@ func TestLegendRender(t *testing.T) {
Style: style,
})
},
- result: "",
+ result: "",
box: chart.Box{
Right: 214,
Bottom: 25,
@@ -102,7 +103,7 @@ func TestLegendRender(t *testing.T) {
Style: style,
})
},
- result: "",
+ result: "",
box: chart.Box{
Right: 400,
Bottom: 25,
@@ -122,7 +123,7 @@ func TestLegendRender(t *testing.T) {
Style: style,
})
},
- result: "",
+ result: "",
box: chart.Box{
Right: 307,
Bottom: 25,
@@ -143,10 +144,10 @@ func TestLegendRender(t *testing.T) {
Orient: OrientVertical,
})
},
- result: "",
+ result: "",
box: chart.Box{
Right: 61,
- Bottom: 70,
+ Bottom: 80,
},
},
{
@@ -166,9 +167,9 @@ func TestLegendRender(t *testing.T) {
},
box: chart.Box{
Right: 101,
- Bottom: 70,
+ Bottom: 80,
},
- result: "",
+ result: "",
},
}
@@ -178,6 +179,7 @@ func TestLegendRender(t *testing.T) {
assert.Nil(err)
assert.Equal(tt.box, b)
data, err := d.Bytes()
+ fmt.Println(string(data))
assert.Nil(err)
assert.NotEmpty(data)
assert.Equal(tt.result, string(data))
diff --git a/series.go b/series.go
index f986c54..e5d9bd8 100644
--- a/series.go
+++ b/series.go
@@ -32,7 +32,9 @@ import (
)
type SeriesData struct {
+ // The value of series data
Value float64
+ // The style of series data
Style chart.Style
}
@@ -57,9 +59,15 @@ func NewSeriesDataFromValues(values []float64) []SeriesData {
}
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
- Color drawing.Color
- Show bool
+ // The color for label
+ Color drawing.Color
+ // Show flag for label
+ Show bool
}
const (
@@ -69,27 +77,42 @@ const (
)
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
- Data []SeriesMarkData
+ // 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
- Type string
- Data []SeriesData
+ 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
- Style chart.Style
- Label SeriesLabel
- Name string
- // Radius of Pie chart, e.g.: 40%
- Radius string
+ // 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
- MarkLine SeriesMarkLine
+ // Make line for series
+ MarkLine SeriesMarkLine
}
type SeriesList []Series
diff --git a/title.go b/title.go
index a5ba6b3..07a2eef 100644
--- a/title.go
+++ b/title.go
@@ -142,7 +142,8 @@ func drawTitle(p *Draw, opt *TitleOption) (chart.Box, error) {
for _, item := range measureOptions {
item.style.WriteTextOptionsToRenderer(r)
x := titleX + (textMaxWidth-item.width)>>1
- d.text(item.text, x, titleY)
+ y := titleY + item.height
+ d.text(item.text, x, y)
titleY += item.height
}
height := titleY + padding.Top + padding.Bottom
diff --git a/title_test.go b/title_test.go
index e5f895c..23573c3 100644
--- a/title_test.go
+++ b/title_test.go
@@ -79,7 +79,7 @@ func TestDrawTitle(t *testing.T) {
{
newDraw: newDraw,
newOption: newOption,
- result: "",
+ result: "",
box: chart.Box{
Right: 43,
Bottom: 58,
@@ -93,7 +93,7 @@ func TestDrawTitle(t *testing.T) {
opt.Top = "50"
return opt
},
- result: "",
+ result: "",
box: chart.Box{
Right: 400,
Bottom: 108,
@@ -107,7 +107,7 @@ func TestDrawTitle(t *testing.T) {
opt.Top = "10"
return opt
},
- result: "",
+ result: "",
box: chart.Box{
Right: 222,
Bottom: 68,
@@ -121,7 +121,7 @@ func TestDrawTitle(t *testing.T) {
opt.Top = "10"
return opt
},
- result: "",
+ result: "",
box: chart.Box{
Right: 83,
Bottom: 68,