From 5068828ca703411491661d4218d5055ab8cdc890 Mon Sep 17 00:00:00 2001 From: vicanso Date: Sun, 15 May 2022 15:07:03 +0800 Subject: [PATCH 01/21] feat: support painter for chart draw function --- alias.go | 33 +++++ line.go | 9 +- painer.go | 394 +++++++++++++++++++++++++++++++++++++++++++++++++ painer_test.go | 371 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 802 insertions(+), 5 deletions(-) create mode 100644 alias.go create mode 100644 painer.go create mode 100644 painer_test.go diff --git a/alias.go b/alias.go new file mode 100644 index 0000000..3a09919 --- /dev/null +++ b/alias.go @@ -0,0 +1,33 @@ +// 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 Box = chart.Box +type Renderer = chart.Renderer +type Style = chart.Style +type Color = drawing.Color diff --git a/line.go b/line.go index 0fc25d6..15ab575 100644 --- a/line.go +++ b/line.go @@ -24,18 +24,17 @@ package charts import ( "github.com/wcharczuk/go-chart/v2" - "github.com/wcharczuk/go-chart/v2/drawing" ) type LineStyle struct { ClassName string StrokeDashArray []float64 - StrokeColor drawing.Color + StrokeColor Color StrokeWidth float64 - FillColor drawing.Color + FillColor Color DotWidth float64 - DotColor drawing.Color - DotFillColor drawing.Color + DotColor Color + DotFillColor Color } func (ls *LineStyle) Style() chart.Style { diff --git a/painer.go b/painer.go new file mode 100644 index 0000000..670f6f6 --- /dev/null +++ b/painer.go @@ -0,0 +1,394 @@ +// 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" +) + +type Painter struct { + render Renderer + box Box + font *truetype.Font + parent *Painter + style Style + previousStyle Style +} + +type PainterOptions struct { + // Draw type, "svg" or "png", default type is "svg" + Type string + // The width of draw canvas + Width int + // The height of draw canvas + Height int + // The font for painter + Font *truetype.Font +} + +type PainterOption func(*Painter) + +// PainterPaddingOption sets the padding of draw canvas +func PainterPaddingOption(padding Box) PainterOption { + return func(p *Painter) { + p.box.Left += padding.Left + p.box.Top += padding.Top + p.box.Right -= padding.Right + p.box.Bottom -= padding.Bottom + } +} + +// PainterBoxOption sets the box of draw canvas +func PainterBoxOption(box Box) PainterOption { + return func(p *Painter) { + if box.IsZero() { + return + } + p.box = box + } +} + +// PainterFontOption sets the font of draw canvas +func PainterFontOption(font *truetype.Font) PainterOption { + return func(p *Painter) { + p.font = font + } +} + +// PainterStyleOption sets the style of draw canvas +func PainterStyleOption(style Style) PainterOption { + return func(p *Painter) { + p.SetDrawingStyle(style) + } +} + +// NewPainter creates a new Painter +func NewPainter(opts PainterOptions, opt ...PainterOption) (*Painter, error) { + if opts.Width <= 0 || opts.Height <= 0 { + return nil, errors.New("width/height can not be nil") + } + font := opts.Font + if font == nil { + f, err := chart.GetDefaultFont() + if err != nil { + return nil, err + } + font = f + } + fn := chart.SVG + if opts.Type == ChartOutputPNG { + fn = chart.PNG + } + width := opts.Width + height := opts.Height + r, err := fn(width, height) + if err != nil { + return nil, err + } + p := &Painter{ + render: r, + box: Box{ + Right: opts.Width, + Bottom: opts.Height, + }, + font: font, + } + p.setOptions(opt...) + return p, nil +} +func (p *Painter) setOptions(opts ...PainterOption) { + for _, fn := range opts { + fn(p) + } +} + +func (p *Painter) Child(opt ...PainterOption) *Painter { + child := &Painter{ + render: p.render, + box: p.box.Clone(), + font: p.font, + parent: p, + style: p.style, + previousStyle: p.previousStyle, + } + child.setOptions(opt...) + return child +} + +func (p *Painter) SetStyle(style Style) { + p.previousStyle = p.style + p.style = style + style.WriteToRenderer(p.render) +} + +func (p *Painter) SetDrawingStyle(style Style) { + p.previousStyle = p.style + p.style = style + style.WriteDrawingOptionsToRenderer(p.render) +} + +func (p *Painter) SetTextStyle(style Style) { + p.previousStyle = p.style + p.style = style + style.WriteTextOptionsToRenderer(p.render) +} + +func (p *Painter) RestoreStyle() { + p.style = p.previousStyle +} + +// Bytes returns the data of draw canvas +func (p *Painter) Bytes() ([]byte, error) { + buffer := bytes.Buffer{} + err := p.render.Save(&buffer) + if err != nil { + return nil, err + } + return buffer.Bytes(), err +} + +// MoveTo moves the cursor to a given point +func (p *Painter) MoveTo(x, y int) { + p.render.MoveTo(x+p.box.Left, y+p.box.Top) +} + +func (p *Painter) ArcTo(cx, cy int, rx, ry, startAngle, delta float64) { + p.render.ArcTo(cx+p.box.Left, cy+p.box.Top, rx, ry, startAngle, delta) +} + +func (p *Painter) LineTo(x, y int) { + p.render.LineTo(x+p.box.Left, y+p.box.Top) +} + +func (p *Painter) Pin(x, y, width int) { + r := float64(width) / 2 + y -= width / 4 + angle := chart.DegreesToRadians(15) + box := p.box + + startAngle := math.Pi/2 + angle + delta := 2*math.Pi - 2*angle + p.ArcTo(x, y, r, r, startAngle, delta) + p.LineTo(x, y) + p.Close() + p.FillStroke() + + startX := x - int(r) + startY := y + endX := x + int(r) + endY := y + p.MoveTo(startX, startY) + + left := box.Left + top := box.Top + cx := x + cy := y + int(r*2.5) + p.render.QuadCurveTo(cx+left, cy+top, endX+left, endY+top) + p.Close() + p.Fill() +} + +func (p *Painter) 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 + } + p.MoveTo(x0, y0) + p.LineTo(x0+halfWidth, y1) + p.LineTo(x1, y0) + p.LineTo(x0+halfWidth, y+dy) + p.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 + } + p.MoveTo(x0, y0) + p.LineTo(x1, y0+halfHeight) + p.LineTo(x0, y0+height) + p.LineTo(x0+dx, y0+halfHeight) + p.LineTo(x0, y0) + } + p.FillStroke() +} + +func (p *Painter) ArrowLeft(x, y, width, height int) { + p.arrow(x, y, width, height, PositionLeft) +} + +func (p *Painter) ArrowRight(x, y, width, height int) { + p.arrow(x, y, width, height, PositionRight) +} + +func (p *Painter) ArrowTop(x, y, width, height int) { + p.arrow(x, y, width, height, PositionTop) +} +func (p *Painter) ArrowBottom(x, y, width, height int) { + p.arrow(x, y, width, height, PositionBottom) +} + +func (p *Painter) Circle(radius float64, x, y int) { + p.render.Circle(radius, x+p.box.Left, y+p.box.Top) +} + +func (p *Painter) Stroke() { + p.render.Stroke() +} + +func (p *Painter) Close() { + p.render.Close() +} + +func (p *Painter) FillStroke() { + p.render.FillStroke() +} + +func (p *Painter) Fill() { + p.render.Fill() +} + +func (p *Painter) LineStroke(points []Point, style LineStyle) { + s := style.Style() + if !s.ShouldDrawStroke() { + return + } + defer p.RestoreStyle() + p.SetDrawingStyle(s.GetStrokeOptions()) + for index, point := range points { + x := point.X + y := point.Y + if index == 0 { + p.MoveTo(x, y) + } else { + p.LineTo(x, y) + } + } + p.Stroke() +} + +func (p *Painter) SetBackground(width, height int, color Color) { + r := p.render + s := chart.Style{ + FillColor: color, + } + defer p.RestoreStyle() + p.SetStyle(s) + // 设置背景色不使用box,因此不直接使用Painter + r.MoveTo(0, 0) + r.LineTo(width, 0) + r.LineTo(width, height) + r.LineTo(0, height) + r.LineTo(0, 0) + p.FillStroke() +} +func (p *Painter) MarkLine(x, y, width int) { + arrowWidth := 16 + arrowHeight := 10 + endX := x + width + p.Circle(3, x, y) + p.render.Fill() + p.MoveTo(x+5, y) + p.LineTo(endX-arrowWidth, y) + p.Stroke() + p.render.SetStrokeDashArray([]float64{}) + p.ArrowRight(endX, y, arrowWidth, arrowHeight) +} + +func (p *Painter) Polygon(center Point, radius float64, sides int) { + points := getPolygonPoints(center, radius, sides) + for i, item := range points { + if i == 0 { + p.MoveTo(item.X, item.Y) + } else { + p.LineTo(item.X, item.Y) + } + } + p.LineTo(points[0].X, points[0].Y) + p.Stroke() +} + +func (p *Painter) FillArea(points []Point, s Style) { + if !s.ShouldDrawFill() { + return + } + defer p.RestoreStyle() + var x, y int + p.SetDrawingStyle(s.GetFillOptions()) + for index, point := range points { + x = point.X + y = point.Y + if index == 0 { + p.MoveTo(x, y) + } else { + p.LineTo(x, y) + } + } + p.Fill() +} + +func (p *Painter) Text(body string, x, y int) { + p.render.Text(body, x+p.box.Left, y+p.box.Top) +} + +func (p *Painter) TextFit(body string, x, y, width int) chart.Box { + style := p.style + textWarp := style.TextWrap + style.TextWrap = chart.TextWrapWord + r := p.render + lines := chart.Text.WrapFit(r, body, width, style) + p.SetTextStyle(style) + var output chart.Box + + for index, line := range lines { + x0 := x + y0 := y + output.Height() + p.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() + } + } + p.style.TextWrap = textWarp + return output +} diff --git a/painer_test.go b/painer_test.go new file mode 100644 index 0000000..425dbbe --- /dev/null +++ b/painer_test.go @@ -0,0 +1,371 @@ +// 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/golang/freetype/truetype" + "github.com/stretchr/testify/assert" + "github.com/wcharczuk/go-chart/v2" + "github.com/wcharczuk/go-chart/v2/drawing" +) + +func TestPainterOption(t *testing.T) { + assert := assert.New(t) + + font := &truetype.Font{} + d, err := NewPainter(PainterOptions{ + Width: 800, + Height: 600, + }, + PainterBoxOption(Box{ + Right: 400, + Bottom: 300, + }), + PainterPaddingOption(Box{ + Left: 1, + Top: 2, + Right: 3, + Bottom: 4, + }), + PainterFontOption(font), + PainterStyleOption(Style{ + ClassName: "test", + }), + ) + assert.Nil(err) + assert.Equal(Box{ + Left: 1, + Top: 2, + Right: 397, + Bottom: 296, + }, d.box) + assert.Equal(font, d.font) + assert.Equal("test", d.style.ClassName) +} + +func TestPainter(t *testing.T) { + assert := assert.New(t) + + tests := []struct { + fn func(*Painter) + result string + }{ + // moveTo, lineTo + { + fn: func(p *Painter) { + p.MoveTo(1, 1) + p.LineTo(2, 2) + p.Stroke() + }, + result: "\\n", + }, + // circle + { + fn: func(p *Painter) { + p.Circle(5, 2, 3) + }, + result: "\\n", + }, + // text + { + fn: func(p *Painter) { + p.Text("hello world!", 3, 6) + }, + result: "\\nhello world!", + }, + // line stroke + { + fn: func(p *Painter) { + p.LineStroke([]Point{ + { + X: 1, + Y: 2, + }, + { + X: 3, + Y: 4, + }, + }, LineStyle{ + StrokeColor: drawing.ColorBlack, + StrokeWidth: 1, + }) + }, + result: "\\n", + }, + // set background + { + fn: func(p *Painter) { + p.SetBackground(400, 300, chart.ColorWhite) + }, + result: "\\n", + }, + // arcTo + { + fn: func(p *Painter) { + p.SetStyle(Style{ + StrokeWidth: 1, + StrokeColor: drawing.ColorBlack, + FillColor: drawing.ColorBlue, + }) + p.ArcTo(100, 100, 100, 100, 0, math.Pi/2) + p.Close() + p.FillStroke() + }, + result: "\\n", + }, + // pin + { + fn: func(p *Painter) { + p.SetStyle(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, + }, + }) + p.Pin(30, 30, 30) + }, + result: "\\n", + }, + // arrow left + { + fn: func(p *Painter) { + p.SetStyle(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, + }, + }) + p.ArrowLeft(30, 30, 16, 10) + }, + result: "\\n", + }, + // arrow right + { + fn: func(p *Painter) { + p.SetStyle(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, + }, + }) + p.ArrowRight(30, 30, 16, 10) + }, + result: "\\n", + }, + // arrow top + { + fn: func(p *Painter) { + p.SetStyle(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, + }, + }) + p.ArrowTop(30, 30, 10, 16) + }, + result: "\\n", + }, + // arrow bottom + { + fn: func(p *Painter) { + p.SetStyle(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, + }, + }) + p.ArrowBottom(30, 30, 10, 16) + }, + result: "\\n", + }, + // mark line + { + fn: func(p *Painter) { + p.SetStyle(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, + }, + }) + p.MarkLine(0, 20, 300) + }, + result: "\\n", + }, + // polygon + { + fn: func(p *Painter) { + p.SetStyle(Style{ + StrokeWidth: 1, + StrokeColor: drawing.Color{ + R: 84, + G: 112, + B: 198, + A: 255, + }, + }) + p.Polygon(Point{ + X: 100, + Y: 100, + }, 50, 6) + }, + result: "\\n", + }, + // FillArea + { + fn: func(p *Painter) { + p.FillArea([]Point{ + { + X: 0, + Y: 0, + }, + { + X: 0, + Y: 100, + }, + { + X: 100, + Y: 100, + }, + { + X: 0, + Y: 0, + }, + }, Style{ + FillColor: drawing.Color{ + R: 84, + G: 112, + B: 198, + A: 255, + }, + }) + }, + result: "\\n", + }, + } + for _, tt := range tests { + d, err := NewPainter(PainterOptions{ + Width: 400, + Height: 300, + }, PainterPaddingOption(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 TestPainterTextFit(t *testing.T) { + assert := assert.New(t) + p, err := NewPainter(PainterOptions{ + Width: 400, + Height: 300, + }) + assert.Nil(err) + f, _ := chart.GetDefaultFont() + style := Style{ + FontSize: 12, + FontColor: chart.ColorBlack, + Font: f, + } + p.SetStyle(style) + box := p.TextFit("Hello World!", 0, 20, 80) + assert.Equal(chart.Box{ + Right: 45, + Bottom: 35, + }, box) + + box = p.TextFit("Hello World!", 0, 100, 200) + assert.Equal(chart.Box{ + Right: 84, + Bottom: 15, + }, box) + + buf, err := p.Bytes() + assert.Nil(err) + assert.Equal(`\nHelloWorld!Hello World!`, string(buf)) +} From 7e80e9a8481d73b4a10d6257a1c07450f886cd9e Mon Sep 17 00:00:00 2001 From: vicanso Date: Mon, 16 May 2022 20:41:13 +0800 Subject: [PATCH 02/21] refactor: adjust axis function --- axis.go | 118 +++++++++++++++--------------- axis_test.go | 14 ++-- painer.go => painter.go | 56 ++++++++++++-- painer_test.go => painter_test.go | 0 util.go | 4 +- xaxis.go | 21 ++---- yaxis.go | 28 +++---- 7 files changed, 134 insertions(+), 107 deletions(-) rename painer.go => painter.go (88%) rename painer_test.go => painter_test.go (100%) diff --git a/axis.go b/axis.go index 46292e4..5881f5e 100644 --- a/axis.go +++ b/axis.go @@ -27,7 +27,6 @@ import ( "github.com/golang/freetype/truetype" "github.com/wcharczuk/go-chart/v2" - "github.com/wcharczuk/go-chart/v2/drawing" ) type AxisOption struct { @@ -42,7 +41,7 @@ type AxisOption struct { SplitNumber int ClassName string // The line color of axis - StrokeColor drawing.Color + StrokeColor Color // The line width StrokeWidth float64 // The length of the axis tick @@ -56,17 +55,17 @@ type AxisOption struct { // The font of label Font *truetype.Font // The color of label - FontColor drawing.Color + 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 drawing.Color + SplitLineColor Color } type axis struct { - d *Draw - data *AxisDataList - option *AxisOption + painter *Painter + data *AxisDataList + option *AxisOption } type axisMeasurement struct { Width int @@ -74,11 +73,11 @@ type axisMeasurement struct { } // NewAxis creates a new axis with data and style options -func NewAxis(d *Draw, data AxisDataList, option AxisOption) *axis { +func NewAxis(p *Painter, data AxisDataList, option AxisOption) *axis { return &axis{ - d: d, - data: &data, - option: &option, + painter: p, + data: &data, + option: &option, } } @@ -149,17 +148,19 @@ func NewAxisDataListFromStringList(textList []string) AxisDataList { func (a *axis) axisLabel(renderOpt *axisRenderOption) { option := a.option data := *a.data - d := a.d + // d := a.d if option.FontColor.IsZero() || len(data) == 0 { return } - r := d.Render + // r := d.Render - s := option.Style(d.Font) - s.GetTextOptions().WriteTextOptionsToRenderer(r) + // s.GetTextOptions().WriteTextOptionsToRenderer(r) + p := a.painter + s := option.Style(p.font) + p.SetTextStyle(s) - width := d.Box.Width() - height := d.Box.Height() + width := p.Width() + height := p.Height() textList := data.TextList() count := len(textList) @@ -188,7 +189,7 @@ func (a *axis) axisLabel(renderOpt *axisRenderOption) { reverseIntSlice(values) for index, text := range textList { y := values[index] - 2 - b := r.MeasureText(text) + b := p.MeasureText(text) if boundaryGap { height := y - values[index+1] y -= (height - b.Height()) >> 1 @@ -200,7 +201,7 @@ func (a *axis) axisLabel(renderOpt *axisRenderOption) { if position == PositionLeft { x = labelWidth - b.Width() - 1 } - d.text(text, x, y) + p.Text(text, x, y) } default: // 定位bottom,重新计算y0的定位 @@ -215,7 +216,7 @@ func (a *axis) axisLabel(renderOpt *axisRenderOption) { } x := values[index] leftOffset := 0 - b := r.MeasureText(text) + b := p.MeasureText(text) if boundaryGap { width := values[index+1] - x leftOffset = (width - b.Width()) >> 1 @@ -223,24 +224,25 @@ func (a *axis) axisLabel(renderOpt *axisRenderOption) { // 左移文本长度 leftOffset = -b.Width() >> 1 } - d.text(text, x+leftOffset, y0) + p.Text(text, x+leftOffset, y0) } } } func (a *axis) axisLine(renderOpt *axisRenderOption) { - d := a.d - r := d.Render + // d := a.d + // r := d.Render + p := a.painter option := a.option - s := option.Style(d.Font) - s.GetStrokeOptions().WriteDrawingOptionsToRenderer(r) + s := option.Style(p.font) + p.SetDrawingStyle(s.GetStrokeOptions()) x0 := 0 y0 := 0 x1 := 0 y1 := 0 - width := d.Box.Width() - height := d.Box.Height() + width := p.Width() + height := p.Height() labelMargin := option.GetLabelMargin() // 轴线 @@ -271,21 +273,22 @@ func (a *axis) axisLine(renderOpt *axisRenderOption) { y1 = y0 } - d.moveTo(x0, y0) - d.lineTo(x1, y1) - r.FillStroke() + p.MoveTo(x0, y0) + p.LineTo(x1, y1) + p.FillStroke() } func (a *axis) axisTick(renderOpt *axisRenderOption) { - d := a.d - r := d.Render + // d := a.d + // r := d.Render + p := a.painter option := a.option - s := option.Style(d.Font) - s.GetStrokeOptions().WriteDrawingOptionsToRenderer(r) + s := option.Style(p.font) + p.SetDrawingStyle(s.GetStrokeOptions()) - width := d.Box.Width() - height := d.Box.Height() + width := p.Width() + height := p.Height() data := *a.data tickCount := len(data) if tickCount == 0 { @@ -319,14 +322,14 @@ func (a *axis) axisTick(renderOpt *axisRenderOption) { for _, v := range values { x := x0 y := v - d.moveTo(x, y) - d.lineTo(x+tickLengthValue, y) - r.Stroke() + p.MoveTo(x, y) + p.LineTo(x+tickLengthValue, y) + p.Stroke() } } // 辅助线 if option.SplitLineShow && !option.SplitLineColor.IsZero() { - r.SetStrokeColor(option.SplitLineColor) + p.SetStrokeColor(option.SplitLineColor) splitLineWidth := width - labelWidth - tickLengthValue x0 = labelWidth + tickLengthValue if position == PositionRight { @@ -336,9 +339,9 @@ func (a *axis) axisTick(renderOpt *axisRenderOption) { for _, v := range values[0 : len(values)-1] { x := x0 y := v - d.moveTo(x, y) - d.lineTo(x+splitLineWidth, y) - r.Stroke() + p.MoveTo(x, y) + p.LineTo(x+splitLineWidth, y) + p.Stroke() } } default: @@ -355,14 +358,14 @@ func (a *axis) axisTick(renderOpt *axisRenderOption) { } x := v y := y0 - d.moveTo(x, y-tickLengthValue) - d.lineTo(x, y) - r.Stroke() + p.MoveTo(x, y-tickLengthValue) + p.LineTo(x, y) + p.Stroke() } } // 辅助线 if option.SplitLineShow && !option.SplitLineColor.IsZero() { - r.SetStrokeColor(option.SplitLineColor) + p.SetStrokeColor(option.SplitLineColor) y0 = 0 splitLineHeight := height - labelHeight - tickLengthValue if position == PositionTop { @@ -377,22 +380,23 @@ func (a *axis) axisTick(renderOpt *axisRenderOption) { x := v y := y0 - d.moveTo(x, y) - d.lineTo(x, y0+splitLineHeight) - r.Stroke() + p.MoveTo(x, y) + p.LineTo(x, y0+splitLineHeight) + p.Stroke() } } } } func (a *axis) measureTextMaxWidthHeight() (int, int) { - d := a.d - r := d.Render - s := a.option.Style(d.Font) + // d := a.d + // r := d.Render + p := a.painter + s := a.option.Style(p.font) data := a.data - s.GetStrokeOptions().WriteDrawingOptionsToRenderer(r) - s.GetTextOptions().WriteTextOptionsToRenderer(r) - return measureTextMaxWidthHeight(data.TextList(), r) + p.SetDrawingStyle(s.GetStrokeOptions()) + p.SetTextStyle(s.GetTextOptions()) + return measureTextMaxWidthHeight(data.TextList(), p) } // measure returns the measurement of axis. @@ -429,7 +433,7 @@ func (a *axis) Render() { } unitCount := chart.MaxInt(option.SplitNumber, 1) - width := a.d.Box.Width() + width := a.painter.Width() textList := a.data.TextList() count := len(textList) diff --git a/axis_test.go b/axis_test.go index 37c8314..fe253e9 100644 --- a/axis_test.go +++ b/axis_test.go @@ -207,10 +207,10 @@ func TestAxis(t *testing.T) { }, } for _, tt := range tests { - d, err := NewDraw(DrawOption{ + p, err := NewPainter(PainterOptions{ Width: 400, Height: 300, - }, PaddingOption(chart.Box{ + }, PainterPaddingOption(chart.Box{ Left: 5, Top: 5, Right: 5, @@ -222,9 +222,9 @@ func TestAxis(t *testing.T) { if tt.newData != nil { data = tt.newData() } - NewAxis(d, data, style).Render() + NewAxis(p, data, style).Render() - result, err := d.Bytes() + result, err := p.Bytes() assert.Nil(err) assert.Equal(tt.result, string(result)) } @@ -233,7 +233,7 @@ func TestAxis(t *testing.T) { func TestMeasureAxis(t *testing.T) { assert := assert.New(t) - d, err := NewDraw(DrawOption{ + p, err := NewPainter(PainterOptions{ Width: 400, Height: 300, }) @@ -243,14 +243,14 @@ func TestMeasureAxis(t *testing.T) { "Sun", }) f, _ := chart.GetDefaultFont() - width := NewAxis(d, data, AxisOption{ + width := NewAxis(p, data, AxisOption{ FontSize: 12, Font: f, Position: PositionLeft, }).measure().Width assert.Equal(44, width) - height := NewAxis(d, data, AxisOption{ + height := NewAxis(p, data, AxisOption{ FontSize: 12, Font: f, Position: PositionTop, diff --git a/painer.go b/painter.go similarity index 88% rename from painer.go rename to painter.go index 670f6f6..639371e 100644 --- a/painer.go +++ b/painter.go @@ -38,14 +38,15 @@ type Painter struct { parent *Painter style Style previousStyle Style + theme *Theme } type PainterOptions struct { // Draw type, "svg" or "png", default type is "svg" Type string - // The width of draw canvas + // The width of draw painter Width int - // The height of draw canvas + // The height of draw painter Height int // The font for painter Font *truetype.Font @@ -53,7 +54,7 @@ type PainterOptions struct { type PainterOption func(*Painter) -// PainterPaddingOption sets the padding of draw canvas +// PainterPaddingOption sets the padding of draw painter func PainterPaddingOption(padding Box) PainterOption { return func(p *Painter) { p.box.Left += padding.Left @@ -63,7 +64,7 @@ func PainterPaddingOption(padding Box) PainterOption { } } -// PainterBoxOption sets the box of draw canvas +// PainterBoxOption sets the box of draw painter func PainterBoxOption(box Box) PainterOption { return func(p *Painter) { if box.IsZero() { @@ -73,21 +74,45 @@ func PainterBoxOption(box Box) PainterOption { } } -// PainterFontOption sets the font of draw canvas +// PainterFontOption sets the font of draw painter func PainterFontOption(font *truetype.Font) PainterOption { return func(p *Painter) { + if font == nil { + return + } p.font = font } } -// PainterStyleOption sets the style of draw canvas +// PainterStyleOption sets the style of draw painter func PainterStyleOption(style Style) PainterOption { return func(p *Painter) { p.SetDrawingStyle(style) } } -// NewPainter creates a new Painter +// PainterThemeOption sets the theme of draw painter +func PainterThemeOption(theme *Theme) PainterOption { + return func(p *Painter) { + if theme == nil { + return + } + p.theme = theme + } +} + +func PainterWidthHeightOption(width, height int) PainterOption { + return func(p *Painter) { + if width > 0 { + p.box.Right = p.box.Left + width + } + if height > 0 { + p.box.Bottom = p.box.Top + height + } + } +} + +// NewPainter creates a new painter func NewPainter(opts PainterOptions, opt ...PainterOption) (*Painter, error) { if opts.Width <= 0 || opts.Height <= 0 { return nil, errors.New("width/height can not be nil") @@ -135,6 +160,7 @@ func (p *Painter) Child(opt ...PainterOption) *Painter { parent: p, style: p.style, previousStyle: p.previousStyle, + theme: p.theme, } child.setOptions(opt...) return child @@ -286,6 +312,22 @@ func (p *Painter) Fill() { p.render.Fill() } +func (p *Painter) Width() int { + return p.box.Width() +} + +func (p *Painter) Height() int { + return p.box.Height() +} + +func (p *Painter) MeasureText(text string) Box { + return p.render.MeasureText(text) +} + +func (p *Painter) SetStrokeColor(color Color) { + p.render.SetStrokeColor(color) +} + func (p *Painter) LineStroke(points []Point, style LineStyle) { s := style.Style() if !s.ShouldDrawStroke() { diff --git a/painer_test.go b/painter_test.go similarity index 100% rename from painer_test.go rename to painter_test.go diff --git a/util.go b/util.go index c895cc3..d35b4b0 100644 --- a/util.go +++ b/util.go @@ -76,11 +76,11 @@ func autoDivide(max, size int) []int { } // measureTextMaxWidthHeight returns maxWidth and maxHeight of text list -func measureTextMaxWidthHeight(textList []string, r chart.Renderer) (int, int) { +func measureTextMaxWidthHeight(textList []string, p *Painter) (int, int) { maxWidth := 0 maxHeight := 0 for _, text := range textList { - box := r.MeasureText(text) + box := p.MeasureText(text) maxWidth = chart.MaxInt(maxWidth, box.Width()) maxHeight = chart.MaxInt(maxHeight, box.Height()) } diff --git a/xaxis.go b/xaxis.go index edd017f..d79f40e 100644 --- a/xaxis.go +++ b/xaxis.go @@ -24,7 +24,6 @@ package charts import ( "github.com/golang/freetype/truetype" - "github.com/wcharczuk/go-chart/v2" ) type XAxisOption struct { @@ -53,27 +52,19 @@ func NewXAxisOption(data []string, boundaryGap ...*bool) XAxisOption { } // drawXAxis draws x axis, and returns the height, range of if. -func drawXAxis(p *Draw, opt *XAxisOption, yAxisCount int) (int, *Range, error) { +func drawXAxis(p *Painter, opt *XAxisOption, yAxisCount int) (int, *Range, error) { if opt.Hidden { return 0, nil, nil } left := YAxisWidth right := (yAxisCount - 1) * YAxisWidth - dXAxis, err := NewDraw( - DrawOption{ - Parent: p, - }, - PaddingOption(chart.Box{ + pXAxis := p.Child( + PainterPaddingOption(Box{ Left: left, Right: right, }), + PainterFontOption(opt.Font), ) - if opt.Font != nil { - dXAxis.Font = opt.Font - } - if err != nil { - return 0, nil, err - } theme := NewTheme(opt.Theme) data := NewAxisDataListFromStringList(opt.Data) style := AxisOption{ @@ -90,13 +81,13 @@ func drawXAxis(p *Draw, opt *XAxisOption, yAxisCount int) (int, *Range, error) { boundary = false max-- } - axis := NewAxis(dXAxis, data, style) + axis := NewAxis(pXAxis, data, style) axis.Render() return axis.measure().Height, &Range{ divideCount: len(opt.Data), Min: 0, Max: max, - Size: dXAxis.Box.Width(), + Size: pXAxis.Width(), Boundary: boundary, }, nil } diff --git a/yaxis.go b/yaxis.go index a14e409..5d55440 100644 --- a/yaxis.go +++ b/yaxis.go @@ -45,7 +45,7 @@ type YAxisOption struct { // TODO 长度是否可以变化 const YAxisWidth = 40 -func drawYAxis(p *Draw, opt *ChartOption, axisIndex, xAxisHeight int, padding chart.Box) (*Range, error) { +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() @@ -74,32 +74,22 @@ func drawYAxis(p *Draw, opt *ChartOption, axisIndex, xAxisHeight int, padding ch width := NewAxis(p, data, style).measure().Width yAxisCount := len(opt.YAxisList) - boxWidth := p.Box.Width() + boxWidth := p.Width() if axisIndex > 0 { style.SplitLineShow = false style.Position = PositionRight padding.Right += (axisIndex - 1) * YAxisWidth } else { - boxWidth = p.Box.Width() - (yAxisCount-1)*YAxisWidth + boxWidth = p.Width() - (yAxisCount-1)*YAxisWidth padding.Left += (YAxisWidth - width) } - dYAxis, err := NewDraw( - DrawOption{ - Parent: p, - Width: boxWidth, - // 减去x轴的高 - Height: p.Box.Height() - xAxisHeight, - }, - PaddingOption(padding), + pYAxis := p.Child( + PainterWidthHeightOption(boxWidth, p.Height()-xAxisHeight), + PainterPaddingOption(padding), + PainterFontOption(opt.Font), ) - if err != nil { - return nil, err - } - if opt.Font != nil { - dYAxis.Font = opt.Font - } - NewAxis(dYAxis, data, style).Render() - yRange.Size = dYAxis.Box.Height() + NewAxis(pYAxis, data, style).Render() + yRange.Size = pYAxis.Height() return &yRange, nil } From c363d1d5e37e08d01ddee82160ff5a0d4abded71 Mon Sep 17 00:00:00 2001 From: vicanso Date: Mon, 16 May 2022 20:58:41 +0800 Subject: [PATCH 03/21] refactor: reset --- alias.go | 31 + axis.go | 472 ---------- axis_test.go | 259 ------ bar.go | 58 -- bar_chart.go | 163 ---- bar_chart_test.go | 131 --- bar_test.go | 78 -- chart.go | 502 ----------- chart_option.go | 190 ---- chart_option_test.go | 238 ----- chart_test.go | 567 ------------ draw.go | 372 -------- draw_test.go | 507 ----------- echarts.go | 499 ----------- echarts_test.go | 592 ------------- examples/basic/main.go | 94 -- examples/charts/main.go | 1809 --------------------------------------- funnel.go | 141 --- funnel_test.go | 91 -- legend.go | 226 ----- legend_test.go | 185 ---- line.go | 60 +- line_chart.go | 131 --- line_chart_test.go | 97 --- line_test.go | 165 ---- mark_line.go | 92 -- mark_line_test.go | 99 --- mark_point.go | 89 -- mark_point_test.go | 103 --- painter.go | 8 + painter_test.go | 2 +- pie_chart.go | 159 ---- pie_chart_test.go | 69 -- radar_chart.go | 193 ----- radar_chart_test.go | 102 --- range.go | 109 --- range_test.go | 94 -- series.go | 233 ----- series_test.go | 166 ---- table.go | 145 ---- theme.go | 203 +---- theme_test.go | 87 -- title.go | 155 ---- title_test.go | 142 --- util.go | 4 +- util_test.go | 10 +- xaxis.go | 93 -- xaxis_test.go | 108 --- yaxis.go | 95 -- yaxis_test.go | 119 --- 50 files changed, 55 insertions(+), 10282 deletions(-) delete mode 100644 axis.go delete mode 100644 axis_test.go delete mode 100644 bar.go delete mode 100644 bar_chart.go delete mode 100644 bar_chart_test.go delete mode 100644 bar_test.go delete mode 100644 chart.go delete mode 100644 chart_option.go delete mode 100644 chart_option_test.go delete mode 100644 chart_test.go delete mode 100644 draw.go delete mode 100644 draw_test.go delete mode 100644 echarts.go delete mode 100644 echarts_test.go delete mode 100644 examples/basic/main.go delete mode 100644 examples/charts/main.go delete mode 100644 funnel.go delete mode 100644 funnel_test.go delete mode 100644 legend.go delete mode 100644 legend_test.go delete mode 100644 line_chart.go delete mode 100644 line_chart_test.go delete mode 100644 line_test.go delete mode 100644 mark_line.go delete mode 100644 mark_line_test.go delete mode 100644 mark_point.go delete mode 100644 mark_point_test.go delete mode 100644 pie_chart.go delete mode 100644 pie_chart_test.go delete mode 100644 radar_chart.go delete mode 100644 radar_chart_test.go delete mode 100644 range.go delete mode 100644 range_test.go delete mode 100644 series.go delete mode 100644 series_test.go delete mode 100644 table.go delete mode 100644 theme_test.go delete mode 100644 title.go delete mode 100644 title_test.go delete mode 100644 xaxis.go delete mode 100644 xaxis_test.go delete mode 100644 yaxis.go delete mode 100644 yaxis_test.go 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: "\\nMonTueWedThuFriSatSun", - }, - // 文本居中展示 - // axis位于bottom - { - newOption: func() AxisOption { - opt := getDefaultOption() - return opt - }, - result: "\\nMonTueWedThuFriSatSun", - }, - // 文本按起始位置展示 - // axis位于top - { - newOption: func() AxisOption { - opt := getDefaultOption() - opt.Position = PositionTop - opt.BoundaryGap = FalseFlag() - return opt - }, - result: "\\nMonTueWedThuFriSatSun", - }, - // 文本居中展示 - // axis位于top - { - newOption: func() AxisOption { - opt := getDefaultOption() - opt.Position = PositionTop - return opt - }, - result: "\\nMonTueWedThuFriSatSun", - }, - // 文本按起始位置展示 - // axis位于left - { - newOption: func() AxisOption { - opt := getDefaultOption() - opt.Position = PositionLeft - opt.BoundaryGap = FalseFlag() - return opt - }, - result: "\\nMonTueWedThuFriSatSun", - }, - // 文本居中展示 - // axis位于left - { - newOption: func() AxisOption { - opt := getDefaultOption() - opt.Position = PositionLeft - return opt - }, - result: "\\nMonTueWedThuFriSatSun", - }, - // 文本按起始位置展示 - // axis位于right - { - newOption: func() AxisOption { - opt := getDefaultOption() - opt.Position = PositionRight - opt.BoundaryGap = FalseFlag() - return opt - }, - result: "\\nMonTueWedThuFriSatSun", - }, - // 文本居中展示 - // axis位于right - { - newOption: func() AxisOption { - opt := getDefaultOption() - opt.Position = PositionRight - return opt - }, - result: "\\nMonTueWedThuFriSatSun", - }, - // 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: "\\n01-0201-0501-0801-1101-1401-1701-20", - }, - } - 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("\\n20206090", 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("\\n", 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("\\n010203024681012", 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("\\n010203024681012", 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("\\n", 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("\\nABC", 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("\\n(100%)(60%)(20%)", 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("\\n2012201320142015201620170153045607590Milk TeaMatcha LatteCheese CocoaWalnut BrownieMilk Tea: 34.03%Matcha Latte: 27.66%Cheese Cocoa: 22.32%Walnut Brownie: 15.96%SalesAdministrationInformation TechnologyCustomer SupportDevelopmentMarketingAllocated BudgetActual Spending", 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: "\\n", - }, - // circle - { - fn: func(d *Draw) { - d.circle(5, 2, 3) - }, - result: "\\n", - }, - // text - { - fn: func(d *Draw) { - d.text("hello world!", 3, 6) - }, - result: "\\nhello world!", - }, - // line stroke - { - fn: func(d *Draw) { - d.lineStroke([]Point{ - { - X: 1, - Y: 2, - }, - { - X: 3, - Y: 4, - }, - }, LineStyle{ - StrokeColor: drawing.ColorBlack, - StrokeWidth: 1, - }) - }, - result: "\\n", - }, - // set background - { - fn: func(d *Draw) { - d.setBackground(400, 300, chart.ColorWhite) - }, - result: "\\n", - }, - // 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: "\\n", - }, - // 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: "\\n", - }, - // 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: "\\n", - }, - // 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: "\\n", - }, - // 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: "\\n", - }, - // 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: "\\n", - }, - // 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: "\\n", - }, - // 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: "\\n", - }, - // 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: "\\n", - }, - } - 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(`\nHelloWorld!Hello World!`, 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("\\nShow(100%)Click(80%)Visit(60%)Inquiry(40%)Order(20%)", 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: "\\nMonTueWed", - 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: "\\nMonTueWed", - 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: "\\nMonTueWed", - 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: "\\nMonTueWed", - 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: "\\nMonTueWed", - }, - } - - 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("\\n56.66206090", 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("\\n", 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("\\n", 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("\\n", 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("\\n915", 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("\\n51", 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: "\\nhello world!", + result: "\\nhello world!", }, // 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("\\na: 33.33%b: 66.66%c: 0%", 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("\\nSalesAdministrationInformation TechnologyCustomer SupportDevelopmentMarketing", 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: "\\ntitleHellosubtitleWorld!", - box: chart.Box{ - Right: 43, - Bottom: 58, - }, - }, - { - newDraw: newDraw, - newOption: func() *TitleOption { - opt := newOption() - opt.Left = PositionRight - opt.Top = "50" - return opt - }, - result: "\\ntitleHellosubtitleWorld!", - box: chart.Box{ - Right: 400, - Bottom: 108, - }, - }, - { - newDraw: newDraw, - newOption: func() *TitleOption { - opt := newOption() - opt.Left = PositionCenter - opt.Top = "10" - return opt - }, - result: "\\ntitleHellosubtitleWorld!", - box: chart.Box{ - Right: 222, - Bottom: 68, - }, - }, - { - newDraw: newDraw, - newOption: func() *TitleOption { - opt := newOption() - opt.Left = "10%" - opt.Top = "10" - return opt - }, - result: "\\ntitleHellosubtitleWorld!", - 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: "\\nMonTue", - }, - { - 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: "\\n01-0201-0501-08", - }, - } - - 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: "\\n03.336.661013.3316.6620", - }, - { - 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: "\\n0 C3.33 C6.66 C10 C13.33 C16.66 C20 C", - }, - } - - 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)) - } -} From ddd5cf6d4314b254099585d5a51aaef78413b5f8 Mon Sep 17 00:00:00 2001 From: vicanso Date: Mon, 23 May 2022 21:00:10 +0800 Subject: [PATCH 04/21] refactor: enhance painter --- painter.go | 23 ++---- painter_test.go | 22 +++--- theme.go | 198 +++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 215 insertions(+), 28 deletions(-) diff --git a/painter.go b/painter.go index d762e86..47c494f 100644 --- a/painter.go +++ b/painter.go @@ -38,7 +38,7 @@ type Painter struct { parent *Painter style Style previousStyle Style - theme *Theme + theme ColorPalette } type PainterOptions struct { @@ -92,7 +92,7 @@ func PainterStyleOption(style Style) PainterOption { } // PainterThemeOption sets the theme of draw painter -func PainterThemeOption(theme *Theme) PainterOption { +func PainterThemeOption(theme ColorPalette) PainterOption { return func(p *Painter) { if theme == nil { return @@ -194,6 +194,7 @@ func (p *Painter) SetTextStyle(style Style) { func (p *Painter) RestoreStyle() { p.style = p.previousStyle + p.style.WriteToRenderer(p.render) } // Bytes returns the data of draw canvas @@ -336,13 +337,7 @@ func (p *Painter) SetStrokeColor(color Color) { p.render.SetStrokeColor(color) } -func (p *Painter) LineStroke(points []Point, style LineStyle) { - s := style.Style() - if !s.ShouldDrawStroke() { - return - } - defer p.RestoreStyle() - p.SetDrawingStyle(s.GetStrokeOptions()) +func (p *Painter) LineStroke(points []Point) { for index, point := range points { x := point.X y := point.Y @@ -360,8 +355,9 @@ func (p *Painter) SetBackground(width, height int, color Color) { s := chart.Style{ FillColor: color, } - defer p.RestoreStyle() + // 背景色 p.SetStyle(s) + defer p.RestoreStyle() // 设置背景色不使用box,因此不直接使用Painter r.MoveTo(0, 0) r.LineTo(width, 0) @@ -396,13 +392,8 @@ func (p *Painter) Polygon(center Point, radius float64, sides int) { p.Stroke() } -func (p *Painter) FillArea(points []Point, s Style) { - if !s.ShouldDrawFill() { - return - } - defer p.RestoreStyle() +func (p *Painter) FillArea(points []Point) { var x, y int - p.SetDrawingStyle(s.GetFillOptions()) for index, point := range points { x = point.X y = point.Y diff --git a/painter_test.go b/painter_test.go index 1cc08be..34c0c05 100644 --- a/painter_test.go +++ b/painter_test.go @@ -99,6 +99,10 @@ func TestPainter(t *testing.T) { // line stroke { fn: func(p *Painter) { + p.SetDrawingStyle(Style{ + StrokeColor: drawing.ColorBlack, + StrokeWidth: 1, + }) p.LineStroke([]Point{ { X: 1, @@ -108,9 +112,6 @@ func TestPainter(t *testing.T) { X: 3, Y: 4, }, - }, LineStyle{ - StrokeColor: drawing.ColorBlack, - StrokeWidth: 1, }) }, result: "\\n", @@ -294,6 +295,14 @@ func TestPainter(t *testing.T) { // FillArea { fn: func(p *Painter) { + p.SetDrawingStyle(Style{ + FillColor: drawing.Color{ + R: 84, + G: 112, + B: 198, + A: 255, + }, + }) p.FillArea([]Point{ { X: 0, @@ -311,13 +320,6 @@ func TestPainter(t *testing.T) { X: 0, Y: 0, }, - }, Style{ - FillColor: drawing.Color{ - R: 84, - G: 112, - B: 198, - A: 255, - }, }) }, result: "\\n", diff --git a/theme.go b/theme.go index 88c73df..bb05249 100644 --- a/theme.go +++ b/theme.go @@ -22,13 +22,20 @@ package charts +import "github.com/wcharczuk/go-chart/v2/drawing" + const ThemeDark = "dark" const ThemeLight = "light" const ThemeGrafana = "grafana" const ThemeAnt = "ant" -type Theme struct { - palette *themeColorPalette +type ColorPalette interface { + IsDark() bool + GetAxisStrokeColor() Color + GetAxisSplitLineColor() Color + GetSeriesColor(int) Color + GetBackgroundColor() Color + GetTextColor() Color } type themeColorPalette struct { @@ -39,3 +46,190 @@ type themeColorPalette struct { textColor Color seriesColors []Color } + +var palettes = map[string]ColorPalette{} + +func init() { + echartSeriesColors := []Color{ + parseColor("#5470c6"), + parseColor("#91cc75"), + parseColor("#fac858"), + parseColor("#ee6666"), + parseColor("#73c0de"), + parseColor("#3ba272"), + parseColor("#fc8452"), + parseColor("#9a60b4"), + parseColor("#ea7ccc"), + } + grafanaSeriesColors := []Color{ + parseColor("#7EB26D"), + parseColor("#EAB839"), + parseColor("#6ED0E0"), + parseColor("#EF843C"), + parseColor("#E24D42"), + parseColor("#1F78C1"), + parseColor("#705DA0"), + parseColor("#508642"), + } + antSeriesColors := []Color{ + parseColor("#5b8ff9"), + parseColor("#5ad8a6"), + parseColor("#5d7092"), + parseColor("#f6bd16"), + parseColor("#6f5ef9"), + parseColor("#6dc8ec"), + parseColor("#945fb9"), + parseColor("#ff9845"), + } + AddTheme( + ThemeDark, + true, + Color{ + R: 185, + G: 184, + B: 206, + A: 255, + }, + Color{ + R: 72, + G: 71, + B: 83, + A: 255, + }, + Color{ + R: 16, + G: 12, + B: 42, + A: 255, + }, + Color{ + R: 238, + G: 238, + B: 238, + A: 255, + }, + echartSeriesColors, + ) + + AddTheme( + ThemeLight, + false, + Color{ + R: 110, + G: 112, + B: 121, + A: 255, + }, + 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, + Color{ + R: 110, + G: 112, + B: 121, + A: 255, + }, + 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) ColorPalette { + p, ok := palettes[name] + if !ok { + p = palettes[ThemeLight] + } + return p +} + +func (t *themeColorPalette) IsDark() bool { + return t.isDarkMode +} + +func (t *themeColorPalette) GetAxisStrokeColor() Color { + return t.axisStrokeColor +} + +func (t *themeColorPalette) GetAxisSplitLineColor() Color { + return t.axisSplitLineColor +} + +func (t *themeColorPalette) GetSeriesColor(index int) Color { + colors := t.seriesColors + return colors[index%len(colors)] +} + +func (t *themeColorPalette) GetBackgroundColor() Color { + return t.backgroundColor +} + +func (t *themeColorPalette) GetTextColor() Color { + return t.textColor +} From 4201c7d4393caec7f6c15725ead9602274e41d41 Mon Sep 17 00:00:00 2001 From: vicanso Date: Tue, 24 May 2022 23:25:08 +0800 Subject: [PATCH 05/21] chore: support axias ticks render --- painter.go | 217 +++++++++++++++++++++++++++++++++++------------- painter_test.go | 2 +- util.go | 15 ++-- 3 files changed, 165 insertions(+), 69 deletions(-) diff --git a/painter.go b/painter.go index 47c494f..971a028 100644 --- a/painter.go +++ b/painter.go @@ -32,13 +32,12 @@ import ( ) type Painter struct { - render Renderer - box Box - font *truetype.Font - parent *Painter - style Style - previousStyle Style - theme ColorPalette + render Renderer + box Box + font *truetype.Font + parent *Painter + style Style + theme ColorPalette } type PainterOptions struct { @@ -54,6 +53,12 @@ type PainterOptions struct { type PainterOption func(*Painter) +type TicksOption struct { + Length int + Orient string + Count int +} + // PainterPaddingOption sets the padding of draw painter func PainterPaddingOption(padding Box) PainterOption { return func(p *Painter) { @@ -87,7 +92,7 @@ func PainterFontOption(font *truetype.Font) PainterOption { // PainterStyleOption sets the style of draw painter func PainterStyleOption(style Style) PainterOption { return func(p *Painter) { - p.SetDrawingStyle(style) + p.SetStyle(style) } } @@ -156,13 +161,12 @@ func (p *Painter) setOptions(opts ...PainterOption) { func (p *Painter) Child(opt ...PainterOption) *Painter { child := &Painter{ - render: p.render, - box: p.box.Clone(), - font: p.font, - parent: p, - style: p.style, - previousStyle: p.previousStyle, - theme: p.theme, + render: p.render, + box: p.box.Clone(), + font: p.font, + parent: p, + style: p.style, + theme: p.theme, } child.setOptions(opt...) return child @@ -172,29 +176,65 @@ 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) } -func (p *Painter) SetDrawingStyle(style Style) { - p.previousStyle = p.style - p.style = style - style.WriteDrawingOptionsToRenderer(p.render) -} - -func (p *Painter) SetTextStyle(style Style) { - if style.Font == nil { - style.Font = p.font +func overrideStyle(defaultStyle Style, style Style) Style { + if style.StrokeWidth == 0 { + style.StrokeWidth = defaultStyle.StrokeWidth } - p.previousStyle = p.style - p.style = style - style.WriteTextOptionsToRenderer(p.render) + if style.StrokeColor.IsZero() { + style.StrokeColor = defaultStyle.StrokeColor + } + if style.StrokeDashArray == nil { + style.StrokeDashArray = defaultStyle.StrokeDashArray + } + if style.DotColor.IsZero() { + style.DotColor = defaultStyle.DotColor + } + if style.DotWidth == 0 { + style.DotWidth = defaultStyle.DotWidth + } + if style.FillColor.IsZero() { + style.FillColor = defaultStyle.FillColor + } + if style.FontSize == 0 { + style.FontSize = defaultStyle.FontSize + } + if style.FontColor.IsZero() { + style.FontColor = defaultStyle.FontColor + } + if style.Font == nil { + style.Font = defaultStyle.Font + } + return style } -func (p *Painter) RestoreStyle() { - p.style = p.previousStyle +func (p *Painter) OverrideDrawingStyle(style Style) *Painter { + s := overrideStyle(p.style, style) + p.SetDrawingStyle(s) + return p +} + +func (p *Painter) SetDrawingStyle(style Style) *Painter { + style.WriteDrawingOptionsToRenderer(p.render) + return p +} + +func (p *Painter) SetTextStyle(style Style) *Painter { + style.WriteTextOptionsToRenderer(p.render) + return p +} +func (p *Painter) OverrideTextStyle(style Style) *Painter { + s := overrideStyle(p.style, style) + p.SetTextStyle(s) + return p +} + +func (p *Painter) ResetStyle() *Painter { p.style.WriteToRenderer(p.render) + return p } // Bytes returns the data of draw canvas @@ -208,19 +248,22 @@ func (p *Painter) Bytes() ([]byte, error) { } // MoveTo moves the cursor to a given point -func (p *Painter) MoveTo(x, y int) { +func (p *Painter) MoveTo(x, y int) *Painter { p.render.MoveTo(x+p.box.Left, y+p.box.Top) + return p } -func (p *Painter) ArcTo(cx, cy int, rx, ry, startAngle, delta float64) { +func (p *Painter) ArcTo(cx, cy int, rx, ry, startAngle, delta float64) *Painter { p.render.ArcTo(cx+p.box.Left, cy+p.box.Top, rx, ry, startAngle, delta) + return p } -func (p *Painter) LineTo(x, y int) { +func (p *Painter) LineTo(x, y int) *Painter { p.render.LineTo(x+p.box.Left, y+p.box.Top) + return p } -func (p *Painter) Pin(x, y, width int) { +func (p *Painter) Pin(x, y, width int) *Painter { r := float64(width) / 2 y -= width / 4 angle := chart.DegreesToRadians(15) @@ -246,9 +289,10 @@ func (p *Painter) Pin(x, y, width int) { p.render.QuadCurveTo(cx+left, cy+top, endX+left, endY+top) p.Close() p.Fill() + return p } -func (p *Painter) arrow(x, y, width, height int, direction string) { +func (p *Painter) arrow(x, y, width, height int, direction string) *Painter { halfWidth := width >> 1 halfHeight := height >> 1 if direction == PositionTop || direction == PositionBottom { @@ -284,41 +328,51 @@ func (p *Painter) arrow(x, y, width, height int, direction string) { p.LineTo(x0, y0) } p.FillStroke() + return p } -func (p *Painter) ArrowLeft(x, y, width, height int) { +func (p *Painter) ArrowLeft(x, y, width, height int) *Painter { p.arrow(x, y, width, height, PositionLeft) + return p } -func (p *Painter) ArrowRight(x, y, width, height int) { +func (p *Painter) ArrowRight(x, y, width, height int) *Painter { p.arrow(x, y, width, height, PositionRight) + return p } -func (p *Painter) ArrowTop(x, y, width, height int) { +func (p *Painter) ArrowTop(x, y, width, height int) *Painter { p.arrow(x, y, width, height, PositionTop) + return p } -func (p *Painter) ArrowBottom(x, y, width, height int) { +func (p *Painter) ArrowBottom(x, y, width, height int) *Painter { p.arrow(x, y, width, height, PositionBottom) + return p } -func (p *Painter) Circle(radius float64, x, y int) { +func (p *Painter) Circle(radius float64, x, y int) *Painter { p.render.Circle(radius, x+p.box.Left, y+p.box.Top) + return p } -func (p *Painter) Stroke() { +func (p *Painter) Stroke() *Painter { p.render.Stroke() + return p } -func (p *Painter) Close() { +func (p *Painter) Close() *Painter { p.render.Close() + return p } -func (p *Painter) FillStroke() { +func (p *Painter) FillStroke() *Painter { p.render.FillStroke() + return p } -func (p *Painter) Fill() { +func (p *Painter) Fill() *Painter { p.render.Fill() + return p } func (p *Painter) Width() int { @@ -333,11 +387,7 @@ func (p *Painter) MeasureText(text string) Box { return p.render.MeasureText(text) } -func (p *Painter) SetStrokeColor(color Color) { - p.render.SetStrokeColor(color) -} - -func (p *Painter) LineStroke(points []Point) { +func (p *Painter) LineStroke(points []Point) *Painter { for index, point := range points { x := point.X y := point.Y @@ -348,16 +398,17 @@ func (p *Painter) LineStroke(points []Point) { } } p.Stroke() + return p } -func (p *Painter) SetBackground(width, height int, color Color) { +func (p *Painter) SetBackground(width, height int, color Color) *Painter { r := p.render s := chart.Style{ FillColor: color, } // 背景色 - p.SetStyle(s) - defer p.RestoreStyle() + p.SetDrawingStyle(s) + defer p.ResetStyle() // 设置背景色不使用box,因此不直接使用Painter r.MoveTo(0, 0) r.LineTo(width, 0) @@ -365,21 +416,23 @@ func (p *Painter) SetBackground(width, height int, color Color) { r.LineTo(0, height) r.LineTo(0, 0) p.FillStroke() + return p } -func (p *Painter) MarkLine(x, y, width int) { +func (p *Painter) MarkLine(x, y, width int) *Painter { arrowWidth := 16 arrowHeight := 10 endX := x + width - p.Circle(3, x, y) + radius := 3 + p.Circle(3, x+radius, y) p.render.Fill() - p.MoveTo(x+5, y) + p.MoveTo(x+radius*3, y) p.LineTo(endX-arrowWidth, y) p.Stroke() - p.render.SetStrokeDashArray([]float64{}) p.ArrowRight(endX, y, arrowWidth, arrowHeight) + return p } -func (p *Painter) Polygon(center Point, radius float64, sides int) { +func (p *Painter) Polygon(center Point, radius float64, sides int) *Painter { points := getPolygonPoints(center, radius, sides) for i, item := range points { if i == 0 { @@ -390,9 +443,10 @@ func (p *Painter) Polygon(center Point, radius float64, sides int) { } p.LineTo(points[0].X, points[0].Y) p.Stroke() + return p } -func (p *Painter) FillArea(points []Point) { +func (p *Painter) FillArea(points []Point) *Painter { var x, y int for index, point := range points { x = point.X @@ -404,10 +458,12 @@ func (p *Painter) FillArea(points []Point) { } } p.Fill() + return p } -func (p *Painter) Text(body string, x, y int) { +func (p *Painter) Text(body string, x, y int) *Painter { p.render.Text(body, x+p.box.Left, y+p.box.Top) + return p } func (p *Painter) TextFit(body string, x, y, width int) chart.Box { @@ -433,3 +489,46 @@ func (p *Painter) TextFit(body string, x, y, width int) chart.Box { p.style.TextWrap = textWarp return output } + +func (p *Painter) Ticks(opt TicksOption) *Painter { + if opt.Count <= 0 || opt.Length <= 0 { + return p + } + count := opt.Count - 1 + width := p.Width() + height := p.Height() + var values []int + if opt.Orient == OrientHorizontal { + values = autoDivide(height, count) + } else { + values = autoDivide(width, count) + } + + for _, value := range values { + if opt.Orient == OrientVertical { + p.LineStroke([]Point{ + { + X: 0, + Y: value, + }, + { + X: opt.Length, + Y: value, + }, + }) + } else { + p.LineStroke([]Point{ + { + X: value, + Y: opt.Length, + }, + { + X: value, + Y: 0, + }, + }) + } + } + + return p +} diff --git a/painter_test.go b/painter_test.go index 34c0c05..c847aff 100644 --- a/painter_test.go +++ b/painter_test.go @@ -271,7 +271,7 @@ func TestPainter(t *testing.T) { }) p.MarkLine(0, 20, 300) }, - result: "\\n", + result: "\\n", }, // polygon { diff --git a/util.go b/util.go index 5fee163..7306919 100644 --- a/util.go +++ b/util.go @@ -59,19 +59,16 @@ func getDefaultInt(value, defaultValue int) int { } func autoDivide(max, size int) []int { - unit := max / size + unit := float64(max) / float64(size) - rest := max - unit*size values := make([]int, size+1) - value := 0 - for i := 0; i < size; i++ { - values[i] = value - if i < rest { - value++ + for i := 0; i < size+1; i++ { + if i == size { + values[i] = max + } else { + values[i] = int(float64(i) * unit) } - value += unit } - values[size] = max return values } From 1dcd50ba9a06c83f57414e017bdae1d4718d71b0 Mon Sep 17 00:00:00 2001 From: vicanso Date: Wed, 25 May 2022 23:09:33 +0800 Subject: [PATCH 06/21] feat: support multi text draw --- painter.go | 74 +++++++++++++++++++++++++++++++++++++++++++++++----- util_test.go | 12 ++++----- 2 files changed, 74 insertions(+), 12 deletions(-) diff --git a/painter.go b/painter.go index 971a028..37c60bd 100644 --- a/painter.go +++ b/painter.go @@ -57,6 +57,15 @@ type TicksOption struct { Length int Orient string Count int + Unit int +} + +type MultiTextOption struct { + TextList []string + Orient string + Unit int + Position string + Align string } // PainterPaddingOption sets the padding of draw painter @@ -223,6 +232,9 @@ func (p *Painter) SetDrawingStyle(style Style) *Painter { } func (p *Painter) SetTextStyle(style Style) *Painter { + if style.Font == nil { + style.Font = p.font + } style.WriteTextOptionsToRenderer(p.render) return p } @@ -494,18 +506,25 @@ func (p *Painter) Ticks(opt TicksOption) *Painter { if opt.Count <= 0 || opt.Length <= 0 { return p } - count := opt.Count - 1 + count := opt.Count width := p.Width() height := p.Height() + unit := 1 + if opt.Unit > 1 { + unit = opt.Unit + } var values []int - if opt.Orient == OrientHorizontal { + isVertical := opt.Orient == OrientVertical + if isVertical { values = autoDivide(height, count) } else { values = autoDivide(width, count) } - - for _, value := range values { - if opt.Orient == OrientVertical { + for index, value := range values { + if index%unit != 0 { + continue + } + if isVertical { p.LineStroke([]Point{ { X: 0, @@ -529,6 +548,49 @@ func (p *Painter) Ticks(opt TicksOption) *Painter { }) } } - + return p +} + +func (p *Painter) MultiText(opt MultiTextOption) *Painter { + if len(opt.TextList) == 0 { + return p + } + count := len(opt.TextList) + positionCenter := true + if opt.Position == PositionLeft { + positionCenter = false + } + width := p.Width() + height := p.Height() + var values []int + isVertical := opt.Orient == OrientVertical + if isVertical { + values = autoDivide(height, count) + } else { + values = autoDivide(width, count) + } + for index, text := range opt.TextList { + box := p.MeasureText(text) + start := values[index] + if positionCenter { + start = (values[index] + values[index+1]) >> 1 + } + x := 0 + y := 0 + if isVertical { + y = start - box.Height()>>1 + switch opt.Align { + case AlignRight: + x = width - box.Width() + case AlignCenter: + x = width - box.Width()>>1 + default: + x = 0 + } + } else { + x = start - box.Width()>>1 + } + p.Text(text, x, y) + } return p } diff --git a/util_test.go b/util_test.go index fefbabc..b25c60d 100644 --- a/util_test.go +++ b/util_test.go @@ -60,12 +60,12 @@ func TestAutoDivide(t *testing.T) { assert.Equal([]int{ 0, - 86, - 172, - 258, - 344, - 430, - 515, + 85, + 171, + 257, + 342, + 428, + 514, 600, }, autoDivide(600, 7)) } From 7e4de64a0ddca62a275c47f8085b202cc92d1f05 Mon Sep 17 00:00:00 2001 From: vicanso Date: Thu, 26 May 2022 23:21:02 +0800 Subject: [PATCH 07/21] feat: support grid render function --- alias.go | 6 ++++ painter.go | 86 +++++++++++++++++++++++++++++++++++++++++++++++++++++- util.go | 18 ++++++++++++ 3 files changed, 109 insertions(+), 1 deletion(-) diff --git a/alias.go b/alias.go index 3bacc67..d19c1f9 100644 --- a/alias.go +++ b/alias.go @@ -58,6 +58,12 @@ const ( PositionBottom = "bottom" ) +const ( + AlignLeft = "left" + AlignRight = "right" + AlignCenter = "center" +) + const ( OrientHorizontal = "horizontal" OrientVertical = "vertical" diff --git a/painter.go b/painter.go index 37c60bd..0120d68 100644 --- a/painter.go +++ b/painter.go @@ -68,6 +68,15 @@ type MultiTextOption struct { Align string } +type GridOption struct { + Column int + Row int + // 忽略不展示的column + IgnoreColumnLines []int + // 忽略不展示的row + IgnoreRowLines []int +} + // PainterPaddingOption sets the padding of draw painter func PainterPaddingOption(padding Box) PainterOption { return func(p *Painter) { @@ -275,6 +284,11 @@ func (p *Painter) LineTo(x, y int) *Painter { return p } +func (p *Painter) QuadCurveTo(cx, cy, x, y int) *Painter { + p.render.QuadCurveTo(cx+p.box.Left, cy+p.box.Top, x+p.box.Left, y+p.box.Top) + return p +} + func (p *Painter) Pin(x, y, width int) *Painter { r := float64(width) / 2 y -= width / 4 @@ -413,6 +427,27 @@ func (p *Painter) LineStroke(points []Point) *Painter { return p } +func (p *Painter) SmoothLineStroke(points []Point) *Painter { + prevX := 0 + prevY := 0 + // TODO 如何生成平滑的折线 + for index, point := range points { + x := point.X + y := point.Y + if index == 0 { + p.MoveTo(x, y) + } else { + cx := prevX + (x-prevX)/5 + cy := y + (y-prevY)/2 + p.QuadCurveTo(cx, cy, x, y) + } + prevX = x + prevY = y + } + p.Stroke() + return p +} + func (p *Painter) SetBackground(width, height int, color Color) *Painter { r := p.render s := chart.Style{ @@ -557,8 +592,12 @@ func (p *Painter) MultiText(opt MultiTextOption) *Painter { } count := len(opt.TextList) positionCenter := true - if opt.Position == PositionLeft { + if containsString([]string{ + PositionLeft, + PositionTop, + }, opt.Position) { positionCenter = false + count-- } width := p.Width() height := p.Height() @@ -594,3 +633,48 @@ func (p *Painter) MultiText(opt MultiTextOption) *Painter { } return p } + +func (p *Painter) Grid(opt GridOption) *Painter { + width := p.Width() + height := p.Height() + drawLines := func(values []int, ignoreIndexList []int, isVertical bool) { + for index, v := range values { + if containsInt(ignoreIndexList, index) { + continue + } + x0 := 0 + y0 := 0 + x1 := 0 + y1 := 0 + if isVertical { + + x0 = v + x1 = v + y1 = height + } else { + x1 = width + y0 = v + y1 = v + } + p.LineStroke([]Point{ + { + X: x0, + Y: y0, + }, + { + X: x1, + Y: y1, + }, + }) + } + } + if opt.Column > 0 { + values := autoDivide(width, opt.Column) + drawLines(values, opt.IgnoreColumnLines, true) + } + if opt.Row > 0 { + values := autoDivide(height, opt.Row) + drawLines(values, opt.IgnoreRowLines, false) + } + return p +} diff --git a/util.go b/util.go index 7306919..adfa9fd 100644 --- a/util.go +++ b/util.go @@ -43,6 +43,24 @@ func FalseFlag() *bool { return &f } +func containsInt(values []int, value int) bool { + for _, v := range values { + if v == value { + return true + } + } + return false +} + +func containsString(values []string, value string) bool { + for _, v := range values { + if v == value { + return true + } + } + return false +} + func ceilFloatToInt(value float64) int { i := int(value) if value == float64(i) { From 8314a2cb372e8ba852c841969b822b3bfbcb1f91 Mon Sep 17 00:00:00 2001 From: vicanso Date: Tue, 31 May 2022 20:25:14 +0800 Subject: [PATCH 08/21] feat: support dots render function --- examples/painter/main.go | 384 +++++++++++++++++++++++++++++++++++++++ line.go | 46 ----- painter.go | 9 + 3 files changed, 393 insertions(+), 46 deletions(-) create mode 100644 examples/painter/main.go delete mode 100644 line.go diff --git a/examples/painter/main.go b/examples/painter/main.go new file mode 100644 index 0000000..d0eec73 --- /dev/null +++ b/examples/painter/main.go @@ -0,0 +1,384 @@ +package main + +import ( + "io/ioutil" + "os" + "path/filepath" + + charts "github.com/vicanso/go-charts" + "github.com/wcharczuk/go-chart/v2/drawing" +) + +func writeFile(buf []byte) error { + tmpPath := "./tmp" + err := os.MkdirAll(tmpPath, 0700) + if err != nil { + return err + } + + file := filepath.Join(tmpPath, "painter.png") + err = ioutil.WriteFile(file, buf, 0600) + if err != nil { + return err + } + return nil +} + +func main() { + p, err := charts.NewPainter(charts.PainterOptions{ + Width: 400, + Height: 1200, + Type: charts.ChartOutputPNG, + }) + if err != nil { + panic(err) + } + // 背景色 + p.SetBackground(p.Width(), p.Height(), drawing.ColorWhite) + + top := 0 + + // 画线 + p.SetDrawingStyle(charts.Style{ + StrokeColor: drawing.ColorBlack, + FillColor: drawing.ColorBlack, + StrokeWidth: 1, + }) + p.LineStroke([]charts.Point{ + { + X: 0, + Y: 0, + }, + { + X: 100, + Y: 10, + }, + { + X: 200, + Y: 0, + }, + { + X: 300, + Y: 10, + }, + }) + + // 圆滑曲线 + // top += 50 + // p.Child(charts.PainterPaddingOption(charts.Box{ + // Top: top, + // })).SetDrawingStyle(charts.Style{ + // StrokeColor: drawing.ColorBlack, + // FillColor: drawing.ColorBlack, + // StrokeWidth: 1, + // }).SmoothLineStroke([]charts.Point{ + // { + // X: 0, + // Y: 0, + // }, + // { + // X: 100, + // Y: 10, + // }, + // { + // X: 200, + // Y: 0, + // }, + // { + // X: 300, + // Y: 10, + // }, + // }) + + // 标线 + top += 50 + p.Child(charts.PainterPaddingOption(charts.Box{ + Top: top, + })).SetDrawingStyle(charts.Style{ + StrokeColor: drawing.ColorBlack, + FillColor: drawing.ColorBlack, + StrokeWidth: 1, + StrokeDashArray: []float64{ + 4, + 2, + }, + }).MarkLine(0, 0, p.Width()) + + top += 60 + // Polygon + p.Child(charts.PainterBoxOption(charts.Box{ + Top: top, + })).SetDrawingStyle(charts.Style{ + StrokeColor: drawing.ColorBlack, + FillColor: drawing.ColorBlack, + StrokeWidth: 1, + }).Polygon(charts.Point{ + X: 100, + Y: 0, + }, 50, 6) + + // FillArea + top += 60 + p.Child(charts.PainterPaddingOption(charts.Box{ + Top: top, + })).SetDrawingStyle(charts.Style{ + FillColor: drawing.ColorBlack, + }).FillArea([]charts.Point{ + { + X: 0, + Y: 0, + }, + { + X: 100, + Y: 0, + }, + { + X: 150, + Y: 40, + }, + { + X: 80, + Y: 30, + }, + { + X: 0, + Y: 0, + }, + }) + + // 坐标轴的点 + top += 50 + p.Child( + charts.PainterBoxOption(charts.Box{ + Top: top, + Left: 1, + Right: p.Width() - 1, + Bottom: 20, + }), + ).SetDrawingStyle(charts.Style{ + StrokeColor: drawing.ColorBlack, + FillColor: drawing.ColorBlack, + StrokeWidth: 1, + }).Ticks(charts.TicksOption{ + Count: 7, + Length: 5, + }) + + // 坐标轴的点,每2格显示一个 + top += 20 + p.Child( + charts.PainterBoxOption(charts.Box{ + Top: top, + Left: 1, + Right: p.Width() - 1, + Bottom: 20, + }), + ).SetDrawingStyle(charts.Style{ + StrokeColor: drawing.ColorBlack, + FillColor: drawing.ColorBlack, + StrokeWidth: 1, + }).Ticks(charts.TicksOption{ + Unit: 2, + Count: 7, + Length: 5, + }) + + // 坐标轴的点,纵向 + top += 20 + p.Child( + charts.PainterBoxOption(charts.Box{ + Top: top, + Left: 1, + Right: p.Width() - 1, + Bottom: top + 100, + }), + ).SetDrawingStyle(charts.Style{ + StrokeColor: drawing.ColorBlack, + FillColor: drawing.ColorBlack, + StrokeWidth: 1, + }).Ticks(charts.TicksOption{ + Orient: charts.OrientVertical, + Count: 7, + Length: 5, + }) + + // 横向展示文本 + top += 120 + p.Child( + charts.PainterBoxOption(charts.Box{ + Top: top, + Left: 1, + Right: p.Width() - 1, + Bottom: 20, + }), + ).OverrideTextStyle(charts.Style{ + FontColor: drawing.ColorBlack, + FontSize: 10, + }).MultiText(charts.MultiTextOption{ + TextList: []string{ + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + "Sun", + }, + }) + + // 横向显示文本,靠左 + top += 20 + p.Child( + charts.PainterBoxOption(charts.Box{ + Top: top, + Left: 1, + Right: p.Width() - 1, + Bottom: 20, + }), + ).OverrideTextStyle(charts.Style{ + FontColor: drawing.ColorBlack, + FontSize: 10, + }).MultiText(charts.MultiTextOption{ + Position: charts.PositionLeft, + TextList: []string{ + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + "Sun", + }, + }) + + // 纵向显示文本 + top += 20 + p.Child( + charts.PainterBoxOption(charts.Box{ + Top: top, + Left: 1, + Right: 50, + Bottom: top + 150, + }), + ).OverrideTextStyle(charts.Style{ + FontColor: drawing.ColorBlack, + FontSize: 10, + }).MultiText(charts.MultiTextOption{ + Orient: charts.OrientVertical, + Align: charts.AlignRight, + TextList: []string{ + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + "Sun", + }, + }) + // 纵向 文本居中 + p.Child( + charts.PainterBoxOption(charts.Box{ + Top: top, + Left: 50, + Right: 100, + Bottom: top + 150, + }), + ).OverrideTextStyle(charts.Style{ + FontColor: drawing.ColorBlack, + FontSize: 10, + }).MultiText(charts.MultiTextOption{ + Orient: charts.OrientVertical, + Align: charts.AlignCenter, + TextList: []string{ + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + "Sun", + }, + }) + // 纵向 文本置顶 + p.Child( + charts.PainterBoxOption(charts.Box{ + Top: top, + Left: 100, + Right: 150, + Bottom: top + 150, + }), + ).OverrideTextStyle(charts.Style{ + FontColor: drawing.ColorBlack, + FontSize: 10, + }).MultiText(charts.MultiTextOption{ + Orient: charts.OrientVertical, + Position: charts.PositionTop, + Align: charts.AlignCenter, + TextList: []string{ + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + "Sun", + }, + }) + + // grid + top += 150 + p.Child( + charts.PainterBoxOption(charts.Box{ + Top: top, + Left: 1, + Right: p.Width() - 1, + Bottom: top + 100, + }), + ).OverrideTextStyle(charts.Style{ + FontColor: drawing.ColorBlack, + FontSize: 10, + }).Grid(charts.GridOption{ + Column: 8, + IgnoreColumnLines: []int{0, 8}, + Row: 8, + IgnoreRowLines: []int{0, 8}, + }) + + // dots + top += 100 + p.Child( + charts.PainterBoxOption(charts.Box{ + Top: top, + Left: 1, + Right: p.Width() - 1, + Bottom: top + 20, + }), + ).OverrideDrawingStyle(charts.Style{ + FillColor: drawing.ColorWhite, + StrokeColor: drawing.ColorBlack, + StrokeWidth: 1, + }).Dots([]charts.Point{ + { + X: 0, + Y: 0, + }, + { + X: 50, + Y: 0, + }, + { + X: 100, + Y: 10, + }, + }) + + buf, err := p.Bytes() + if err != nil { + panic(err) + } + err = writeFile(buf) + if err != nil { + panic(err) + } +} diff --git a/line.go b/line.go deleted file mode 100644 index e4b1f18..0000000 --- a/line.go +++ /dev/null @@ -1,46 +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 - -type LineStyle struct { - ClassName string - StrokeDashArray []float64 - StrokeColor Color - StrokeWidth float64 - FillColor Color - DotWidth float64 - DotColor Color - DotFillColor Color -} - -func (ls *LineStyle) Style() Style { - return Style{ - ClassName: ls.ClassName, - StrokeDashArray: ls.StrokeDashArray, - StrokeColor: ls.StrokeColor, - StrokeWidth: ls.StrokeWidth, - FillColor: ls.FillColor, - DotWidth: ls.DotWidth, - DotColor: ls.DotColor, - } -} diff --git a/painter.go b/painter.go index 0120d68..78d8925 100644 --- a/painter.go +++ b/painter.go @@ -124,6 +124,7 @@ func PainterThemeOption(theme ColorPalette) PainterOption { } } +// PainterWidthHeightOption set width or height of draw painter func PainterWidthHeightOption(width, height int) PainterOption { return func(p *Painter) { if width > 0 { @@ -678,3 +679,11 @@ func (p *Painter) Grid(opt GridOption) *Painter { } return p } + +func (p *Painter) Dots(points []Point) *Painter { + for _, item := range points { + p.Circle(5, item.X, item.Y) + } + p.FillStroke() + return p +} From 622bd8491b778aeeec7209b76e36f93415ad88ac Mon Sep 17 00:00:00 2001 From: vicanso Date: Wed, 1 Jun 2022 20:27:46 +0800 Subject: [PATCH 09/21] feat: support rect and legend line point render --- examples/painter/main.go | 36 ++++++++++++++++++++++++++++++++++++ painter.go | 26 ++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/examples/painter/main.go b/examples/painter/main.go index d0eec73..8b0a157 100644 --- a/examples/painter/main.go +++ b/examples/painter/main.go @@ -373,6 +373,42 @@ func main() { }, }) + // rect + top += 30 + p.Child( + charts.PainterBoxOption(charts.Box{ + Top: top, + Left: 1, + Right: 200, + Bottom: top + 50, + }), + ).OverrideDrawingStyle(charts.Style{ + StrokeColor: drawing.ColorBlack, + FillColor: drawing.ColorBlack, + }).Rect(charts.Box{ + Left: 10, + Top: 0, + Right: 110, + Bottom: 20, + }) + // legend line dot + p.Child( + charts.PainterBoxOption(charts.Box{ + Top: top, + Left: 200, + Right: p.Width() - 1, + Bottom: top + 50, + }), + ).OverrideDrawingStyle(charts.Style{ + StrokeColor: drawing.ColorBlack, + FillColor: drawing.ColorBlack, + }).LegendLineDot(charts.Box{ + Left: 10, + Top: 0, + Right: 50, + Bottom: 20, + }) + buf, err := p.Bytes() if err != nil { panic(err) diff --git a/painter.go b/painter.go index 78d8925..95469f3 100644 --- a/painter.go +++ b/painter.go @@ -687,3 +687,29 @@ func (p *Painter) Dots(points []Point) *Painter { p.FillStroke() return p } + +func (p *Painter) Rect(box Box) *Painter { + p.MoveTo(box.Left, box.Top) + p.LineTo(box.Right, box.Top) + p.LineTo(box.Right, box.Bottom) + p.LineTo(box.Left, box.Bottom) + p.LineTo(box.Left, box.Top) + p.FillStroke() + return p +} + +func (p *Painter) LegendLineDot(box Box) *Painter { + width := box.Width() + height := box.Height() + strokeWidth := 3 + dotHeight := 5 + + p.render.SetStrokeWidth(float64(strokeWidth)) + center := (height - strokeWidth) >> 1 + p.MoveTo(box.Left, box.Top+center) + p.LineTo(box.Right, box.Top+center) + p.Stroke() + p.Circle(float64(dotHeight), box.Left+width>>1, center) + p.FillStroke() + return p +} From 7ee13fe9143b34280b12af28efc52602b9482fb8 Mon Sep 17 00:00:00 2001 From: vicanso Date: Fri, 3 Jun 2022 21:06:40 +0800 Subject: [PATCH 10/21] chore: supper grid renderer --- alias.go | 1 - charts.go | 27 ++++++++++++++ examples/painter/main.go | 13 +++++++ grid.go | 80 ++++++++++++++++++++++++++++++++++++++++ painter.go | 2 +- 5 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 charts.go create mode 100644 grid.go diff --git a/alias.go b/alias.go index d19c1f9..0b161e6 100644 --- a/alias.go +++ b/alias.go @@ -28,7 +28,6 @@ import ( ) type Box = chart.Box -type Renderer = chart.Renderer type Style = chart.Style type Color = drawing.Color diff --git a/charts.go b/charts.go new file mode 100644 index 0000000..445dd7e --- /dev/null +++ b/charts.go @@ -0,0 +1,27 @@ +// 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 + +type Renderer interface { + Render() (Box, error) +} diff --git a/examples/painter/main.go b/examples/painter/main.go index 8b0a157..4614f10 100644 --- a/examples/painter/main.go +++ b/examples/painter/main.go @@ -409,6 +409,19 @@ func main() { Bottom: 20, }) + top += 50 + charts.NewGridPainter(p.Child(charts.PainterBoxOption(charts.Box{ + Top: top, + Left: 1, + Right: p.Width() - 1, + Bottom: top + 100, + })), charts.GridPainterOption{ + Row: 5, + IgnoreFirstRow: true, + IgnoreLastRow: true, + StrokeColor: drawing.ColorBlue, + }).Render() + buf, err := p.Bytes() if err != nil { panic(err) diff --git a/grid.go b/grid.go new file mode 100644 index 0000000..1a00381 --- /dev/null +++ b/grid.go @@ -0,0 +1,80 @@ +// 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 + +type GridPainter struct { + p *Painter + opt *GridPainterOption +} + +type GridPainterOption struct { + StrokeWidth float64 + StrokeColor Color + Column int + Row int + IgnoreFirstRow bool + IgnoreLastRow bool + IgnoreFirstColumn bool + IgnoreLastColumn bool +} + +func NewGridPainter(p *Painter, opt GridPainterOption) *GridPainter { + return &GridPainter{ + p: p, + opt: &opt, + } +} + +func (g *GridPainter) Render() (Box, error) { + opt := g.opt + ignoreColumnLines := make([]int, 0) + if opt.IgnoreFirstColumn { + ignoreColumnLines = append(ignoreColumnLines, 0) + } + if opt.IgnoreLastColumn { + ignoreColumnLines = append(ignoreColumnLines, opt.Column) + } + ignoreRowLines := make([]int, 0) + if opt.IgnoreFirstRow { + ignoreRowLines = append(ignoreRowLines, 0) + } + if opt.IgnoreLastRow { + ignoreRowLines = append(ignoreRowLines, opt.Row) + } + strokeWidth := opt.StrokeWidth + if strokeWidth <= 0 { + strokeWidth = 1 + } + + g.p.SetDrawingStyle(Style{ + StrokeWidth: strokeWidth, + StrokeColor: opt.StrokeColor, + }) + g.p.Grid(GridOption{ + Column: opt.Column, + Row: opt.Row, + IgnoreColumnLines: ignoreColumnLines, + IgnoreRowLines: ignoreRowLines, + }) + return g.p.box, nil +} diff --git a/painter.go b/painter.go index 95469f3..851fe11 100644 --- a/painter.go +++ b/painter.go @@ -32,7 +32,7 @@ import ( ) type Painter struct { - render Renderer + render chart.Renderer box Box font *truetype.Font parent *Painter From 4cf494088e029f30563ff40ab7dbd799b9c00f39 Mon Sep 17 00:00:00 2001 From: vicanso Date: Tue, 7 Jun 2022 23:04:39 +0800 Subject: [PATCH 11/21] feat: support legend render --- examples/painter/main.go | 56 +++++++++++++- legend.go | 154 +++++++++++++++++++++++++++++++++++++++ painter.go | 11 ++- 3 files changed, 216 insertions(+), 5 deletions(-) create mode 100644 legend.go diff --git a/examples/painter/main.go b/examples/painter/main.go index 4614f10..094b98e 100644 --- a/examples/painter/main.go +++ b/examples/painter/main.go @@ -26,7 +26,7 @@ func writeFile(buf []byte) error { func main() { p, err := charts.NewPainter(charts.PainterOptions{ - Width: 400, + Width: 600, Height: 1200, Type: charts.ChartOutputPNG, }) @@ -422,6 +422,60 @@ func main() { StrokeColor: drawing.ColorBlue, }).Render() + top += 100 + charts.NewLegendPainter(p.Child(charts.PainterBoxOption(charts.Box{ + Top: top, + Left: 1, + Right: p.Width() - 1, + Bottom: top + 30, + })), charts.LegendPainterOption{ + Data: []string{ + "Email", + "Union Ads", + "Video Ads", + "Direct", + }, + FontSize: 12, + FontColor: drawing.ColorBlack, + }).Render() + + top += 30 + charts.NewLegendPainter(p.Child(charts.PainterBoxOption(charts.Box{ + Top: top, + Left: 1, + Right: p.Width() - 1, + Bottom: top + 30, + })), charts.LegendPainterOption{ + Data: []string{ + "Email", + "Union Ads", + "Video Ads", + "Direct", + }, + Align: charts.AlignRight, + FontSize: 16, + Icon: charts.IconRect, + FontColor: drawing.ColorBlack, + }).Render() + + top += 30 + charts.NewLegendPainter(p.Child(charts.PainterBoxOption(charts.Box{ + Top: top, + Left: 1, + Right: p.Width() - 1, + Bottom: top + 100, + })), charts.LegendPainterOption{ + Data: []string{ + "Email", + "Union Ads", + "Video Ads", + "Direct", + }, + Orient: charts.OrientVertical, + FontSize: 12, + FontColor: drawing.ColorBlack, + }).Render() + buf, err := p.Bytes() if err != nil { panic(err) diff --git a/legend.go b/legend.go new file mode 100644 index 0000000..d128272 --- /dev/null +++ b/legend.go @@ -0,0 +1,154 @@ +// 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 + +type LegendPainter struct { + p *Painter + opt *LegendPainterOption +} + +const IconRect = "rect" +const IconLineDot = "lineDot" + +type LegendPainterOption struct { + Theme ColorPalette + // 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 + // Font size of legend text + FontSize float64 + // FontColor color of legend text + FontColor Color +} + +func NewLegendPainter(p *Painter, opt LegendPainterOption) *LegendPainter { + return &LegendPainter{ + p: p, + opt: &opt, + } +} + +func (l *LegendPainter) Render() (Box, error) { + opt := l.opt + theme := opt.Theme + if theme == nil { + theme = l.p.theme + } + p := l.p + p.SetTextStyle(Style{ + FontSize: opt.FontSize, + FontColor: opt.FontColor, + }) + measureList := make([]Box, len(opt.Data)) + maxTextWidth := 0 + for index, text := range opt.Data { + b := p.MeasureText(text) + if b.Width() > maxTextWidth { + maxTextWidth = b.Width() + } + measureList[index] = b + } + x := 0 + y := 0 + offset := 20 + textOffset := 2 + legendWidth := 30 + legendHeight := 20 + drawIcon := func(top, left int) int { + if opt.Icon == IconRect { + p.Rect(Box{ + Top: top - legendHeight + 4, + Left: left, + Right: left + legendWidth, + Bottom: top - 2, + }) + } else { + p.LegendLineDot(Box{ + Top: top, + Left: left, + Right: left + legendWidth, + Bottom: top + legendHeight, + }) + } + return left + legendWidth + } + for index, text := range opt.Data { + color := theme.GetSeriesColor(index) + p.SetDrawingStyle(Style{ + FillColor: color, + StrokeColor: color, + }) + if opt.Align != AlignRight { + x = drawIcon(y, x) + x += textOffset + } + p.Text(text, x, y) + x += measureList[index].Width() + if opt.Align == AlignRight { + x += textOffset + x = drawIcon(0, x) + } + if opt.Orient == OrientVertical { + y += offset + x = 0 + } else { + x += offset + y = 0 + } + } + width := 0 + height := 0 + for _, item := range measureList { + if opt.Orient == OrientVertical { + height += item.Height() + } else { + width += item.Width() + } + } + if opt.Orient == OrientVertical { + width = maxTextWidth + textOffset + legendWidth + height = offset * len(opt.Data) + } else { + height = legendHeight + offsetValue := (len(opt.Data) - 1) * (offset + textOffset) + allLegendWidth := len(opt.Data) * legendWidth + width += (offsetValue + allLegendWidth) + } + + return Box{ + Right: width, + Bottom: height, + }, nil +} diff --git a/painter.go b/painter.go index 851fe11..61a8e95 100644 --- a/painter.go +++ b/painter.go @@ -170,6 +170,9 @@ func NewPainter(opts PainterOptions, opt ...PainterOption) (*Painter, error) { font: font, } p.setOptions(opt...) + if p.theme == nil { + p.theme = NewTheme(ThemeLight) + } return p, nil } func (p *Painter) setOptions(opts ...PainterOption) { @@ -705,11 +708,11 @@ func (p *Painter) LegendLineDot(box Box) *Painter { dotHeight := 5 p.render.SetStrokeWidth(float64(strokeWidth)) - center := (height - strokeWidth) >> 1 - p.MoveTo(box.Left, box.Top+center) - p.LineTo(box.Right, box.Top+center) + center := (height-strokeWidth)>>1 - 1 + p.MoveTo(box.Left, box.Top-center) + p.LineTo(box.Right, box.Top-center) p.Stroke() - p.Circle(float64(dotHeight), box.Left+width>>1, center) + p.Circle(float64(dotHeight), box.Left+width>>1, box.Top-center) p.FillStroke() return p } From b394e1b49f9965a06b3be4e9121301fad81b144a Mon Sep 17 00:00:00 2001 From: vicanso Date: Wed, 8 Jun 2022 23:19:03 +0800 Subject: [PATCH 12/21] feat: support axis render --- axis.go | 192 +++++++++++++++++++++++++++++++++++++++ examples/painter/main.go | 71 ++++++++++++++- grid.go | 8 +- legend.go | 97 +++++++++++++------- painter.go | 15 +++ 5 files changed, 344 insertions(+), 39 deletions(-) create mode 100644 axis.go diff --git a/axis.go b/axis.go new file mode 100644 index 0000000..d2b559b --- /dev/null +++ b/axis.go @@ -0,0 +1,192 @@ +// 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 axisPainter struct { + p *Painter + opt *AxisPainterOption +} + +func NewAxisPainter(p *Painter, opt AxisPainterOption) *axisPainter { + return &axisPainter{ + p: p, + opt: &opt, + } +} + +type AxisPainterOption struct { + // The label of axis + Data []string + // The boundary gap on both sides of a coordinate axis. + // Nil or *true means the center part of two axis ticks + BoundaryGap *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 + // The line color of axis + StrokeColor Color + // The line width + StrokeWidth float64 + // The length of the axis tick + TickLength int + // 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 +} + +func (a *axisPainter) Render() (Box, error) { + opt := a.opt + p := a.p + + strokeWidth := opt.StrokeWidth + if strokeWidth == 0 { + strokeWidth = 1 + } + + tickCount := opt.SplitNumber + if tickCount == 0 { + tickCount = len(opt.Data) + } + + boundaryGap := true + if opt.BoundaryGap != nil && !*opt.BoundaryGap { + boundaryGap = false + } + + labelPosition := "" + if !boundaryGap { + tickCount-- + labelPosition = PositionLeft + } + + // TODO 计算unit + unit := 1 + // 如果小于0,则表示不处理 + tickLength := getDefaultInt(opt.TickLength, 5) + labelMargin := getDefaultInt(opt.LabelMargin, 5) + + textMaxWidth, textMaxHeight := p.MeasureTextMaxWidthHeight(opt.Data) + + width := 0 + height := 0 + // 垂直 + if opt.Position == PositionLeft || + opt.Position == PositionRight { + width = textMaxWidth + tickLength<<1 + height = p.Height() + } else { + width = p.Width() + height = tickLength<<1 + textMaxHeight + } + padding := Box{} + switch opt.Position { + case PositionTop: + padding.Top = p.Height() - height + case PositionLeft: + padding.Right = p.Width() - width + } + p = p.Child(PainterPaddingOption(padding)) + p.SetDrawingStyle(Style{ + StrokeColor: opt.StrokeColor, + StrokeWidth: strokeWidth, + }).OverrideTextStyle(Style{ + Font: opt.Font, + FontColor: opt.FontColor, + FontSize: opt.FontSize, + }) + + x0 := 0 + y0 := 0 + x1 := 0 + y1 := 0 + ticksPadding := 0 + labelPadding := 0 + orient := "" + textAlign := "" + + switch opt.Position { + case PositionTop: + labelPadding = labelMargin + x1 = p.Width() + y0 = labelMargin + int(opt.FontSize) + ticksPadding = int(opt.FontSize) + y1 = y0 + orient = OrientHorizontal + case PositionLeft: + orient = OrientVertical + textAlign = AlignRight + default: + labelPadding = height + x1 = p.Width() + orient = OrientHorizontal + } + + p.Child(PainterPaddingOption(Box{ + Top: ticksPadding, + })).Ticks(TicksOption{ + Count: tickCount, + Length: tickLength, + Unit: unit, + Orient: orient, + }) + + p.LineStroke([]Point{ + { + X: x0, + Y: y0, + }, + { + X: x1, + Y: y1, + }, + }) + + p.Child(PainterPaddingOption(Box{ + Top: labelPadding, + })).MultiText(MultiTextOption{ + Align: textAlign, + TextList: opt.Data, + Orient: orient, + Unit: unit, + Position: labelPosition, + }) + + return Box{ + Bottom: height, + Right: width, + }, nil +} diff --git a/examples/painter/main.go b/examples/painter/main.go index 094b98e..acbb3ef 100644 --- a/examples/painter/main.go +++ b/examples/painter/main.go @@ -27,7 +27,7 @@ func writeFile(buf []byte) error { func main() { p, err := charts.NewPainter(charts.PainterOptions{ Width: 600, - Height: 1200, + Height: 2000, Type: charts.ChartOutputPNG, }) if err != nil { @@ -429,6 +429,7 @@ func main() { Right: p.Width() - 1, Bottom: top + 30, })), charts.LegendPainterOption{ + Left: "10", Data: []string{ "Email", "Union Ads", @@ -446,6 +447,7 @@ func main() { Right: p.Width() - 1, Bottom: top + 30, })), charts.LegendPainterOption{ + Left: charts.PositionRight, Data: []string{ "Email", "Union Ads", @@ -465,6 +467,7 @@ func main() { Right: p.Width() - 1, Bottom: top + 100, })), charts.LegendPainterOption{ + Top: "10", Data: []string{ "Email", "Union Ads", @@ -476,6 +479,72 @@ func main() { FontColor: drawing.ColorBlack, }).Render() + top += 100 + charts.NewAxisPainter(p.Child(charts.PainterBoxOption(charts.Box{ + Top: top, + Left: 1, + Right: p.Width() - 1, + Bottom: top + 50, + })), charts.AxisPainterOption{ + Data: []string{ + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + "Sun", + }, + StrokeColor: drawing.ColorBlack, + FontSize: 12, + FontColor: drawing.ColorBlack, + }).Render() + + top += 50 + charts.NewAxisPainter(p.Child(charts.PainterBoxOption(charts.Box{ + Top: top, + Left: 1, + Right: p.Width() - 1, + Bottom: top + 50, + })), charts.AxisPainterOption{ + Position: charts.PositionTop, + BoundaryGap: charts.FalseFlag(), + Data: []string{ + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + "Sun", + }, + StrokeColor: drawing.ColorBlack, + FontSize: 12, + FontColor: drawing.ColorBlack, + }).Render() + + top += 50 + charts.NewAxisPainter(p.Child(charts.PainterBoxOption(charts.Box{ + Top: top, + Left: 10, + Right: p.Width() - 1, + Bottom: top + 200, + })), charts.AxisPainterOption{ + Position: charts.PositionLeft, + Data: []string{ + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + "Sun", + }, + StrokeColor: drawing.ColorBlack, + FontSize: 12, + FontColor: drawing.ColorBlack, + }).Render() + buf, err := p.Bytes() if err != nil { panic(err) diff --git a/grid.go b/grid.go index 1a00381..252fe2e 100644 --- a/grid.go +++ b/grid.go @@ -22,7 +22,7 @@ package charts -type GridPainter struct { +type gridPainter struct { p *Painter opt *GridPainterOption } @@ -38,14 +38,14 @@ type GridPainterOption struct { IgnoreLastColumn bool } -func NewGridPainter(p *Painter, opt GridPainterOption) *GridPainter { - return &GridPainter{ +func NewGridPainter(p *Painter, opt GridPainterOption) *gridPainter { + return &gridPainter{ p: p, opt: &opt, } } -func (g *GridPainter) Render() (Box, error) { +func (g *gridPainter) Render() (Box, error) { opt := g.opt ignoreColumnLines := make([]int, 0) if opt.IgnoreFirstColumn { diff --git a/legend.go b/legend.go index d128272..16341e4 100644 --- a/legend.go +++ b/legend.go @@ -22,7 +22,12 @@ package charts -type LegendPainter struct { +import ( + "strconv" + "strings" +) + +type legendPainter struct { p *Painter opt *LegendPainterOption } @@ -53,14 +58,14 @@ type LegendPainterOption struct { FontColor Color } -func NewLegendPainter(p *Painter, opt LegendPainterOption) *LegendPainter { - return &LegendPainter{ +func NewLegendPainter(p *Painter, opt LegendPainterOption) *legendPainter { + return &legendPainter{ p: p, opt: &opt, } } -func (l *LegendPainter) Render() (Box, error) { +func (l *legendPainter) Render() (Box, error) { opt := l.opt theme := opt.Theme if theme == nil { @@ -80,12 +85,54 @@ func (l *LegendPainter) Render() (Box, error) { } measureList[index] = b } - x := 0 - y := 0 + + // 计算展示的宽高 + width := 0 + height := 0 offset := 20 textOffset := 2 legendWidth := 30 legendHeight := 20 + for _, item := range measureList { + if opt.Orient == OrientVertical { + height += item.Height() + } else { + width += item.Width() + } + } + if opt.Orient == OrientVertical { + width = maxTextWidth + textOffset + legendWidth + height = offset * len(opt.Data) + } else { + height = legendHeight + offsetValue := (len(opt.Data) - 1) * (offset + textOffset) + allLegendWidth := len(opt.Data) * legendWidth + width += (offsetValue + allLegendWidth) + } + + // 计算开始的位置 + left := 0 + switch opt.Left { + case PositionRight: + left = p.Width() - width + case PositionCenter: + left = (p.Width() - width) >> 1 + default: + if strings.HasSuffix(opt.Left, "%") { + value, _ := strconv.Atoi(strings.ReplaceAll(opt.Left, "%", "")) + left = p.Width() * value / 100 + } else { + value, _ := strconv.Atoi(opt.Left) + left = value + } + } + top, _ := strconv.Atoi(opt.Top) + + x := int(left) + y := int(top) + x0 := x + y0 := y + drawIcon := func(top, left int) int { if opt.Icon == IconRect { p.Rect(Box{ @@ -111,41 +158,23 @@ func (l *LegendPainter) Render() (Box, error) { StrokeColor: color, }) if opt.Align != AlignRight { - x = drawIcon(y, x) - x += textOffset + x0 = drawIcon(y0, x0) + x0 += textOffset } - p.Text(text, x, y) - x += measureList[index].Width() + p.Text(text, x0, y0) + x0 += measureList[index].Width() if opt.Align == AlignRight { - x += textOffset - x = drawIcon(0, x) + x0 += textOffset + x0 = drawIcon(0, x0) } if opt.Orient == OrientVertical { - y += offset - x = 0 + y0 += offset + x0 = x } else { - x += offset - y = 0 + x0 += offset + y0 = y } } - width := 0 - height := 0 - for _, item := range measureList { - if opt.Orient == OrientVertical { - height += item.Height() - } else { - width += item.Width() - } - } - if opt.Orient == OrientVertical { - width = maxTextWidth + textOffset + legendWidth - height = offset * len(opt.Data) - } else { - height = legendHeight - offsetValue := (len(opt.Data) - 1) * (offset + textOffset) - allLegendWidth := len(opt.Data) * legendWidth - width += (offsetValue + allLegendWidth) - } return Box{ Right: width, diff --git a/painter.go b/painter.go index 61a8e95..fb18510 100644 --- a/painter.go +++ b/painter.go @@ -417,6 +417,21 @@ func (p *Painter) MeasureText(text string) Box { return p.render.MeasureText(text) } +func (p *Painter) MeasureTextMaxWidthHeight(textList []string) (int, int) { + maxWidth := 0 + maxHeight := 0 + for _, text := range textList { + box := p.MeasureText(text) + if maxWidth < box.Width() { + maxWidth = box.Width() + } + if maxHeight < box.Height() { + maxHeight = box.Height() + } + } + return maxWidth, maxHeight +} + func (p *Painter) LineStroke(points []Point) *Painter { for index, point := range points { x := point.X From c4045cfbbee1f8b8125ed8ee30e1c90f5f884eb1 Mon Sep 17 00:00:00 2001 From: vicanso Date: Sun, 12 Jun 2022 11:55:37 +0800 Subject: [PATCH 13/21] feat: support line chart render function --- axis.go | 154 ++++++++++++++------ charts.go | 23 +++ examples/line_chart/main.go | 111 +++++++++++++++ examples/painter/main.go | 54 +++++++- line_chart.go | 134 ++++++++++++++++++ painter.go | 4 +- range.go | 127 +++++++++++++++++ series.go | 270 ++++++++++++++++++++++++++++++++++++ theme.go | 27 +++- xaxis.go | 88 ++++++++++++ yaxis.go | 66 +++++++++ 11 files changed, 1012 insertions(+), 46 deletions(-) create mode 100644 examples/line_chart/main.go create mode 100644 line_chart.go create mode 100644 range.go create mode 100644 series.go create mode 100644 xaxis.go create mode 100644 yaxis.go diff --git a/axis.go b/axis.go index d2b559b..bb2e6a3 100644 --- a/axis.go +++ b/axis.go @@ -39,6 +39,8 @@ func NewAxisPainter(p *Painter, opt AxisPainterOption) *axisPainter { } type AxisPainterOption struct { + // The theme of chart + Theme ColorPalette // The label of axis Data []string // The boundary gap on both sides of a coordinate axis. @@ -70,13 +72,31 @@ type AxisPainterOption struct { func (a *axisPainter) Render() (Box, error) { opt := a.opt - p := a.p + top := a.p + theme := opt.Theme strokeWidth := opt.StrokeWidth if strokeWidth == 0 { strokeWidth = 1 } + font := opt.Font + if font == nil { + font = theme.GetFont() + } + fontColor := opt.FontColor + if fontColor.IsZero() { + fontColor = theme.GetTextColor() + } + fontSize := opt.FontSize + if fontSize == 0 { + fontSize = theme.GetFontSize() + } + strokeColor := opt.StrokeColor + if strokeColor.IsZero() { + strokeColor = theme.GetAxisStrokeColor() + } + tickCount := opt.SplitNumber if tickCount == 0 { tickCount = len(opt.Data) @@ -86,12 +106,17 @@ func (a *axisPainter) Render() (Box, error) { if opt.BoundaryGap != nil && !*opt.BoundaryGap { boundaryGap = false } + isVertical := opt.Position == PositionLeft || + opt.Position == PositionRight labelPosition := "" if !boundaryGap { tickCount-- labelPosition = PositionLeft } + if isVertical && boundaryGap { + labelPosition = PositionCenter + } // TODO 计算unit unit := 1 @@ -99,84 +124,104 @@ func (a *axisPainter) Render() (Box, error) { tickLength := getDefaultInt(opt.TickLength, 5) labelMargin := getDefaultInt(opt.LabelMargin, 5) - textMaxWidth, textMaxHeight := p.MeasureTextMaxWidthHeight(opt.Data) + style := Style{ + StrokeColor: strokeColor, + StrokeWidth: strokeWidth, + Font: font, + FontColor: fontColor, + FontSize: fontSize, + } + top.SetDrawingStyle(style).OverrideTextStyle(style) + + textMaxWidth, textMaxHeight := top.MeasureTextMaxWidthHeight(opt.Data) width := 0 height := 0 // 垂直 - if opt.Position == PositionLeft || - opt.Position == PositionRight { + if isVertical { width = textMaxWidth + tickLength<<1 - height = p.Height() + height = top.Height() } else { - width = p.Width() + width = top.Width() height = tickLength<<1 + textMaxHeight } padding := Box{} switch opt.Position { case PositionTop: - padding.Top = p.Height() - height + padding.Top = top.Height() - height case PositionLeft: - padding.Right = p.Width() - width + padding.Right = top.Width() - width + case PositionRight: + padding.Left = top.Width() - width } - p = p.Child(PainterPaddingOption(padding)) - p.SetDrawingStyle(Style{ - StrokeColor: opt.StrokeColor, - StrokeWidth: strokeWidth, - }).OverrideTextStyle(Style{ - Font: opt.Font, - FontColor: opt.FontColor, - FontSize: opt.FontSize, - }) + + p := top.Child(PainterPaddingOption(padding)) x0 := 0 y0 := 0 x1 := 0 y1 := 0 - ticksPadding := 0 - labelPadding := 0 + ticksPaddingTop := 0 + ticksPaddingLeft := 0 + labelPaddingTop := 0 + labelPaddingLeft := 0 + labelPaddingRight := 0 orient := "" textAlign := "" switch opt.Position { case PositionTop: - labelPadding = labelMargin + labelPaddingTop = labelMargin x1 = p.Width() y0 = labelMargin + int(opt.FontSize) - ticksPadding = int(opt.FontSize) + ticksPaddingTop = int(opt.FontSize) y1 = y0 orient = OrientHorizontal case PositionLeft: + x0 = p.Width() + y0 = 0 + x1 = p.Width() + y1 = p.Height() orient = OrientVertical textAlign = AlignRight + ticksPaddingLeft = textMaxWidth + tickLength + labelPaddingRight = width - textMaxWidth + case PositionRight: + orient = OrientVertical + y1 = p.Height() + labelPaddingLeft = width - textMaxWidth default: - labelPadding = height + labelPaddingTop = height x1 = p.Width() orient = OrientHorizontal } - p.Child(PainterPaddingOption(Box{ - Top: ticksPadding, - })).Ticks(TicksOption{ - Count: tickCount, - Length: tickLength, - Unit: unit, - Orient: orient, - }) - - p.LineStroke([]Point{ - { - X: x0, - Y: y0, - }, - { - X: x1, - Y: y1, - }, - }) + if strokeWidth > 0 { + p.Child(PainterPaddingOption(Box{ + Top: ticksPaddingTop, + Left: ticksPaddingLeft, + })).Ticks(TicksOption{ + Count: tickCount, + Length: tickLength, + Unit: unit, + Orient: orient, + }) + p.LineStroke([]Point{ + { + X: x0, + Y: y0, + }, + { + X: x1, + Y: y1, + }, + }) + } p.Child(PainterPaddingOption(Box{ - Top: labelPadding, + Left: labelPaddingLeft, + Top: labelPaddingTop, + Right: labelPaddingRight, })).MultiText(MultiTextOption{ Align: textAlign, TextList: opt.Data, @@ -184,6 +229,31 @@ func (a *axisPainter) Render() (Box, error) { Unit: unit, Position: labelPosition, }) + // 显示辅助线 + if opt.SplitLineShow { + style.StrokeColor = opt.SplitLineColor + top.OverrideDrawingStyle(style) + if isVertical { + x0 := p.Width() + x1 := top.Width() + if opt.Position == PositionRight { + x0 = 0 + x1 = top.Width() - p.Width() + } + for _, y := range autoDivide(height, tickCount) { + top.LineStroke([]Point{ + { + X: x0, + Y: y, + }, + { + X: x1, + Y: y, + }, + }) + } + } + } return Box{ Bottom: height, diff --git a/charts.go b/charts.go index 445dd7e..591ebea 100644 --- a/charts.go +++ b/charts.go @@ -25,3 +25,26 @@ package charts type Renderer interface { Render() (Box, error) } + +type defaultRenderOption struct { + Theme ColorPalette + Padding Box +} + +func defaultRender(p *Painter, opt defaultRenderOption) *Painter { + p.SetBackground(p.Width(), p.Height(), opt.Theme.GetBackgroundColor()) + if !opt.Padding.IsZero() { + p = p.Child(PainterPaddingOption(opt.Padding)) + } + return p +} + +func doRender(renderers ...Renderer) error { + for _, r := range renderers { + _, err := r.Render() + if err != nil { + return err + } + } + return nil +} diff --git a/examples/line_chart/main.go b/examples/line_chart/main.go new file mode 100644 index 0000000..e15500c --- /dev/null +++ b/examples/line_chart/main.go @@ -0,0 +1,111 @@ +package main + +import ( + "io/ioutil" + "os" + "path/filepath" + + "github.com/vicanso/go-charts" +) + +func writeFile(buf []byte) error { + tmpPath := "./tmp" + err := os.MkdirAll(tmpPath, 0700) + if err != nil { + return err + } + + file := filepath.Join(tmpPath, "line-chart.png") + err = ioutil.WriteFile(file, buf, 0600) + if err != nil { + return err + } + return nil +} + +func main() { + p, err := charts.NewPainter(charts.PainterOptions{ + Width: 800, + Height: 600, + Type: charts.ChartOutputPNG, + }) + if err != nil { + panic(err) + } + _, err = charts.NewLineChart(p, charts.LineChartOption{ + Padding: charts.Box{ + Left: 10, + Top: 10, + Right: 10, + Bottom: 10, + }, + XAxis: charts.NewXAxisOption([]string{ + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + "Sun", + }), + SeriesList: charts.SeriesList{ + 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, + }), + }, + }).Render() + if err != nil { + panic(err) + } + + buf, err := p.Bytes() + if err != nil { + panic(err) + } + err = writeFile(buf) + if err != nil { + panic(err) + } +} diff --git a/examples/painter/main.go b/examples/painter/main.go index acbb3ef..5022584 100644 --- a/examples/painter/main.go +++ b/examples/painter/main.go @@ -409,6 +409,7 @@ func main() { Bottom: 20, }) + // grid top += 50 charts.NewGridPainter(p.Child(charts.PainterBoxOption(charts.Box{ Top: top, @@ -422,6 +423,7 @@ func main() { StrokeColor: drawing.ColorBlue, }).Render() + // legend top += 100 charts.NewLegendPainter(p.Child(charts.PainterBoxOption(charts.Box{ Top: top, @@ -440,6 +442,7 @@ func main() { FontColor: drawing.ColorBlack, }).Render() + // legend top += 30 charts.NewLegendPainter(p.Child(charts.PainterBoxOption(charts.Box{ Top: top, @@ -460,6 +463,7 @@ func main() { FontColor: drawing.ColorBlack, }).Render() + // legend top += 30 charts.NewLegendPainter(p.Child(charts.PainterBoxOption(charts.Box{ Top: top, @@ -479,6 +483,7 @@ func main() { FontColor: drawing.ColorBlack, }).Render() + // axis bottom top += 100 charts.NewAxisPainter(p.Child(charts.PainterBoxOption(charts.Box{ Top: top, @@ -500,6 +505,7 @@ func main() { FontColor: drawing.ColorBlack, }).Render() + // axis top top += 50 charts.NewAxisPainter(p.Child(charts.PainterBoxOption(charts.Box{ Top: top, @@ -523,11 +529,12 @@ func main() { FontColor: drawing.ColorBlack, }).Render() + // axis left top += 50 charts.NewAxisPainter(p.Child(charts.PainterBoxOption(charts.Box{ Top: top, Left: 10, - Right: p.Width() - 1, + Right: 60, Bottom: top + 200, })), charts.AxisPainterOption{ Position: charts.PositionLeft, @@ -544,6 +551,51 @@ func main() { FontSize: 12, FontColor: drawing.ColorBlack, }).Render() + // axis right + charts.NewAxisPainter(p.Child(charts.PainterBoxOption(charts.Box{ + Top: top, + Left: 100, + Right: 150, + Bottom: top + 200, + })), charts.AxisPainterOption{ + Position: charts.PositionRight, + Data: []string{ + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + "Sun", + }, + StrokeColor: drawing.ColorBlack, + FontSize: 12, + FontColor: drawing.ColorBlack, + }).Render() + + // axis left no tick + charts.NewAxisPainter(p.Child(charts.PainterBoxOption(charts.Box{ + Top: top, + Left: 150, + Right: 300, + Bottom: top + 200, + })), charts.AxisPainterOption{ + BoundaryGap: charts.FalseFlag(), + Position: charts.PositionLeft, + Data: []string{ + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + "Sun", + }, + FontSize: 12, + FontColor: drawing.ColorBlack, + SplitLineShow: true, + SplitLineColor: drawing.ColorBlack.WithAlpha(100), + }).Render() buf, err := p.Bytes() if err != nil { diff --git a/line_chart.go b/line_chart.go new file mode 100644 index 0000000..9640087 --- /dev/null +++ b/line_chart.go @@ -0,0 +1,134 @@ +// 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" +) + +type lineChart struct { + p *Painter + opt *LineChartOption +} + +func NewLineChart(p *Painter, opt LineChartOption) *lineChart { + if opt.Theme == nil { + opt.Theme = NewTheme("") + } + return &lineChart{ + p: p, + opt: &opt, + } +} + +type LineChartOption struct { + Theme ColorPalette + // The data series list + SeriesList SeriesList + // The x axis option + XAxis XAxisOption + // The padding of line chart + Padding Box + // The y axis option + YAxis YAxisOption +} + +func (l *lineChart) Render() (Box, error) { + p := l.p + opt := l.opt + p = defaultRender(p, defaultRenderOption{ + Theme: opt.Theme, + Padding: opt.Padding, + }) + + seriesList := opt.SeriesList + seriesList.init() + // 过滤前先计算最大最小值 + max, min := seriesList.GetMaxMin() + + seriesList = seriesList.Filter(ChartTypeLine) + + // Y轴 + yr := NewRange(AxisRangeOption{ + Min: min, + Max: max, + // 高度需要减去x轴的高度 + Size: p.Height() - defaultXAxisHeight, + DivideCount: defaultAxisDivideCount, + }) + if opt.YAxis.Theme == nil { + opt.YAxis.Theme = opt.Theme + } + opt.YAxis.Data = yr.Values() + reverseStringSlice(opt.YAxis.Data) + yAxis := NewLeftYAxis(p, opt.YAxis) + yAxisBox, err := yAxis.Render() + if err != nil { + return chart.BoxZero, err + } + seriesPainter := p.Child(PainterPaddingOption(Box{ + Bottom: defaultXAxisHeight, + Left: yAxisBox.Width(), + })) + + if opt.XAxis.Theme == nil { + opt.XAxis.Theme = opt.Theme + } + xAxis := NewBottomXAxis(p.Child(PainterPaddingOption(Box{ + Left: yAxisBox.Width(), + })), opt.XAxis) + + xDivideValues := autoDivide(seriesPainter.Width(), len(opt.XAxis.Data)) + xValues := make([]int, len(xDivideValues)-1) + for i := 0; i < len(xDivideValues)-1; i++ { + xValues[i] = (xDivideValues[i] + xDivideValues[i+1]) >> 1 + } + for index, series := range seriesList { + seriesColor := opt.Theme.GetSeriesColor(index) + seriesPainter.SetDrawingStyle(Style{ + StrokeColor: seriesColor, + StrokeWidth: 2, + FillColor: seriesColor, + }) + points := make([]Point, 0) + for i, item := range series.Data { + h := yr.getRestHeight(item.Value) + p := Point{ + X: xValues[i], + Y: h, + } + points = append(points, p) + } + seriesPainter.LineStroke(points) + seriesPainter.Dots(points) + } + + err = doRender( + xAxis, + ) + if err != nil { + return chart.BoxZero, err + } + + return p.box, nil +} diff --git a/painter.go b/painter.go index fb18510..75d4a38 100644 --- a/painter.go +++ b/painter.go @@ -636,7 +636,7 @@ func (p *Painter) MultiText(opt MultiTextOption) *Painter { x := 0 y := 0 if isVertical { - y = start - box.Height()>>1 + y = start + box.Height()>>1 switch opt.Align { case AlignRight: x = width - box.Width() @@ -700,7 +700,7 @@ func (p *Painter) Grid(opt GridOption) *Painter { func (p *Painter) Dots(points []Point) *Painter { for _, item := range points { - p.Circle(5, item.X, item.Y) + p.Circle(3, item.X, item.Y) } p.FillStroke() return p diff --git a/range.go b/range.go new file mode 100644 index 0000000..399c449 --- /dev/null +++ b/range.go @@ -0,0 +1,127 @@ +// 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" +) + +const defaultAxisDivideCount = 6 + +type axisRange struct { + divideCount int + min float64 + max float64 + size int + boundary bool +} + +type AxisRangeOption struct { + Min float64 + Max float64 + Size int + Boundary bool + DivideCount int +} + +func NewRange(opt AxisRangeOption) axisRange { + max := opt.Max + min := opt.Min + + max += math.Abs(max * 0.1) + min -= math.Abs(min * 0.1) + divideCount := opt.DivideCount + 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 axisRange{ + divideCount: divideCount, + min: min, + max: max, + size: opt.Size, + boundary: opt.Boundary, + } +} + +func (r axisRange) 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 *axisRange) getHeight(value float64) int { + v := (value - r.min) / (r.max - r.min) + return int(v * float64(r.size)) +} + +func (r *axisRange) getRestHeight(value float64) int { + return r.size - r.getHeight(value) +} + +func (r *axisRange) GetRange(index int) (float64, float64) { + unit := float64(r.size) / float64(r.divideCount) + return unit * float64(index), unit * float64(index+1) +} +func (r *axisRange) AutoDivide() []int { + return autoDivide(r.size, r.divideCount) +} + +func (r *axisRange) 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/series.go b/series.go new file mode 100644 index 0000000..07bd5d9 --- /dev/null +++ b/series.go @@ -0,0 +1,270 @@ +// 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" +) + +type SeriesData struct { + // The value of series data + Value float64 + // The style of series data + Style 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 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 + +func (sl SeriesList) init() { + if sl[len(sl)-1].index != 0 { + return + } + for i := 0; i < len(sl); i++ { + if sl[i].Type == "" { + sl[i].Type = ChartTypeLine + } + sl[i].index = i + } +} + +func (sl SeriesList) Filter(chartType string) SeriesList { + arr := make(SeriesList, 0) + for index, item := range sl { + if item.Type == chartType { + arr = append(arr, sl[index]) + } + } + return arr +} + +// GetMaxMin get max and min value of series list +func (sl SeriesList) GetMaxMin() (float64, float64) { + min := math.MaxFloat64 + max := -math.MaxFloat64 + for _, series := range sl { + for _, item := range series.Data { + if item.Value > max { + max = item.Value + } + if item.Value < min { + min = item.Value + } + } + } + return max, min +} + +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/theme.go b/theme.go index bb05249..544588a 100644 --- a/theme.go +++ b/theme.go @@ -22,7 +22,11 @@ package charts -import "github.com/wcharczuk/go-chart/v2/drawing" +import ( + "github.com/golang/freetype/truetype" + "github.com/wcharczuk/go-chart/v2" + "github.com/wcharczuk/go-chart/v2/drawing" +) const ThemeDark = "dark" const ThemeLight = "light" @@ -36,6 +40,8 @@ type ColorPalette interface { GetSeriesColor(int) Color GetBackgroundColor() Color GetTextColor() Color + GetFontSize() float64 + GetFont() *truetype.Font } type themeColorPalette struct { @@ -45,10 +51,14 @@ type themeColorPalette struct { backgroundColor Color textColor Color seriesColors []Color + fontSize float64 + font *truetype.Font } var palettes = map[string]ColorPalette{} +const defaultFontSize = 12.0 + func init() { echartSeriesColors := []Color{ parseColor("#5470c6"), @@ -233,3 +243,18 @@ func (t *themeColorPalette) GetBackgroundColor() Color { func (t *themeColorPalette) GetTextColor() Color { return t.textColor } + +func (t *themeColorPalette) GetFontSize() float64 { + if t.fontSize != 0 { + return t.fontSize + } + return defaultFontSize +} + +func (t *themeColorPalette) GetFont() *truetype.Font { + if t.font != nil { + return t.font + } + f, _ := chart.GetDefaultFont() + return f +} diff --git a/xaxis.go b/xaxis.go new file mode 100644 index 0000000..a8c28c0 --- /dev/null +++ b/xaxis.go @@ -0,0 +1,88 @@ +// 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 { + // The font of x axis + 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 ColorPalette + // The font size of x axis label + FontSize float64 + // 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 + // The position of axis, it can be 'top' or 'bottom' + Position string + // The line color of axis + StrokeColor Color + // The color of label + FontColor Color +} + +const defaultXAxisHeight = 30 + +func NewXAxisOption(data []string, boundaryGap ...*bool) XAxisOption { + opt := XAxisOption{ + Data: data, + } + if len(boundaryGap) != 0 { + opt.BoundaryGap = boundaryGap[0] + } + return opt +} + +func (opt *XAxisOption) ToAxisPainterOption() AxisPainterOption { + position := PositionBottom + if opt.Position == PositionTop { + position = PositionTop + } + return AxisPainterOption{ + Theme: opt.Theme, + Data: opt.Data, + BoundaryGap: opt.BoundaryGap, + Position: position, + SplitNumber: opt.SplitNumber, + StrokeColor: opt.StrokeColor, + FontSize: opt.FontSize, + Font: opt.Font, + FontColor: opt.FontColor, + } +} + +func NewBottomXAxis(p *Painter, opt XAxisOption) *axisPainter { + p = p.Child(PainterPaddingOption(Box{ + Top: p.Height() - defaultXAxisHeight, + })) + return NewAxisPainter(p, opt.ToAxisPainterOption()) +} diff --git a/yaxis.go b/yaxis.go new file mode 100644 index 0000000..653f6ec --- /dev/null +++ b/yaxis.go @@ -0,0 +1,66 @@ +// 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 YAxisOption struct { + // The font of y axis + Font *truetype.Font + // The data value of x axis + Data []string + // The theme of chart + Theme ColorPalette + // The font size of x axis label + FontSize float64 + // The position of axis, it can be 'left' or 'right' + Position string + // The color of label + FontColor Color +} + +func (opt *YAxisOption) ToAxisPainterOption() AxisPainterOption { + position := PositionLeft + if opt.Position == PositionRight { + position = PositionRight + } + return AxisPainterOption{ + Theme: opt.Theme, + Data: opt.Data, + Position: position, + FontSize: opt.FontSize, + StrokeWidth: -1, + Font: opt.Font, + FontColor: opt.FontColor, + BoundaryGap: FalseFlag(), + SplitLineShow: true, + SplitLineColor: opt.Theme.GetAxisSplitLineColor(), + } +} + +func NewLeftYAxis(p *Painter, opt YAxisOption) *axisPainter { + p = p.Child(PainterPaddingOption(Box{ + Bottom: defaultXAxisHeight, + })) + return NewAxisPainter(p, opt.ToAxisPainterOption()) +} From 72e11e49b17a39e897e5e6dd3bb88aabf54c29e4 Mon Sep 17 00:00:00 2001 From: vicanso Date: Sun, 12 Jun 2022 19:58:36 +0800 Subject: [PATCH 14/21] refactor: default render function for axis --- charts.go | 84 ++++++++++++++++++++++++++++++++++++++++++++++++--- line_chart.go | 53 +++++++------------------------- series.go | 9 ++++-- 3 files changed, 97 insertions(+), 49 deletions(-) diff --git a/charts.go b/charts.go index 591ebea..6a2f5f2 100644 --- a/charts.go +++ b/charts.go @@ -27,16 +27,92 @@ type Renderer interface { } type defaultRenderOption struct { - Theme ColorPalette - Padding Box + Theme ColorPalette + Padding Box + SeriesList SeriesList + // The y axis option + YAxisOptions []YAxisOption + // The x axis option + XAxis XAxisOption } -func defaultRender(p *Painter, opt defaultRenderOption) *Painter { +type defaultRenderResult struct { + axisRanges map[int]axisRange + p *Painter +} + +func defaultRender(p *Painter, opt defaultRenderOption) (*defaultRenderResult, error) { p.SetBackground(p.Width(), p.Height(), opt.Theme.GetBackgroundColor()) if !opt.Padding.IsZero() { p = p.Child(PainterPaddingOption(opt.Padding)) } - return p + result := defaultRenderResult{ + axisRanges: make(map[int]axisRange), + } + + // 计算图表对应的轴有哪些 + axisIndexList := make([]int, 0) + for _, series := range opt.SeriesList { + if containsInt(axisIndexList, series.AxisIndex) { + continue + } + axisIndexList = append(axisIndexList, series.index) + } + // 高度需要减去x轴的高度 + rangeHeight := p.Height() - defaultXAxisHeight + rangeWidth := 0 + + // 计算对应的axis range + for _, index := range axisIndexList { + max, min := opt.SeriesList.GetMaxMin(index) + r := NewRange(AxisRangeOption{ + Min: min, + Max: max, + // 高度需要减去x轴的高度 + Size: rangeHeight, + // 分隔数量 + DivideCount: defaultAxisDivideCount, + }) + result.axisRanges[index] = r + yAxisOption := YAxisOption{} + if len(opt.YAxisOptions) > index { + yAxisOption = opt.YAxisOptions[index] + } + if yAxisOption.Theme == nil { + yAxisOption.Theme = opt.Theme + } + yAxisOption.Data = r.Values() + reverseStringSlice(yAxisOption.Data) + // TODO生成其它位置既yAxis + yAxis := NewLeftYAxis(p, yAxisOption) + yAxisBox, err := yAxis.Render() + if err != nil { + return nil, err + } + rangeWidth += yAxisBox.Width() + } + + if opt.XAxis.Theme == nil { + opt.XAxis.Theme = opt.Theme + } + xAxis := NewBottomXAxis(p.Child(PainterPaddingOption(Box{ + Left: rangeWidth, + })), opt.XAxis) + _, err := xAxis.Render() + if err != nil { + return nil, err + } + + // // 生成Y轴 + // for _, yAxisOption := range opt.YAxisOptions { + + // } + + result.p = p.Child(PainterPaddingOption(Box{ + Bottom: rangeHeight, + Left: rangeWidth, + })) + return &result, nil } func doRender(renderers ...Renderer) error { diff --git a/line_chart.go b/line_chart.go index 9640087..3d93341 100644 --- a/line_chart.go +++ b/line_chart.go @@ -50,53 +50,28 @@ type LineChartOption struct { // The padding of line chart Padding Box // The y axis option - YAxis YAxisOption + YAxisOptions []YAxisOption } func (l *lineChart) Render() (Box, error) { p := l.p opt := l.opt - p = defaultRender(p, defaultRenderOption{ - Theme: opt.Theme, - Padding: opt.Padding, - }) - seriesList := opt.SeriesList seriesList.init() - // 过滤前先计算最大最小值 - max, min := seriesList.GetMaxMin() - - seriesList = seriesList.Filter(ChartTypeLine) - - // Y轴 - yr := NewRange(AxisRangeOption{ - Min: min, - Max: max, - // 高度需要减去x轴的高度 - Size: p.Height() - defaultXAxisHeight, - DivideCount: defaultAxisDivideCount, + renderResult, err := defaultRender(p, defaultRenderOption{ + Theme: opt.Theme, + Padding: opt.Padding, + SeriesList: seriesList, + XAxis: opt.XAxis, + YAxisOptions: opt.YAxisOptions, }) - if opt.YAxis.Theme == nil { - opt.YAxis.Theme = opt.Theme - } - opt.YAxis.Data = yr.Values() - reverseStringSlice(opt.YAxis.Data) - yAxis := NewLeftYAxis(p, opt.YAxis) - yAxisBox, err := yAxis.Render() if err != nil { return chart.BoxZero, err } - seriesPainter := p.Child(PainterPaddingOption(Box{ - Bottom: defaultXAxisHeight, - Left: yAxisBox.Width(), - })) - if opt.XAxis.Theme == nil { - opt.XAxis.Theme = opt.Theme - } - xAxis := NewBottomXAxis(p.Child(PainterPaddingOption(Box{ - Left: yAxisBox.Width(), - })), opt.XAxis) + seriesList = seriesList.Filter(ChartTypeLine) + + seriesPainter := renderResult.p xDivideValues := autoDivide(seriesPainter.Width(), len(opt.XAxis.Data)) xValues := make([]int, len(xDivideValues)-1) @@ -110,6 +85,7 @@ func (l *lineChart) Render() (Box, error) { StrokeWidth: 2, FillColor: seriesColor, }) + yr := renderResult.axisRanges[series.AxisIndex] points := make([]Point, 0) for i, item := range series.Data { h := yr.getRestHeight(item.Value) @@ -123,12 +99,5 @@ func (l *lineChart) Render() (Box, error) { seriesPainter.Dots(points) } - err = doRender( - xAxis, - ) - if err != nil { - return chart.BoxZero, err - } - return p.box, nil } diff --git a/series.go b/series.go index 07bd5d9..2888f30 100644 --- a/series.go +++ b/series.go @@ -99,8 +99,8 @@ type Series struct { // The data list of series Data []SeriesData // The Y axis index, it should be 0 or 1. - // Default value is 1 - YAxisIndex int + // Default value is 0 + AxisIndex int // The style for series Style chart.Style // The label for series @@ -143,10 +143,13 @@ func (sl SeriesList) Filter(chartType string) SeriesList { } // GetMaxMin get max and min value of series list -func (sl SeriesList) GetMaxMin() (float64, float64) { +func (sl SeriesList) GetMaxMin(axisIndex int) (float64, float64) { min := math.MaxFloat64 max := -math.MaxFloat64 for _, series := range sl { + if series.AxisIndex != axisIndex { + continue + } for _, item := range series.Data { if item.Value > max { max = item.Value From 8a5990fe8fd8ce0f3b40538ed645840c3e587f47 Mon Sep 17 00:00:00 2001 From: vicanso Date: Mon, 13 Jun 2022 23:22:15 +0800 Subject: [PATCH 15/21] feat: support mark line and mark point render --- chart_option.go | 120 ++++ charts.go | 117 +++- examples/charts/main.go | 1321 +++++++++++++++++++++++++++++++++++ examples/line_chart/main.go | 13 + legend.go | 24 +- line_chart.go | 56 +- mark_line.go | 118 ++++ mark_point.go | 102 +++ painter.go | 2 +- title.go | 192 +++++ 10 files changed, 2046 insertions(+), 19 deletions(-) create mode 100644 chart_option.go create mode 100644 examples/charts/main.go create mode 100644 mark_line.go create mode 100644 mark_point.go create mode 100644 title.go diff --git a/chart_option.go b/chart_option.go new file mode 100644 index 0000000..6ca7cd7 --- /dev/null +++ b/chart_option.go @@ -0,0 +1,120 @@ +// 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 ( + "sort" + + "github.com/golang/freetype/truetype" + "github.com/wcharczuk/go-chart/v2" +) + +type ChartOption struct { + theme ColorPalette + font *truetype.Font + // 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 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 + YAxisOptions []YAxisOption + // The width of chart, default width is 600 + Width int + // The height of chart, default height is 400 + Height int + Parent *Painter + // The padding for chart, default padding is [20, 10, 10, 10] + Padding Box + // The canvas box for chart + Box Box + // The series list + SeriesList SeriesList + // The radar indicator list + // RadarIndicators []RadarIndicator + // The background color of chart + BackgroundColor Color + // The child charts + Children []ChartOption +} + +func (o *ChartOption) fillDefault() { + t := NewTheme(o.Theme) + o.theme = t + // 如果为空,初始化 + axisCount := 1 + for _, series := range o.SeriesList { + if series.AxisIndex >= axisCount { + axisCount++ + } + } + o.Width = getDefaultInt(o.Width, defaultChartWidth) + o.Height = getDefaultInt(o.Height, defaultChartHeight) + yAxisOptions := make([]YAxisOption, axisCount) + copy(yAxisOptions, o.YAxisOptions) + o.YAxisOptions = yAxisOptions + o.font, _ = GetFont(o.FontFamily) + + 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, + } + } + // 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] + }) + } +} diff --git a/charts.go b/charts.go index 6a2f5f2..947fa8d 100644 --- a/charts.go +++ b/charts.go @@ -22,6 +22,24 @@ package charts +const labelFontSize = 10 +const defaultDotWidth = 2.0 +const defaultStrokeWidth = 2.0 + +var defaultChartWidth = 600 +var defaultChartHeight = 400 + +func SetDefaultWidth(width int) { + if width > 0 { + defaultChartWidth = width + } +} +func SetDefaultHeight(height int) { + if height > 0 { + defaultChartHeight = height + } +} + type Renderer interface { Render() (Box, error) } @@ -34,6 +52,10 @@ type defaultRenderOption struct { YAxisOptions []YAxisOption // The x axis option XAxis XAxisOption + // The title option + TitleOption TitleOption + // The legend option + LegendOption LegendOption } type defaultRenderResult struct { @@ -42,10 +64,37 @@ type defaultRenderResult struct { } func defaultRender(p *Painter, opt defaultRenderOption) (*defaultRenderResult, error) { - p.SetBackground(p.Width(), p.Height(), opt.Theme.GetBackgroundColor()) if !opt.Padding.IsZero() { p = p.Child(PainterPaddingOption(opt.Padding)) } + + if len(opt.LegendOption.Data) != 0 { + if opt.LegendOption.Theme == nil { + opt.LegendOption.Theme = opt.Theme + } + _, err := NewLegendPainter(p, opt.LegendOption).Render() + if err != nil { + return nil, err + } + } + + // 如果有标题 + if opt.TitleOption.Text != "" { + if opt.TitleOption.Theme == nil { + opt.TitleOption.Theme = opt.Theme + } + titlePainter := NewTitlePainter(p, opt.TitleOption) + + titleBox, err := titlePainter.Render() + if err != nil { + return nil, err + } + p = p.Child(PainterPaddingOption(Box{ + // 标题下留白 + Top: titleBox.Height() + 20, + })) + } + result := defaultRenderResult{ axisRanges: make(map[int]axisRange), } @@ -60,7 +109,8 @@ func defaultRender(p *Painter, opt defaultRenderOption) (*defaultRenderResult, e } // 高度需要减去x轴的高度 rangeHeight := p.Height() - defaultXAxisHeight - rangeWidth := 0 + rangeWidthLeft := 0 + rangeWidthRight := 0 // 计算对应的axis range for _, index := range axisIndexList { @@ -89,28 +139,28 @@ func defaultRender(p *Painter, opt defaultRenderOption) (*defaultRenderResult, e if err != nil { return nil, err } - rangeWidth += yAxisBox.Width() + if index == 0 { + rangeWidthLeft += yAxisBox.Width() + } else { + rangeWidthRight += yAxisBox.Width() + } } if opt.XAxis.Theme == nil { opt.XAxis.Theme = opt.Theme } xAxis := NewBottomXAxis(p.Child(PainterPaddingOption(Box{ - Left: rangeWidth, + Left: rangeWidthLeft, })), opt.XAxis) _, err := xAxis.Render() if err != nil { return nil, err } - // // 生成Y轴 - // for _, yAxisOption := range opt.YAxisOptions { - - // } - result.p = p.Child(PainterPaddingOption(Box{ Bottom: rangeHeight, - Left: rangeWidth, + Left: rangeWidthLeft, + Right: rangeWidthRight, })) return &result, nil } @@ -124,3 +174,50 @@ func doRender(renderers ...Renderer) error { } return nil } + +func Render(opt ChartOption) (*Painter, error) { + opt.fillDefault() + + if opt.Parent == nil { + p, err := NewPainter(PainterOptions{ + Type: opt.Type, + Width: opt.Width, + Height: opt.Height, + }) + if err != nil { + return nil, err + } + opt.Parent = p + } + p := opt.Parent + p.SetBackground(p.Width(), p.Height(), opt.BackgroundColor) + seriesList := opt.SeriesList + seriesList.init() + + rendererList := make([]Renderer, 0) + + // line chart + lineChartSeriesList := seriesList.Filter(ChartTypeLine) + if len(lineChartSeriesList) != 0 { + renderer := NewLineChart(p, LineChartOption{ + Theme: opt.theme, + Font: opt.font, + SeriesList: lineChartSeriesList, + XAxis: opt.XAxis, + Padding: opt.Padding, + YAxisOptions: opt.YAxisOptions, + TitleOption: opt.Title, + LegendOption: opt.Legend, + }) + rendererList = append(rendererList, renderer) + } + + for _, renderer := range rendererList { + _, err := renderer.Render() + if err != nil { + return nil, err + } + } + + return p, nil +} diff --git a/examples/charts/main.go b/examples/charts/main.go new file mode 100644 index 0000000..18f5a95 --- /dev/null +++ b/examples/charts/main.go @@ -0,0 +1,1321 @@ +package main + +import ( + "bytes" + "net/http" + "strconv" + + charts "github.com/vicanso/go-charts" +) + +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: charts.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), + }, + }, + }, + } + 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/examples/line_chart/main.go b/examples/line_chart/main.go index e15500c..c168f08 100644 --- a/examples/line_chart/main.go +++ b/examples/line_chart/main.go @@ -39,6 +39,19 @@ func main() { Right: 10, Bottom: 10, }, + TitleOption: charts.TitleOption{ + Text: "Line", + }, + LegendOption: charts.LegendOption{ + Data: []string{ + "Email", + "Union Ads", + "Video Ads", + "Direct", + "Search Engine", + }, + Left: charts.PositionCenter, + }, XAxis: charts.NewXAxisOption([]string{ "Mon", "Tue", diff --git a/legend.go b/legend.go index 16341e4..b8a6fdc 100644 --- a/legend.go +++ b/legend.go @@ -29,13 +29,13 @@ import ( type legendPainter struct { p *Painter - opt *LegendPainterOption + opt *LegendOption } const IconRect = "rect" const IconLineDot = "lineDot" -type LegendPainterOption struct { +type LegendOption struct { Theme ColorPalette // Text array of legend Data []string @@ -58,7 +58,17 @@ type LegendPainterOption struct { FontColor Color } -func NewLegendPainter(p *Painter, opt LegendPainterOption) *legendPainter { +func NewLegendOption(labels []string, left ...string) LegendOption { + opt := LegendOption{ + Data: labels, + } + if len(left) != 0 { + opt.Left = left[0] + } + return opt +} + +func NewLegendPainter(p *Painter, opt LegendOption) *legendPainter { return &legendPainter{ p: p, opt: &opt, @@ -71,6 +81,12 @@ func (l *legendPainter) Render() (Box, error) { if theme == nil { theme = l.p.theme } + if opt.FontSize == 0 { + opt.FontSize = theme.GetFontSize() + } + if opt.FontColor.IsZero() { + opt.FontColor = theme.GetTextColor() + } p := l.p p.SetTextStyle(Style{ FontSize: opt.FontSize, @@ -129,7 +145,7 @@ func (l *legendPainter) Render() (Box, error) { top, _ := strconv.Atoi(opt.Top) x := int(left) - y := int(top) + y := int(top) + 10 x0 := x y0 := y diff --git a/line_chart.go b/line_chart.go index 3d93341..5f4ea4f 100644 --- a/line_chart.go +++ b/line_chart.go @@ -23,7 +23,9 @@ package charts import ( + "github.com/golang/freetype/truetype" "github.com/wcharczuk/go-chart/v2" + "github.com/wcharczuk/go-chart/v2/drawing" ) type lineChart struct { @@ -43,6 +45,8 @@ func NewLineChart(p *Painter, opt LineChartOption) *lineChart { type LineChartOption struct { Theme ColorPalette + // The font size + Font *truetype.Font // The data series list SeriesList SeriesList // The x axis option @@ -51,6 +55,10 @@ type LineChartOption struct { Padding Box // The y axis option YAxisOptions []YAxisOption + // The option of title + TitleOption TitleOption + // The legend option + LegendOption LegendOption } func (l *lineChart) Render() (Box, error) { @@ -64,6 +72,8 @@ func (l *lineChart) Render() (Box, error) { SeriesList: seriesList, XAxis: opt.XAxis, YAxisOptions: opt.YAxisOptions, + TitleOption: opt.TitleOption, + LegendOption: opt.LegendOption, }) if err != nil { return chart.BoxZero, err @@ -78,13 +88,20 @@ func (l *lineChart) Render() (Box, error) { for i := 0; i < len(xDivideValues)-1; i++ { xValues[i] = (xDivideValues[i] + xDivideValues[i+1]) >> 1 } + markPointPainter := NewMarkPointPainter(seriesPainter) + markLinePainter := NewMarkLinePainter(seriesPainter) + rendererList := []Renderer{ + markPointPainter, + markLinePainter, + } for index, series := range seriesList { seriesColor := opt.Theme.GetSeriesColor(index) - seriesPainter.SetDrawingStyle(Style{ + drawingStyle := Style{ StrokeColor: seriesColor, - StrokeWidth: 2, - FillColor: seriesColor, - }) + StrokeWidth: defaultStrokeWidth, + } + + seriesPainter.SetDrawingStyle(drawingStyle) yr := renderResult.axisRanges[series.AxisIndex] points := make([]Point, 0) for i, item := range series.Data { @@ -95,8 +112,39 @@ func (l *lineChart) Render() (Box, error) { } points = append(points, p) } + // 画线 seriesPainter.LineStroke(points) + + // 画点 + if opt.Theme.IsDark() { + drawingStyle.FillColor = drawingStyle.StrokeColor + } else { + drawingStyle.FillColor = drawing.ColorWhite + } + drawingStyle.StrokeWidth = 1 + seriesPainter.SetDrawingStyle(drawingStyle) seriesPainter.Dots(points) + markPointPainter.Add(markPointRenderOption{ + FillColor: seriesColor, + Font: opt.Font, + Points: points, + Series: series, + }) + markLinePainter.Add(markLineRenderOption{ + FillColor: seriesColor, + FontColor: opt.Theme.GetTextColor(), + StrokeColor: seriesColor, + Font: opt.Font, + Series: series, + Range: yr, + }) + } + // 最大、最小的mark point + for _, renderer := range rendererList { + _, err = renderer.Render() + if err != nil { + return chart.BoxZero, err + } } return p.box, nil diff --git a/mark_line.go b/mark_line.go new file mode 100644 index 0000000..9a9d568 --- /dev/null +++ b/mark_line.go @@ -0,0 +1,118 @@ +// 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" +) + +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 markLinePainter struct { + p *Painter + options []markLineRenderOption +} + +func (m *markLinePainter) Add(opt markLineRenderOption) { + m.options = append(m.options, opt) +} + +func NewMarkLinePainter(p *Painter) *markLinePainter { + return &markLinePainter{ + p: p, + options: make([]markLineRenderOption, 0), + } +} + +type markLineRenderOption struct { + FillColor Color + FontColor Color + StrokeColor Color + Font *truetype.Font + Series Series + Range axisRange +} + +func (m *markLinePainter) Render() (Box, error) { + painter := m.p + for _, opt := range m.options { + s := opt.Series + if len(s.MarkLine.Data) == 0 { + continue + } + summary := s.Summary() + for _, markLine := range s.MarkLine.Data { + // 由于mark line会修改style,因此每次重新设置 + painter.OverrideDrawingStyle(Style{ + FillColor: opt.FillColor, + StrokeColor: opt.StrokeColor, + StrokeWidth: 1, + StrokeDashArray: []float64{ + 4, + 2, + }, + }).OverrideTextStyle(Style{ + Font: opt.Font, + FontColor: opt.FontColor, + FontSize: labelFontSize, + }) + 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 := painter.Width() + text := commafWithDigits(value) + textBox := painter.MeasureText(text) + painter.MarkLine(0, y, width-2) + painter.Text(text, width, y+textBox.Height()>>1-2) + } + } + return chart.BoxZero, nil +} + +func markLineRender(opt markLineRenderOption) { + // d := opt.Draw + // s := opt.Series + // if len(s.MarkLine.Data) == 0 { + // return + // } + // r := d.Render + +} diff --git a/mark_point.go b/mark_point.go new file mode 100644 index 0000000..ce3cb0f --- /dev/null +++ b/mark_point.go @@ -0,0 +1,102 @@ +// 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" +) + +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 markPointPainter struct { + p *Painter + options []markPointRenderOption +} + +func (m *markPointPainter) Add(opt markPointRenderOption) { + m.options = append(m.options, opt) +} + +type markPointRenderOption struct { + FillColor Color + Font *truetype.Font + Series Series + Points []Point +} + +func NewMarkPointPainter(p *Painter) *markPointPainter { + return &markPointPainter{ + p: p, + options: make([]markPointRenderOption, 0), + } +} + +func (m *markPointPainter) Render() (Box, error) { + painter := m.p + for _, opt := range m.options { + s := opt.Series + if len(s.MarkPoint.Data) == 0 { + continue + } + points := opt.Points + summary := s.Summary() + symbolSize := s.MarkPoint.SymbolSize + if symbolSize == 0 { + symbolSize = 30 + } + painter.OverrideDrawingStyle(Style{ + FillColor: opt.FillColor, + }).OverrideTextStyle(Style{ + FontColor: NewTheme(ThemeDark).GetTextColor(), + FontSize: labelFontSize, + StrokeWidth: 1, + Font: opt.Font, + }) + 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 + } + + painter.Pin(p.X, p.Y-symbolSize>>1, symbolSize) + text := commafWithDigits(value) + textBox := painter.MeasureText(text) + painter.Text(text, p.X-textBox.Width()>>1, p.Y-symbolSize>>1-2) + } + } + return chart.BoxZero, nil +} diff --git a/painter.go b/painter.go index 75d4a38..fff6ca7 100644 --- a/painter.go +++ b/painter.go @@ -700,7 +700,7 @@ func (p *Painter) Grid(opt GridOption) *Painter { func (p *Painter) Dots(points []Point) *Painter { for _, item := range points { - p.Circle(3, item.X, item.Y) + p.Circle(2, item.X, item.Y) } p.FillStroke() return p diff --git a/title.go b/title.go new file mode 100644 index 0000000..30831ac --- /dev/null +++ b/title.go @@ -0,0 +1,192 @@ +// 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/golang/freetype/truetype" +) + +type TitleOption struct { + // The theme of chart + Theme ColorPalette + // Title text, support \n for new line + Text string + // Subtitle text, support \n for new line + Subtext string + // // Title style + // Style Style + // // Subtitle style + // SubtextStyle 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 + // The font of label + Font *truetype.Font + // The font size of label + FontSize float64 + // The color of label + FontColor Color + // The subtext font size of label + SubtextFontSize float64 + // The subtext font color of label + SubtextFontColor Color +} + +type titleMeasureOption struct { + width int + height int + text string + style 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 +} + +type titlePainter struct { + p *Painter + opt *TitleOption +} + +func NewTitlePainter(p *Painter, opt TitleOption) *titlePainter { + return &titlePainter{ + p: p, + opt: &opt, + } +} + +func (t *titlePainter) Render() (Box, error) { + opt := t.opt + p := t.p + theme := opt.Theme + measureOptions := make([]titleMeasureOption, 0) + + if opt.Font == nil { + opt.Font = theme.GetFont() + } + if opt.FontColor.IsZero() { + opt.FontColor = theme.GetTextColor() + } + if opt.FontSize == 0 { + opt.FontSize = theme.GetFontSize() + } + if opt.SubtextFontColor.IsZero() { + opt.SubtextFontColor = opt.FontColor + } + if opt.SubtextFontSize == 0 { + opt.SubtextFontSize = opt.FontSize + } + + titleTextStyle := Style{ + Font: opt.Font, + FontSize: opt.FontSize, + FontColor: opt.FontColor, + } + // 主标题 + for _, v := range splitTitleText(opt.Text) { + measureOptions = append(measureOptions, titleMeasureOption{ + text: v, + style: titleTextStyle, + }) + } + subtextStyle := Style{ + Font: opt.Font, + FontSize: opt.SubtextFontSize, + FontColor: opt.SubtextFontColor, + } + // 副标题 + for _, v := range splitTitleText(opt.Subtext) { + measureOptions = append(measureOptions, titleMeasureOption{ + text: v, + style: subtextStyle, + }) + } + textMaxWidth := 0 + textMaxHeight := 0 + for index, item := range measureOptions { + p.OverrideTextStyle(item.style) + textBox := p.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 + switch opt.Left { + case PositionRight: + titleX = p.Width() - textMaxWidth + case PositionCenter: + titleX = p.Width()>>1 - (textMaxWidth >> 1) + default: + if strings.HasSuffix(opt.Left, "%") { + value, _ := strconv.Atoi(strings.ReplaceAll(opt.Left, "%", "")) + titleX = p.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 { + p.OverrideTextStyle(item.style) + x := titleX + (textMaxWidth-item.width)>>1 + y := titleY + item.height + p.Text(item.text, x, y) + titleY += item.height + } + + return Box{ + Bottom: titleY, + Right: titleX + width, + }, nil +} From b69728dd1260336d9816b654e69797b7187dbbc6 Mon Sep 17 00:00:00 2001 From: vicanso Date: Tue, 14 Jun 2022 23:07:11 +0800 Subject: [PATCH 16/21] feat: support bar chart render --- axis.go | 6 +- bar_chart.go | 205 ++++++++++++++++++++++++++++++++++++ charts.go | 30 ++++-- examples/bar_chart/main.go | 127 ++++++++++++++++++++++ examples/line_chart/main.go | 4 +- examples/painter/main.go | 10 +- legend.go | 3 + line_chart.go | 58 ++++++---- mark_point.go | 3 +- xaxis.go | 6 +- yaxis.go | 6 +- 11 files changed, 408 insertions(+), 50 deletions(-) create mode 100644 bar_chart.go create mode 100644 examples/bar_chart/main.go diff --git a/axis.go b/axis.go index bb2e6a3..bd760b6 100644 --- a/axis.go +++ b/axis.go @@ -28,17 +28,17 @@ import ( type axisPainter struct { p *Painter - opt *AxisPainterOption + opt *AxisOption } -func NewAxisPainter(p *Painter, opt AxisPainterOption) *axisPainter { +func NewAxisPainter(p *Painter, opt AxisOption) *axisPainter { return &axisPainter{ p: p, opt: &opt, } } -type AxisPainterOption struct { +type AxisOption struct { // The theme of chart Theme ColorPalette // The label of axis diff --git a/bar_chart.go b/bar_chart.go new file mode 100644 index 0000000..9dadb1e --- /dev/null +++ b/bar_chart.go @@ -0,0 +1,205 @@ +// 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 barChart struct { + p *Painter + opt *BarChartOption +} + +func NewBarChart(p *Painter, opt BarChartOption) *barChart { + if opt.Theme == nil { + opt.Theme = NewTheme("") + } + return &barChart{ + p: p, + opt: &opt, + } +} + +type BarChartOption struct { + Theme ColorPalette + // The font size + Font *truetype.Font + // The data series list + SeriesList SeriesList + // The x axis option + XAxis XAxisOption + // The padding of line chart + Padding Box + // The y axis option + YAxisOptions []YAxisOption + // The option of title + Title TitleOption + // The legend option + Legend LegendOption +} + +func (b *barChart) Render() (Box, error) { + p := b.p + opt := b.opt + seriesList := opt.SeriesList + seriesList.init() + renderResult, err := defaultRender(p, defaultRenderOption{ + Theme: opt.Theme, + Padding: opt.Padding, + SeriesList: seriesList, + XAxis: opt.XAxis, + YAxisOptions: opt.YAxisOptions, + TitleOption: opt.Title, + LegendOption: opt.Legend, + }) + if err != nil { + return chart.BoxZero, err + } + seriesPainter := renderResult.seriesPainter + seriesList = seriesList.Filter(ChartTypeBar) + + xRange := NewRange(AxisRangeOption{ + DivideCount: len(opt.XAxis.Data), + Size: seriesPainter.Width(), + }) + 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(seriesList) + // 总的宽度-两个margin-(总数-1)的barMargin + barWidth := (width - 2*margin - barMargin*(seriesCount-1)) / len(seriesList) + barMaxHeight := seriesPainter.Height() + theme := opt.Theme + seriesNames := seriesList.Names() + + markPointPainter := NewMarkPointPainter(seriesPainter) + markLinePainter := NewMarkLinePainter(seriesPainter) + rendererList := []Renderer{ + markPointPainter, + markLinePainter, + } + for i := range seriesList { + series := seriesList[i] + yRange := renderResult.axisRanges[series.AxisIndex] + index := series.index + if index == 0 { + index = i + } + seriesColor := theme.GetSeriesColor(index) + + divideValues := xRange.AutoDivide() + points := make([]Point, len(series.Data)) + 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 + + seriesPainter.OverrideDrawingStyle(Style{ + FillColor: fillColor, + }).Rect(chart.Box{ + Top: top, + Left: x, + Right: x + barWidth, + Bottom: barMaxHeight - 1, + }) + // 用于生成marker point + points[j] = Point{ + // 居中的位置 + X: x + barWidth>>1, + Y: top, + } + // 用于生成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 := Style{ + FontColor: theme.GetTextColor(), + FontSize: labelFontSize, + Font: opt.Font, + } + if !series.Label.Color.IsZero() { + labelStyle.FontColor = series.Label.Color + } + seriesPainter.OverrideTextStyle(labelStyle) + textBox := seriesPainter.MeasureText(text) + seriesPainter.Text(text, x+(barWidth-textBox.Width())>>1, barMaxHeight-h-distance) + } + + markPointPainter.Add(markPointRenderOption{ + FillColor: seriesColor, + Font: opt.Font, + Series: series, + Points: points, + }) + markLinePainter.Add(markLineRenderOption{ + FillColor: seriesColor, + FontColor: opt.Theme.GetTextColor(), + StrokeColor: seriesColor, + Font: opt.Font, + Series: series, + Range: yRange, + }) + } + // 最大、最小的mark point + err = doRender(rendererList...) + if err != nil { + return chart.BoxZero, err + } + + return chart.BoxZero, nil +} diff --git a/charts.go b/charts.go index 947fa8d..09dfc3f 100644 --- a/charts.go +++ b/charts.go @@ -56,14 +56,21 @@ type defaultRenderOption struct { TitleOption TitleOption // The legend option LegendOption LegendOption + // background is filled + backgroundIsFilled bool } type defaultRenderResult struct { axisRanges map[int]axisRange - p *Painter + // 图例区域 + seriesPainter *Painter } func defaultRender(p *Painter, opt defaultRenderOption) (*defaultRenderResult, error) { + if !opt.backgroundIsFilled { + p.SetBackground(p.Width(), p.Height(), opt.Theme.GetBackgroundColor()) + } + if !opt.Padding.IsZero() { p = p.Child(PainterPaddingOption(opt.Padding)) } @@ -157,8 +164,8 @@ func defaultRender(p *Painter, opt defaultRenderOption) (*defaultRenderResult, e return nil, err } - result.p = p.Child(PainterPaddingOption(Box{ - Bottom: rangeHeight, + result.seriesPainter = p.Child(PainterPaddingOption(Box{ + Bottom: defaultXAxisHeight, Left: rangeWidthLeft, Right: rangeWidthRight, })) @@ -200,14 +207,15 @@ func Render(opt ChartOption) (*Painter, error) { lineChartSeriesList := seriesList.Filter(ChartTypeLine) if len(lineChartSeriesList) != 0 { renderer := NewLineChart(p, LineChartOption{ - Theme: opt.theme, - Font: opt.font, - SeriesList: lineChartSeriesList, - XAxis: opt.XAxis, - Padding: opt.Padding, - YAxisOptions: opt.YAxisOptions, - TitleOption: opt.Title, - LegendOption: opt.Legend, + Theme: opt.theme, + Font: opt.font, + SeriesList: lineChartSeriesList, + XAxis: opt.XAxis, + Padding: opt.Padding, + YAxisOptions: opt.YAxisOptions, + Title: opt.Title, + Legend: opt.Legend, + backgroundIsFilled: true, }) rendererList = append(rendererList, renderer) } diff --git a/examples/bar_chart/main.go b/examples/bar_chart/main.go new file mode 100644 index 0000000..5d5da2a --- /dev/null +++ b/examples/bar_chart/main.go @@ -0,0 +1,127 @@ +package main + +import ( + "io/ioutil" + "os" + "path/filepath" + + "github.com/vicanso/go-charts" +) + +func writeFile(buf []byte) error { + tmpPath := "./tmp" + err := os.MkdirAll(tmpPath, 0700) + if err != nil { + return err + } + + file := filepath.Join(tmpPath, "bar-chart.png") + err = ioutil.WriteFile(file, buf, 0600) + if err != nil { + return err + } + return nil +} + +func main() { + p, err := charts.NewPainter(charts.PainterOptions{ + Width: 800, + Height: 600, + Type: charts.ChartOutputPNG, + }) + if err != nil { + panic(err) + } + _, err = charts.NewBarChart(p, charts.BarChartOption{ + Title: charts.TitleOption{ + Text: "Rainfall vs Evaporation", + Subtext: "Fake Data", + }, + Padding: charts.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, + ), + }, + }, + }).Render() + if err != nil { + panic(err) + } + + buf, err := p.Bytes() + if err != nil { + panic(err) + } + err = writeFile(buf) + if err != nil { + panic(err) + } +} diff --git a/examples/line_chart/main.go b/examples/line_chart/main.go index c168f08..414f676 100644 --- a/examples/line_chart/main.go +++ b/examples/line_chart/main.go @@ -39,10 +39,10 @@ func main() { Right: 10, Bottom: 10, }, - TitleOption: charts.TitleOption{ + Title: charts.TitleOption{ Text: "Line", }, - LegendOption: charts.LegendOption{ + Legend: charts.LegendOption{ Data: []string{ "Email", "Union Ads", diff --git a/examples/painter/main.go b/examples/painter/main.go index 5022584..cf2bb81 100644 --- a/examples/painter/main.go +++ b/examples/painter/main.go @@ -490,7 +490,7 @@ func main() { Left: 1, Right: p.Width() - 1, Bottom: top + 50, - })), charts.AxisPainterOption{ + })), charts.AxisOption{ Data: []string{ "Mon", "Tue", @@ -512,7 +512,7 @@ func main() { Left: 1, Right: p.Width() - 1, Bottom: top + 50, - })), charts.AxisPainterOption{ + })), charts.AxisOption{ Position: charts.PositionTop, BoundaryGap: charts.FalseFlag(), Data: []string{ @@ -536,7 +536,7 @@ func main() { Left: 10, Right: 60, Bottom: top + 200, - })), charts.AxisPainterOption{ + })), charts.AxisOption{ Position: charts.PositionLeft, Data: []string{ "Mon", @@ -557,7 +557,7 @@ func main() { Left: 100, Right: 150, Bottom: top + 200, - })), charts.AxisPainterOption{ + })), charts.AxisOption{ Position: charts.PositionRight, Data: []string{ "Mon", @@ -579,7 +579,7 @@ func main() { Left: 150, Right: 300, Bottom: top + 200, - })), charts.AxisPainterOption{ + })), charts.AxisOption{ BoundaryGap: charts.FalseFlag(), Position: charts.PositionLeft, Data: []string{ diff --git a/legend.go b/legend.go index b8a6fdc..e645e17 100644 --- a/legend.go +++ b/legend.go @@ -87,6 +87,9 @@ func (l *legendPainter) Render() (Box, error) { if opt.FontColor.IsZero() { opt.FontColor = theme.GetTextColor() } + if opt.Left == "" { + opt.Left = PositionCenter + } p := l.p p.SetTextStyle(Style{ FontSize: opt.FontSize, diff --git a/line_chart.go b/line_chart.go index 5f4ea4f..451edfe 100644 --- a/line_chart.go +++ b/line_chart.go @@ -56,9 +56,11 @@ type LineChartOption struct { // The y axis option YAxisOptions []YAxisOption // The option of title - TitleOption TitleOption + Title TitleOption // The legend option - LegendOption LegendOption + Legend LegendOption + // background is filled + backgroundIsFilled bool } func (l *lineChart) Render() (Box, error) { @@ -67,26 +69,39 @@ func (l *lineChart) Render() (Box, error) { seriesList := opt.SeriesList seriesList.init() renderResult, err := defaultRender(p, defaultRenderOption{ - Theme: opt.Theme, - Padding: opt.Padding, - SeriesList: seriesList, - XAxis: opt.XAxis, - YAxisOptions: opt.YAxisOptions, - TitleOption: opt.TitleOption, - LegendOption: opt.LegendOption, + Theme: opt.Theme, + Padding: opt.Padding, + SeriesList: seriesList, + XAxis: opt.XAxis, + YAxisOptions: opt.YAxisOptions, + TitleOption: opt.Title, + LegendOption: opt.Legend, + backgroundIsFilled: opt.backgroundIsFilled, }) if err != nil { return chart.BoxZero, err } + boundaryGap := true + if opt.XAxis.BoundaryGap != nil && !*opt.XAxis.BoundaryGap { + boundaryGap = false + } seriesList = seriesList.Filter(ChartTypeLine) - seriesPainter := renderResult.p + seriesPainter := renderResult.seriesPainter - xDivideValues := autoDivide(seriesPainter.Width(), len(opt.XAxis.Data)) + xDivideCount := len(opt.XAxis.Data) + if !boundaryGap { + xDivideCount-- + } + xDivideValues := autoDivide(seriesPainter.Width(), xDivideCount) xValues := make([]int, len(xDivideValues)-1) - for i := 0; i < len(xDivideValues)-1; i++ { - xValues[i] = (xDivideValues[i] + xDivideValues[i+1]) >> 1 + if boundaryGap { + for i := 0; i < len(xDivideValues)-1; i++ { + xValues[i] = (xDivideValues[i] + xDivideValues[i+1]) >> 1 + } + } else { + xValues = xDivideValues } markPointPainter := NewMarkPointPainter(seriesPainter) markLinePainter := NewMarkLinePainter(seriesPainter) @@ -94,7 +109,8 @@ func (l *lineChart) Render() (Box, error) { markPointPainter, markLinePainter, } - for index, series := range seriesList { + for index := range seriesList { + series := seriesList[index] seriesColor := opt.Theme.GetSeriesColor(index) drawingStyle := Style{ StrokeColor: seriesColor, @@ -102,10 +118,10 @@ func (l *lineChart) Render() (Box, error) { } seriesPainter.SetDrawingStyle(drawingStyle) - yr := renderResult.axisRanges[series.AxisIndex] + yRange := renderResult.axisRanges[series.AxisIndex] points := make([]Point, 0) for i, item := range series.Data { - h := yr.getRestHeight(item.Value) + h := yRange.getRestHeight(item.Value) p := Point{ X: xValues[i], Y: h, @@ -136,15 +152,13 @@ func (l *lineChart) Render() (Box, error) { StrokeColor: seriesColor, Font: opt.Font, Series: series, - Range: yr, + Range: yRange, }) } // 最大、最小的mark point - for _, renderer := range rendererList { - _, err = renderer.Render() - if err != nil { - return chart.BoxZero, err - } + err = doRender(rendererList...) + if err != nil { + return chart.BoxZero, err } return p.box, nil diff --git a/mark_point.go b/mark_point.go index ce3cb0f..07daf57 100644 --- a/mark_point.go +++ b/mark_point.go @@ -64,6 +64,7 @@ func NewMarkPointPainter(p *Painter) *markPointPainter { func (m *markPointPainter) Render() (Box, error) { painter := m.p + theme := m.p.theme for _, opt := range m.options { s := opt.Series if len(s.MarkPoint.Data) == 0 { @@ -78,7 +79,7 @@ func (m *markPointPainter) Render() (Box, error) { painter.OverrideDrawingStyle(Style{ FillColor: opt.FillColor, }).OverrideTextStyle(Style{ - FontColor: NewTheme(ThemeDark).GetTextColor(), + FontColor: theme.GetTextColor(), FontSize: labelFontSize, StrokeWidth: 1, Font: opt.Font, diff --git a/xaxis.go b/xaxis.go index a8c28c0..d8f6700 100644 --- a/xaxis.go +++ b/xaxis.go @@ -62,12 +62,12 @@ func NewXAxisOption(data []string, boundaryGap ...*bool) XAxisOption { return opt } -func (opt *XAxisOption) ToAxisPainterOption() AxisPainterOption { +func (opt *XAxisOption) ToAxisOption() AxisOption { position := PositionBottom if opt.Position == PositionTop { position = PositionTop } - return AxisPainterOption{ + return AxisOption{ Theme: opt.Theme, Data: opt.Data, BoundaryGap: opt.BoundaryGap, @@ -84,5 +84,5 @@ func NewBottomXAxis(p *Painter, opt XAxisOption) *axisPainter { p = p.Child(PainterPaddingOption(Box{ Top: p.Height() - defaultXAxisHeight, })) - return NewAxisPainter(p, opt.ToAxisPainterOption()) + return NewAxisPainter(p, opt.ToAxisOption()) } diff --git a/yaxis.go b/yaxis.go index 653f6ec..b011a74 100644 --- a/yaxis.go +++ b/yaxis.go @@ -39,12 +39,12 @@ type YAxisOption struct { FontColor Color } -func (opt *YAxisOption) ToAxisPainterOption() AxisPainterOption { +func (opt *YAxisOption) ToAxisOption() AxisOption { position := PositionLeft if opt.Position == PositionRight { position = PositionRight } - return AxisPainterOption{ + return AxisOption{ Theme: opt.Theme, Data: opt.Data, Position: position, @@ -62,5 +62,5 @@ func NewLeftYAxis(p *Painter, opt YAxisOption) *axisPainter { p = p.Child(PainterPaddingOption(Box{ Bottom: defaultXAxisHeight, })) - return NewAxisPainter(p, opt.ToAxisPainterOption()) + return NewAxisPainter(p, opt.ToAxisOption()) } From 3f245215931296b1de0f5c30fd7363a147a3c79a Mon Sep 17 00:00:00 2001 From: vicanso Date: Wed, 15 Jun 2022 23:30:37 +0800 Subject: [PATCH 17/21] feat: support horizontal bar chart --- alias.go | 4 + axis.go | 25 +++- bar_chart.go | 46 +++--- charts.go | 119 +++++++++++++--- examples/charts/main.go | 195 ++++++++++++++++++++++++++ examples/horizontal_bar_chart/main.go | 94 +++++++++++++ horizontal_bar_chart.go | 152 ++++++++++++++++++++ legend.go | 8 +- line_chart.go | 50 ++++--- mark_line.go | 3 +- mark_point.go | 3 +- series.go | 6 + title.go | 5 + xaxis.go | 33 +++-- yaxis.go | 25 +++- 15 files changed, 677 insertions(+), 91 deletions(-) create mode 100644 examples/horizontal_bar_chart/main.go create mode 100644 horizontal_bar_chart.go diff --git a/alias.go b/alias.go index 0b161e6..a96f50b 100644 --- a/alias.go +++ b/alias.go @@ -31,6 +31,8 @@ type Box = chart.Box type Style = chart.Style type Color = drawing.Color +var BoxZero = chart.BoxZero + type Point struct { X int Y int @@ -42,6 +44,8 @@ const ( ChartTypePie = "pie" ChartTypeRadar = "radar" ChartTypeFunnel = "funnel" + // horizontal bar + ChartTypeHorizontalBar = "horizontalBar" ) const ( diff --git a/axis.go b/axis.go index bd760b6..d069c39 100644 --- a/axis.go +++ b/axis.go @@ -153,6 +153,8 @@ func (a *axisPainter) Render() (Box, error) { padding.Right = top.Width() - width case PositionRight: padding.Left = top.Width() - width + default: + padding.Top = top.Height() - defaultXAxisHeight } p := top.Child(PainterPaddingOption(padding)) @@ -240,7 +242,10 @@ func (a *axisPainter) Render() (Box, error) { x0 = 0 x1 = top.Width() - p.Width() } - for _, y := range autoDivide(height, tickCount) { + for index, y := range autoDivide(height, tickCount) { + if index == 0 { + continue + } top.LineStroke([]Point{ { X: x0, @@ -252,6 +257,24 @@ func (a *axisPainter) Render() (Box, error) { }, }) } + } else { + y0 := p.Height() - defaultXAxisHeight + y1 := top.Height() - defaultXAxisHeight + for index, x := range autoDivide(width, tickCount) { + if index == 0 { + continue + } + top.LineStroke([]Point{ + { + X: x, + Y: y0, + }, + { + X: x, + Y: y1, + }, + }) + } } } diff --git a/bar_chart.go b/bar_chart.go index 9dadb1e..8fae4df 100644 --- a/bar_chart.go +++ b/bar_chart.go @@ -60,25 +60,10 @@ type BarChartOption struct { Legend LegendOption } -func (b *barChart) Render() (Box, error) { +func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (Box, error) { p := b.p opt := b.opt - seriesList := opt.SeriesList - seriesList.init() - renderResult, err := defaultRender(p, defaultRenderOption{ - Theme: opt.Theme, - Padding: opt.Padding, - SeriesList: seriesList, - XAxis: opt.XAxis, - YAxisOptions: opt.YAxisOptions, - TitleOption: opt.Title, - LegendOption: opt.Legend, - }) - if err != nil { - return chart.BoxZero, err - } - seriesPainter := renderResult.seriesPainter - seriesList = seriesList.Filter(ChartTypeBar) + seriesPainter := result.seriesPainter xRange := NewRange(AxisRangeOption{ DivideCount: len(opt.XAxis.Data), @@ -112,7 +97,7 @@ func (b *barChart) Render() (Box, error) { } for i := range seriesList { series := seriesList[i] - yRange := renderResult.axisRanges[series.AxisIndex] + yRange := result.axisRanges[series.AxisIndex] index := series.index if index == 0 { index = i @@ -196,10 +181,29 @@ func (b *barChart) Render() (Box, error) { }) } // 最大、最小的mark point - err = doRender(rendererList...) + err := doRender(rendererList...) if err != nil { - return chart.BoxZero, err + return BoxZero, err } - return chart.BoxZero, nil + return p.box, nil +} + +func (b *barChart) Render() (Box, error) { + p := b.p + opt := b.opt + renderResult, err := defaultRender(p, defaultRenderOption{ + Theme: opt.Theme, + Padding: opt.Padding, + SeriesList: opt.SeriesList, + XAxis: opt.XAxis, + YAxisOptions: opt.YAxisOptions, + TitleOption: opt.Title, + LegendOption: opt.Legend, + }) + if err != nil { + return BoxZero, err + } + seriesList := opt.SeriesList.Filter(ChartTypeLine) + return b.render(renderResult, seriesList) } diff --git a/charts.go b/charts.go index 09dfc3f..ae14c8d 100644 --- a/charts.go +++ b/charts.go @@ -22,6 +22,8 @@ package charts +import "errors" + const labelFontSize = 10 const defaultDotWidth = 2.0 const defaultStrokeWidth = 2.0 @@ -44,6 +46,28 @@ type Renderer interface { Render() (Box, error) } +type renderHandler struct { + list []func() error +} + +func (rh *renderHandler) Add(fn func() error) { + list := rh.list + if len(list) == 0 { + list = make([]func() error, 0) + } + rh.list = append(list, fn) +} + +func (rh *renderHandler) Do() error { + for _, fn := range rh.list { + err := fn() + if err != nil { + return err + } + } + return nil +} + type defaultRenderOption struct { Theme ColorPalette Padding Box @@ -58,6 +82,8 @@ type defaultRenderOption struct { LegendOption LegendOption // background is filled backgroundIsFilled bool + // x y axis is reversed + axisReversed bool } type defaultRenderResult struct { @@ -67,6 +93,8 @@ type defaultRenderResult struct { } func defaultRender(p *Painter, opt defaultRenderOption) (*defaultRenderResult, error) { + seriesList := opt.SeriesList + seriesList.init() if !opt.backgroundIsFilled { p.SetBackground(p.Width(), p.Height(), opt.Theme.GetBackgroundColor()) } @@ -138,7 +166,13 @@ func defaultRender(p *Painter, opt defaultRenderOption) (*defaultRenderResult, e if yAxisOption.Theme == nil { yAxisOption.Theme = opt.Theme } - yAxisOption.Data = r.Values() + if !opt.axisReversed { + yAxisOption.Data = r.Values() + } else { + yAxisOption.isCategoryAxis = true + opt.XAxis.Data = r.Values() + opt.XAxis.isValueAxis = true + } reverseStringSlice(yAxisOption.Data) // TODO生成其它位置既yAxis yAxis := NewLeftYAxis(p, yAxisOption) @@ -201,30 +235,71 @@ func Render(opt ChartOption) (*Painter, error) { seriesList := opt.SeriesList seriesList.init() - rendererList := make([]Renderer, 0) - // line chart - lineChartSeriesList := seriesList.Filter(ChartTypeLine) - if len(lineChartSeriesList) != 0 { - renderer := NewLineChart(p, LineChartOption{ - Theme: opt.theme, - Font: opt.font, - SeriesList: lineChartSeriesList, - XAxis: opt.XAxis, - Padding: opt.Padding, - YAxisOptions: opt.YAxisOptions, - Title: opt.Title, - Legend: opt.Legend, - backgroundIsFilled: true, - }) - rendererList = append(rendererList, renderer) + lineSeriesList := seriesList.Filter(ChartTypeLine) + barSeriesList := seriesList.Filter(ChartTypeBar) + horizontalBarSeriesList := seriesList.Filter(ChartTypeHorizontalBar) + if len(horizontalBarSeriesList) != 0 && len(horizontalBarSeriesList) != len(seriesList) { + return nil, errors.New("Horizontal bar can not mix other charts") } - for _, renderer := range rendererList { - _, err := renderer.Render() - if err != nil { - return nil, err - } + axisReversed := len(horizontalBarSeriesList) != 0 + + renderResult, err := defaultRender(p, defaultRenderOption{ + Theme: opt.theme, + Padding: opt.Padding, + SeriesList: opt.SeriesList, + XAxis: opt.XAxis, + YAxisOptions: opt.YAxisOptions, + TitleOption: opt.Title, + LegendOption: opt.Legend, + axisReversed: axisReversed, + }) + if err != nil { + return nil, err + } + + handler := renderHandler{} + + if len(lineSeriesList) != 0 { + handler.Add(func() error { + _, err := NewLineChart(p, LineChartOption{ + Theme: opt.theme, + Font: opt.font, + XAxis: opt.XAxis, + }).render(renderResult, lineSeriesList) + return err + }) + } + + // bar chart + if len(barSeriesList) != 0 { + handler.Add(func() error { + _, err := NewBarChart(p, BarChartOption{ + Theme: opt.theme, + Font: opt.font, + XAxis: opt.XAxis, + }).render(renderResult, barSeriesList) + return err + }) + } + + // horizontal bar chart + if len(horizontalBarSeriesList) != 0 { + handler.Add(func() error { + _, err := NewHorizontalBarChart(p, HorizontalBarChartOption{ + Theme: opt.theme, + Font: opt.font, + YAxisOptions: opt.YAxisOptions, + }).render(renderResult, horizontalBarSeriesList) + return err + }) + } + + err = handler.Do() + + if err != nil { + return nil, err } return p, nil diff --git a/examples/charts/main.go b/examples/charts/main.go index 18f5a95..c7986a4 100644 --- a/examples/charts/main.go +++ b/examples/charts/main.go @@ -218,6 +218,201 @@ func indexHandler(w http.ResponseWriter, req *http.Request) { }, }, }, + // 柱状图 + { + 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.IconRect, + }, + 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: charts.Style{ + FillColor: charts.Color{ + R: 169, + G: 0, + B: 0, + A: 255, + }, + }, + }, + { + Value: 230, + }, + { + Value: 140, + }, + { + Value: 100, + }, + { + Value: 200, + }, + { + Value: 180, + }, + }, + }, + }, + }, + // 水平柱状图 + { + Title: charts.TitleOption{ + Text: "World Population", + }, + Padding: charts.Box{ + Top: 20, + Right: 40, + Bottom: 20, + Left: 20, + }, + Legend: charts.NewLegendOption([]string{ + "2011", + "2012", + }), + YAxisOptions: charts.NewYAxisOptions([]string{ + "Brazil", + "Indonesia", + "USA", + "India", + "China", + "World", + }), + SeriesList: []charts.Series{ + { + Type: charts.ChartTypeHorizontalBar, + Data: charts.NewSeriesDataFromValues([]float64{ + 18203, + 23489, + 29034, + 104970, + 131744, + 630230, + }), + }, + { + Type: charts.ChartTypeHorizontalBar, + Data: charts.NewSeriesDataFromValues([]float64{ + 19325, + 23438, + 31000, + 121594, + 134141, + 681807, + }), + }, + }, + }, + { + Title: charts.TitleOption{ + Text: "Rainfall vs Evaporation", + Subtext: "Fake Data", + }, + Padding: charts.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, + ), + }, + }, + }, } handler(w, req, chartOptions, nil) } diff --git a/examples/horizontal_bar_chart/main.go b/examples/horizontal_bar_chart/main.go new file mode 100644 index 0000000..eecd9ec --- /dev/null +++ b/examples/horizontal_bar_chart/main.go @@ -0,0 +1,94 @@ +package main + +import ( + "io/ioutil" + "os" + "path/filepath" + + "github.com/vicanso/go-charts" +) + +func writeFile(buf []byte) error { + tmpPath := "./tmp" + err := os.MkdirAll(tmpPath, 0700) + if err != nil { + return err + } + + file := filepath.Join(tmpPath, "horizontal-bar-chart.png") + err = ioutil.WriteFile(file, buf, 0600) + if err != nil { + return err + } + return nil +} + +func main() { + p, err := charts.NewPainter(charts.PainterOptions{ + Width: 800, + Height: 600, + Type: charts.ChartOutputPNG, + }) + if err != nil { + panic(err) + } + _, err = charts.NewHorizontalBarChart(p, charts.HorizontalBarChartOption{ + Title: charts.TitleOption{ + Text: "World Population", + }, + Padding: charts.Box{ + Top: 20, + Right: 40, + Bottom: 20, + Left: 20, + }, + Legend: charts.NewLegendOption([]string{ + "2011", + "2012", + }), + YAxisOptions: charts.NewYAxisOptions([]string{ + "Brazil", + "Indonesia", + "USA", + "India", + "China", + "World", + }), + SeriesList: []charts.Series{ + { + Type: charts.ChartTypeHorizontalBar, + Data: charts.NewSeriesDataFromValues([]float64{ + 18203, + 23489, + 29034, + 104970, + 131744, + 630230, + }), + }, + { + Type: charts.ChartTypeHorizontalBar, + Data: charts.NewSeriesDataFromValues([]float64{ + 19325, + 23438, + 31000, + 121594, + 134141, + 681807, + }), + }, + }, + }).Render() + if err != nil { + panic(err) + } + + buf, err := p.Bytes() + if err != nil { + panic(err) + } + err = writeFile(buf) + if err != nil { + panic(err) + } +} diff --git a/horizontal_bar_chart.go b/horizontal_bar_chart.go new file mode 100644 index 0000000..87ca9ae --- /dev/null +++ b/horizontal_bar_chart.go @@ -0,0 +1,152 @@ +// 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 horizontalBarChart struct { + p *Painter + opt *HorizontalBarChartOption +} + +type HorizontalBarChartOption struct { + Theme ColorPalette + // The font size + Font *truetype.Font + // The data series list + SeriesList SeriesList + // The x axis option + XAxis XAxisOption + // The padding of line chart + Padding Box + // The y axis option + YAxisOptions []YAxisOption + // The option of title + Title TitleOption + // The legend option + Legend LegendOption +} + +func NewHorizontalBarChart(p *Painter, opt HorizontalBarChartOption) *horizontalBarChart { + if opt.Theme == nil { + opt.Theme = NewTheme("") + } + return &horizontalBarChart{ + p: p, + opt: &opt, + } +} + +func (h *horizontalBarChart) render(result *defaultRenderResult, seriesList SeriesList) (Box, error) { + p := h.p + opt := h.opt + seriesPainter := result.seriesPainter + yRange := result.axisRanges[0] + y0, y1 := yRange.GetRange(0) + height := int(y1 - y0) + // 每一块之间的margin + margin := 10 + // 每一个bar之间的margin + barMargin := 5 + if height < 20 { + margin = 2 + barMargin = 2 + } else if height < 50 { + margin = 5 + barMargin = 3 + } + seriesCount := len(seriesList) + // 总的高度-两个margin-(总数-1)的barMargin + barHeight := (height - 2*margin - barMargin*(seriesCount-1)) / len(seriesList) + + theme := opt.Theme + + max, min := seriesList.GetMaxMin(0) + xRange := NewRange(AxisRangeOption{ + Min: min, + Max: max, + DivideCount: defaultAxisDivideCount, + Size: seriesPainter.Width(), + }) + + for i := range seriesList { + series := seriesList[i] + index := series.index + if index == 0 { + index = i + } + seriesColor := theme.GetSeriesColor(index) + divideValues := yRange.AutoDivide() + for j, item := range series.Data { + if j >= yRange.divideCount { + continue + } + // 显示位置切换 + j = yRange.divideCount - j - 1 + y := divideValues[j] + y += margin + if i != 0 { + y += i * (barHeight + barMargin) + } + + w := int(xRange.getHeight(item.Value)) + fillColor := seriesColor + if !item.Style.FillColor.IsZero() { + fillColor = item.Style.FillColor + } + right := w + seriesPainter.OverrideDrawingStyle(Style{ + FillColor: fillColor, + }).Rect(chart.Box{ + Top: y, + Left: 0, + Right: right, + Bottom: y + barHeight, + }) + } + } + return p.box, nil +} + +func (h *horizontalBarChart) Render() (Box, error) { + p := h.p + opt := h.opt + renderResult, err := defaultRender(p, defaultRenderOption{ + Theme: opt.Theme, + Padding: opt.Padding, + SeriesList: opt.SeriesList, + XAxis: opt.XAxis, + YAxisOptions: opt.YAxisOptions, + TitleOption: opt.Title, + LegendOption: opt.Legend, + axisReversed: true, + }) + if err != nil { + return BoxZero, err + } + seriesList := opt.SeriesList.Filter(ChartTypeHorizontalBar) + return h.render(renderResult, seriesList) +} diff --git a/legend.go b/legend.go index e645e17..65793c9 100644 --- a/legend.go +++ b/legend.go @@ -155,17 +155,17 @@ func (l *legendPainter) Render() (Box, error) { drawIcon := func(top, left int) int { if opt.Icon == IconRect { p.Rect(Box{ - Top: top - legendHeight + 4, + Top: top - legendHeight + 8, Left: left, Right: left + legendWidth, - Bottom: top - 2, + Bottom: top + 1, }) } else { p.LegendLineDot(Box{ - Top: top, + Top: top + 1, Left: left, Right: left + legendWidth, - Bottom: top + legendHeight, + Bottom: top + legendHeight + 1, }) } return left + legendWidth diff --git a/line_chart.go b/line_chart.go index 451edfe..47a497f 100644 --- a/line_chart.go +++ b/line_chart.go @@ -24,7 +24,6 @@ package charts import ( "github.com/golang/freetype/truetype" - "github.com/wcharczuk/go-chart/v2" "github.com/wcharczuk/go-chart/v2/drawing" ) @@ -63,32 +62,15 @@ type LineChartOption struct { backgroundIsFilled bool } -func (l *lineChart) Render() (Box, error) { +func (l *lineChart) render(result *defaultRenderResult, seriesList SeriesList) (Box, error) { p := l.p opt := l.opt - seriesList := opt.SeriesList - seriesList.init() - renderResult, err := defaultRender(p, defaultRenderOption{ - Theme: opt.Theme, - Padding: opt.Padding, - SeriesList: seriesList, - XAxis: opt.XAxis, - YAxisOptions: opt.YAxisOptions, - TitleOption: opt.Title, - LegendOption: opt.Legend, - backgroundIsFilled: opt.backgroundIsFilled, - }) - if err != nil { - return chart.BoxZero, err - } boundaryGap := true if opt.XAxis.BoundaryGap != nil && !*opt.XAxis.BoundaryGap { boundaryGap = false } - seriesList = seriesList.Filter(ChartTypeLine) - - seriesPainter := renderResult.seriesPainter + seriesPainter := result.seriesPainter xDivideCount := len(opt.XAxis.Data) if !boundaryGap { @@ -118,7 +100,7 @@ func (l *lineChart) Render() (Box, error) { } seriesPainter.SetDrawingStyle(drawingStyle) - yRange := renderResult.axisRanges[series.AxisIndex] + yRange := result.axisRanges[series.AxisIndex] points := make([]Point, 0) for i, item := range series.Data { h := yRange.getRestHeight(item.Value) @@ -156,10 +138,32 @@ func (l *lineChart) Render() (Box, error) { }) } // 最大、最小的mark point - err = doRender(rendererList...) + err := doRender(rendererList...) if err != nil { - return chart.BoxZero, err + return BoxZero, err } return p.box, nil } + +func (l *lineChart) Render() (Box, error) { + p := l.p + opt := l.opt + + renderResult, err := defaultRender(p, defaultRenderOption{ + Theme: opt.Theme, + Padding: opt.Padding, + SeriesList: opt.SeriesList, + XAxis: opt.XAxis, + YAxisOptions: opt.YAxisOptions, + TitleOption: opt.Title, + LegendOption: opt.Legend, + backgroundIsFilled: opt.backgroundIsFilled, + }) + if err != nil { + return BoxZero, err + } + seriesList := opt.SeriesList.Filter(ChartTypeLine) + + return l.render(renderResult, seriesList) +} diff --git a/mark_line.go b/mark_line.go index 9a9d568..bb1b602 100644 --- a/mark_line.go +++ b/mark_line.go @@ -24,7 +24,6 @@ package charts import ( "github.com/golang/freetype/truetype" - "github.com/wcharczuk/go-chart/v2" ) func NewMarkLine(markLineTypes ...string) SeriesMarkLine { @@ -104,7 +103,7 @@ func (m *markLinePainter) Render() (Box, error) { painter.Text(text, width, y+textBox.Height()>>1-2) } } - return chart.BoxZero, nil + return BoxZero, nil } func markLineRender(opt markLineRenderOption) { diff --git a/mark_point.go b/mark_point.go index 07daf57..3d43a73 100644 --- a/mark_point.go +++ b/mark_point.go @@ -24,7 +24,6 @@ package charts import ( "github.com/golang/freetype/truetype" - "github.com/wcharczuk/go-chart/v2" ) func NewMarkPoint(markPointTypes ...string) SeriesMarkPoint { @@ -99,5 +98,5 @@ func (m *markPointPainter) Render() (Box, error) { painter.Text(text, p.X-textBox.Width()>>1, p.Y-symbolSize>>1-2) } } - return chart.BoxZero, nil + return BoxZero, nil } diff --git a/series.go b/series.go index 2888f30..4808bcb 100644 --- a/series.go +++ b/series.go @@ -132,6 +132,12 @@ func (sl SeriesList) init() { } } +func (sl SeriesList) reverse() { + for i, j := 0, len(sl)-1; i < j; i, j = i+1, j-1 { + sl[i], sl[j] = sl[j], sl[i] + } +} + func (sl SeriesList) Filter(chartType string) SeriesList { arr := make(SeriesList, 0) for index, item := range sl { diff --git a/title.go b/title.go index 30831ac..a805c55 100644 --- a/title.go +++ b/title.go @@ -95,6 +95,11 @@ func (t *titlePainter) Render() (Box, error) { opt := t.opt p := t.p theme := opt.Theme + + if opt.Text == "" && opt.Subtext == "" { + return BoxZero, nil + } + measureOptions := make([]titleMeasureOption, 0) if opt.Font == nil { diff --git a/xaxis.go b/xaxis.go index d8f6700..f06d71f 100644 --- a/xaxis.go +++ b/xaxis.go @@ -47,7 +47,8 @@ type XAxisOption struct { // The line color of axis StrokeColor Color // The color of label - FontColor Color + FontColor Color + isValueAxis bool } const defaultXAxisHeight = 30 @@ -67,22 +68,26 @@ func (opt *XAxisOption) ToAxisOption() AxisOption { if opt.Position == PositionTop { position = PositionTop } - return AxisOption{ - Theme: opt.Theme, - Data: opt.Data, - BoundaryGap: opt.BoundaryGap, - Position: position, - SplitNumber: opt.SplitNumber, - StrokeColor: opt.StrokeColor, - FontSize: opt.FontSize, - Font: opt.Font, - FontColor: opt.FontColor, + axisOpt := AxisOption{ + Theme: opt.Theme, + Data: opt.Data, + BoundaryGap: opt.BoundaryGap, + Position: position, + SplitNumber: opt.SplitNumber, + StrokeColor: opt.StrokeColor, + FontSize: opt.FontSize, + Font: opt.Font, + FontColor: opt.FontColor, + SplitLineColor: opt.Theme.GetAxisSplitLineColor(), } + if opt.isValueAxis { + axisOpt.SplitLineShow = true + axisOpt.StrokeWidth = -1 + axisOpt.BoundaryGap = FalseFlag() + } + return axisOpt } func NewBottomXAxis(p *Painter, opt XAxisOption) *axisPainter { - p = p.Child(PainterPaddingOption(Box{ - Top: p.Height() - defaultXAxisHeight, - })) return NewAxisPainter(p, opt.ToAxisOption()) } diff --git a/yaxis.go b/yaxis.go index b011a74..609924f 100644 --- a/yaxis.go +++ b/yaxis.go @@ -36,7 +36,22 @@ type YAxisOption struct { // The position of axis, it can be 'left' or 'right' Position string // The color of label - FontColor Color + FontColor Color + isCategoryAxis bool +} + +func NewYAxisOptions(data []string, others ...[]string) []YAxisOption { + arr := [][]string{ + data, + } + arr = append(arr, others...) + opts := make([]YAxisOption, 0) + for _, data := range arr { + opts = append(opts, YAxisOption{ + Data: data, + }) + } + return opts } func (opt *YAxisOption) ToAxisOption() AxisOption { @@ -44,7 +59,7 @@ func (opt *YAxisOption) ToAxisOption() AxisOption { if opt.Position == PositionRight { position = PositionRight } - return AxisOption{ + axisOpt := AxisOption{ Theme: opt.Theme, Data: opt.Data, Position: position, @@ -56,6 +71,12 @@ func (opt *YAxisOption) ToAxisOption() AxisOption { SplitLineShow: true, SplitLineColor: opt.Theme.GetAxisSplitLineColor(), } + if opt.isCategoryAxis { + axisOpt.BoundaryGap = TrueFlag() + axisOpt.StrokeWidth = 1 + axisOpt.SplitLineShow = false + } + return axisOpt } func NewLeftYAxis(p *Painter, opt YAxisOption) *axisPainter { From 65a1cb11adfda2466a77ecb333fd3f216467993d Mon Sep 17 00:00:00 2001 From: vicanso Date: Thu, 16 Jun 2022 23:08:20 +0800 Subject: [PATCH 18/21] feat: support pie, radar and funnel chart --- axis.go | 34 ++- bar_chart.go | 16 +- chart_option.go | 2 +- charts.go | 150 ++++++++-- echarts.go | 505 ++++++++++++++++++++++++++++++++++ examples/charts/main.go | 372 +++++++++++++++++++++++-- examples/funnel_chart/main.go | 97 +++++++ examples/pie_chart/main.go | 83 ++++++ examples/radar_chart/main.go | 112 ++++++++ funnel_chart.go | 172 ++++++++++++ horizontal_bar_chart.go | 14 +- legend.go | 21 +- line_chart.go | 2 +- painter.go | 3 + pie_chart.go | 211 ++++++++++++++ radar_chart.go | 245 +++++++++++++++++ xaxis.go | 5 +- yaxis.go | 28 +- 18 files changed, 1987 insertions(+), 85 deletions(-) create mode 100644 echarts.go create mode 100644 examples/funnel_chart/main.go create mode 100644 examples/pie_chart/main.go create mode 100644 examples/radar_chart/main.go create mode 100644 funnel_chart.go create mode 100644 pie_chart.go create mode 100644 radar_chart.go diff --git a/axis.go b/axis.go index d069c39..7b828d2 100644 --- a/axis.go +++ b/axis.go @@ -23,7 +23,10 @@ package charts import ( + "strings" + "github.com/golang/freetype/truetype" + "github.com/wcharczuk/go-chart/v2" ) type axisPainter struct { @@ -41,11 +44,15 @@ func NewAxisPainter(p *Painter, opt AxisOption) *axisPainter { type AxisOption struct { // The theme of chart Theme ColorPalette + // Formatter for y axis text value + Formatter string // The label of axis Data []string // 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. @@ -74,6 +81,9 @@ func (a *axisPainter) Render() (Box, error) { opt := a.opt top := a.p theme := opt.Theme + if opt.Show != nil && !*opt.Show { + return BoxZero, nil + } strokeWidth := opt.StrokeWidth if strokeWidth == 0 { @@ -97,10 +107,15 @@ func (a *axisPainter) Render() (Box, error) { strokeColor = theme.GetAxisStrokeColor() } - tickCount := opt.SplitNumber - if tickCount == 0 { - tickCount = len(opt.Data) + data := opt.Data + formatter := opt.Formatter + if len(formatter) != 0 { + for index, text := range data { + data[index] = strings.ReplaceAll(formatter, "{value}", text) + } } + dataCount := len(data) + tickCount := dataCount boundaryGap := true if opt.BoundaryGap != nil && !*opt.BoundaryGap { @@ -118,8 +133,6 @@ func (a *axisPainter) Render() (Box, error) { labelPosition = PositionCenter } - // TODO 计算unit - unit := 1 // 如果小于0,则表示不处理 tickLength := getDefaultInt(opt.TickLength, 5) labelMargin := getDefaultInt(opt.LabelMargin, 5) @@ -133,7 +146,9 @@ func (a *axisPainter) Render() (Box, error) { } top.SetDrawingStyle(style).OverrideTextStyle(style) - textMaxWidth, textMaxHeight := top.MeasureTextMaxWidthHeight(opt.Data) + textMaxWidth, textMaxHeight := top.MeasureTextMaxWidthHeight(data) + textCount := ceilFloatToInt(float64(top.Width()) / float64(textMaxWidth)) + unit := ceilFloatToInt(float64(dataCount) / float64(chart.MaxInt(textCount, opt.SplitNumber))) width := 0 height := 0 @@ -226,7 +241,7 @@ func (a *axisPainter) Render() (Box, error) { Right: labelPaddingRight, })).MultiText(MultiTextOption{ Align: textAlign, - TextList: opt.Data, + TextList: data, Orient: orient, Unit: unit, Position: labelPosition, @@ -242,10 +257,7 @@ func (a *axisPainter) Render() (Box, error) { x0 = 0 x1 = top.Width() - p.Width() } - for index, y := range autoDivide(height, tickCount) { - if index == 0 { - continue - } + for _, y := range autoDivide(height, tickCount) { top.LineStroke([]Point{ { X: x0, diff --git a/bar_chart.go b/bar_chart.go index 8fae4df..8330542 100644 --- a/bar_chart.go +++ b/bar_chart.go @@ -95,14 +95,10 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B markPointPainter, markLinePainter, } - for i := range seriesList { - series := seriesList[i] + for index := range seriesList { + series := seriesList[index] yRange := result.axisRanges[series.AxisIndex] - index := series.index - if index == 0 { - index = i - } - seriesColor := theme.GetSeriesColor(index) + seriesColor := theme.GetSeriesColor(series.index) divideValues := xRange.AutoDivide() points := make([]Point, len(series.Data)) @@ -112,8 +108,8 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B } x := divideValues[j] x += margin - if i != 0 { - x += i * (barWidth + barMargin) + if index != 0 { + x += index * (barWidth + barMargin) } h := int(yRange.getHeight(item.Value)) @@ -151,7 +147,7 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B if distance == 0 { distance = 5 } - text := NewValueLabelFormater(seriesNames, series.Label.Formatter)(i, item.Value, -1) + text := NewValueLabelFormater(seriesNames, series.Label.Formatter)(index, item.Value, -1) labelStyle := Style{ FontColor: theme.GetTextColor(), FontSize: labelFontSize, diff --git a/chart_option.go b/chart_option.go index 6ca7cd7..0cea754 100644 --- a/chart_option.go +++ b/chart_option.go @@ -59,7 +59,7 @@ type ChartOption struct { // The series list SeriesList SeriesList // The radar indicator list - // RadarIndicators []RadarIndicator + RadarIndicators []RadarIndicator // The background color of chart BackgroundColor Color // The child charts diff --git a/charts.go b/charts.go index ae14c8d..51e247a 100644 --- a/charts.go +++ b/charts.go @@ -22,7 +22,10 @@ package charts -import "errors" +import ( + "errors" + "sort" +) const labelFontSize = 10 const defaultDotWidth = 2.0 @@ -140,16 +143,29 @@ func defaultRender(p *Painter, opt defaultRenderOption) (*defaultRenderResult, e if containsInt(axisIndexList, series.AxisIndex) { continue } - axisIndexList = append(axisIndexList, series.index) + axisIndexList = append(axisIndexList, series.AxisIndex) } // 高度需要减去x轴的高度 rangeHeight := p.Height() - defaultXAxisHeight rangeWidthLeft := 0 rangeWidthRight := 0 + // 倒序 + sort.Sort(sort.Reverse(sort.IntSlice(axisIndexList))) + // 计算对应的axis range for _, index := range axisIndexList { + yAxisOption := YAxisOption{} + if len(opt.YAxisOptions) > index { + yAxisOption = opt.YAxisOptions[index] + } max, min := opt.SeriesList.GetMaxMin(index) + if yAxisOption.Min != nil { + min = *yAxisOption.Min + } + if yAxisOption.Max != nil { + max = *yAxisOption.Max + } r := NewRange(AxisRangeOption{ Min: min, Max: max, @@ -159,10 +175,7 @@ func defaultRender(p *Painter, opt defaultRenderOption) (*defaultRenderResult, e DivideCount: defaultAxisDivideCount, }) result.axisRanges[index] = r - yAxisOption := YAxisOption{} - if len(opt.YAxisOptions) > index { - yAxisOption = opt.YAxisOptions[index] - } + if yAxisOption.Theme == nil { yAxisOption.Theme = opt.Theme } @@ -175,7 +188,16 @@ func defaultRender(p *Painter, opt defaultRenderOption) (*defaultRenderResult, e } reverseStringSlice(yAxisOption.Data) // TODO生成其它位置既yAxis - yAxis := NewLeftYAxis(p, yAxisOption) + var yAxis *axisPainter + child := p.Child(PainterPaddingOption(Box{ + Left: rangeWidthLeft, + Right: rangeWidthRight, + })) + if index == 0 { + yAxis = NewLeftYAxis(child, yAxisOption) + } else { + yAxis = NewRightYAxis(child, yAxisOption) + } yAxisBox, err := yAxis.Render() if err != nil { return nil, err @@ -191,7 +213,8 @@ func defaultRender(p *Painter, opt defaultRenderOption) (*defaultRenderResult, e opt.XAxis.Theme = opt.Theme } xAxis := NewBottomXAxis(p.Child(PainterPaddingOption(Box{ - Left: rangeWidthLeft, + Left: rangeWidthLeft, + Right: rangeWidthRight, })), opt.XAxis) _, err := xAxis.Render() if err != nil { @@ -219,7 +242,9 @@ func doRender(renderers ...Renderer) error { func Render(opt ChartOption) (*Painter, error) { opt.fillDefault() + isChild := true if opt.Parent == nil { + isChild = false p, err := NewPainter(PainterOptions{ Type: opt.Type, Width: opt.Width, @@ -231,21 +256,40 @@ func Render(opt ChartOption) (*Painter, error) { opt.Parent = p } p := opt.Parent - p.SetBackground(p.Width(), p.Height(), opt.BackgroundColor) + if !opt.Box.IsZero() { + p = p.Child(PainterBoxOption(opt.Box)) + } + if !isChild { + p.SetBackground(p.Width(), p.Height(), opt.BackgroundColor) + } seriesList := opt.SeriesList seriesList.init() + seriesCount := len(seriesList) + // line chart lineSeriesList := seriesList.Filter(ChartTypeLine) barSeriesList := seriesList.Filter(ChartTypeBar) horizontalBarSeriesList := seriesList.Filter(ChartTypeHorizontalBar) - if len(horizontalBarSeriesList) != 0 && len(horizontalBarSeriesList) != len(seriesList) { + pieSeriesList := seriesList.Filter(ChartTypePie) + radarSeriesList := seriesList.Filter(ChartTypeRadar) + funnelSeriesList := seriesList.Filter(ChartTypeFunnel) + + if len(horizontalBarSeriesList) != 0 && len(horizontalBarSeriesList) != seriesCount { return nil, errors.New("Horizontal bar can not mix other charts") } + if len(pieSeriesList) != 0 && len(pieSeriesList) != seriesCount { + return nil, errors.New("Pie can not mix other charts") + } + if len(radarSeriesList) != 0 && len(radarSeriesList) != seriesCount { + return nil, errors.New("Radar can not mix other charts") + } + if len(funnelSeriesList) != 0 && len(funnelSeriesList) != seriesCount { + return nil, errors.New("Funnel can not mix other charts") + } axisReversed := len(horizontalBarSeriesList) != 0 - - renderResult, err := defaultRender(p, defaultRenderOption{ + renderOpt := defaultRenderOption{ Theme: opt.theme, Padding: opt.Padding, SeriesList: opt.SeriesList, @@ -254,24 +298,28 @@ func Render(opt ChartOption) (*Painter, error) { TitleOption: opt.Title, LegendOption: opt.Legend, axisReversed: axisReversed, - }) + } + if isChild { + renderOpt.backgroundIsFilled = true + } + if len(pieSeriesList) != 0 || + len(radarSeriesList) != 0 || + len(funnelSeriesList) != 0 { + renderOpt.XAxis.Show = FalseFlag() + renderOpt.YAxisOptions = []YAxisOption{ + { + Show: FalseFlag(), + }, + } + } + + renderResult, err := defaultRender(p, renderOpt) if err != nil { return nil, err } handler := renderHandler{} - if len(lineSeriesList) != 0 { - handler.Add(func() error { - _, err := NewLineChart(p, LineChartOption{ - Theme: opt.theme, - Font: opt.font, - XAxis: opt.XAxis, - }).render(renderResult, lineSeriesList) - return err - }) - } - // bar chart if len(barSeriesList) != 0 { handler.Add(func() error { @@ -296,11 +344,65 @@ func Render(opt ChartOption) (*Painter, error) { }) } + // pie chart + if len(pieSeriesList) != 0 { + handler.Add(func() error { + _, err := NewPieChart(p, PieChartOption{ + Theme: opt.theme, + Font: opt.font, + }).render(renderResult, pieSeriesList) + return err + }) + } + + // line chart + if len(lineSeriesList) != 0 { + handler.Add(func() error { + _, err := NewLineChart(p, LineChartOption{ + Theme: opt.theme, + Font: opt.font, + XAxis: opt.XAxis, + }).render(renderResult, lineSeriesList) + return err + }) + } + + // radar chart + if len(radarSeriesList) != 0 { + handler.Add(func() error { + _, err := NewRadarChart(p, RadarChartOption{ + Theme: opt.theme, + Font: opt.font, + // 相应值 + RadarIndicators: opt.RadarIndicators, + }).render(renderResult, radarSeriesList) + return err + }) + } + + // funnel chart + if len(funnelSeriesList) != 0 { + handler.Add(func() error { + _, err := NewFunnelChart(p, FunnelChartOption{ + Theme: opt.theme, + Font: opt.font, + }).render(renderResult, funnelSeriesList) + return err + }) + } + err = handler.Do() if err != nil { return nil, err } + for _, item := range opt.Children { + item.Parent = p + _, err = Render(item) + if err != nil { + return nil, err + } + } return p, nil } diff --git a/echarts.go b/echarts.go new file mode 100644 index 0000000..ac28436 --- /dev/null +++ b/echarts.go @@ -0,0 +1,505 @@ +// 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, + AxisIndex: 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 + } + titleTextStyle := eo.Title.TextStyle.ToStyle() + titleSubtextStyle := eo.Title.SubtextStyle.ToStyle() + legendTextStyle := eo.Legend.TextStyle.ToStyle() + o := ChartOption{ + Type: eo.Type, + FontFamily: fontFamily, + Theme: eo.Theme, + Title: TitleOption{ + Text: eo.Title.Text, + Subtext: eo.Title.Subtext, + FontColor: titleTextStyle.FontColor, + FontSize: titleTextStyle.FontSize, + SubtextFontSize: titleSubtextStyle.FontSize, + SubtextFontColor: titleSubtextStyle.FontColor, + Left: string(eo.Title.Left), + Top: string(eo.Title.Top), + }, + Legend: LegendOption{ + Show: eo.Legend.Show, + FontSize: legendTextStyle.FontSize, + FontColor: legendTextStyle.FontColor, + 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.YAxisOptions = 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/examples/charts/main.go b/examples/charts/main.go index c7986a4..3a625f7 100644 --- a/examples/charts/main.go +++ b/examples/charts/main.go @@ -83,13 +83,13 @@ func handler(w http.ResponseWriter, req *http.Request, chartOptions []charts.Cha } bytesList = append(bytesList, buf) } - // for _, opt := range echartsOptions { - // buf, err := charts.RenderEChartsToSVG(opt) - // 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") @@ -333,6 +333,7 @@ func indexHandler(w http.ResponseWriter, req *http.Request) { }, }, }, + // 柱状图+标记 { Title: charts.TitleOption{ Text: "Rainfall vs Evaporation", @@ -413,6 +414,342 @@ func indexHandler(w http.ResponseWriter, req *http.Request) { }, }, }, + // 双Y轴示例 + { + Title: charts.TitleOption{ + Text: "Temperature", + }, + XAxis: charts.NewXAxisOption([]string{ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + }), + Legend: charts.NewLegendOption([]string{ + "Evaporation", + "Precipitation", + "Temperature", + }), + YAxisOptions: []charts.YAxisOption{ + { + Formatter: "{value}ml", + Color: charts.Color{ + R: 84, + G: 112, + B: 198, + A: 255, + }, + }, + { + Formatter: "{value}°C", + Color: charts.Color{ + R: 250, + G: 200, + B: 88, + 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, + }), + }, + { + 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, + }), + }, + { + 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, + }), + AxisIndex: 1, + }, + }, + }, + // 饼图 + { + 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: "Show", + Data: charts.NewSeriesDataFromValues([]float64{ + 100, + }), + }, + { + Type: charts.ChartTypeFunnel, + Name: "Click", + Data: charts.NewSeriesDataFromValues([]float64{ + 80, + }), + }, + { + 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, + }), + }, + }, + }, + // 多图展示 + { + Legend: charts.LegendOption{ + Top: "-90", + Data: []string{ + "Milk Tea", + "Matcha Latte", + "Cheese Cocoa", + "Walnut Brownie", + }, + }, + Padding: charts.Box{ + Top: 100, + Right: 10, + Bottom: 10, + Left: 10, + }, + XAxis: charts.NewXAxisOption([]string{ + "2012", + "2013", + "2014", + "2015", + "2016", + "2017", + }), + YAxisOptions: []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: charts.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) } @@ -879,12 +1216,7 @@ func echartsHandler(w http.ResponseWriter, req *http.Request) { 23.2, 25.6, 76.7, - 135.6, - 162.2, - 32.6, - 20, - 6.4, - 3.3 + 135.6 ] }, { @@ -898,12 +1230,7 @@ func echartsHandler(w http.ResponseWriter, req *http.Request) { 26.4, 28.7, 70.7, - 175.6, - 182.2, - 48.7, - 18.8, - 6, - 2.3 + 175.6 ] }, { @@ -918,12 +1245,7 @@ func echartsHandler(w http.ResponseWriter, req *http.Request) { 4.5, 6.3, 10.2, - 20.3, - 23.4, - 23, - 16.5, - 12, - 6.2 + 20.3 ] } ] diff --git a/examples/funnel_chart/main.go b/examples/funnel_chart/main.go new file mode 100644 index 0000000..eb753fd --- /dev/null +++ b/examples/funnel_chart/main.go @@ -0,0 +1,97 @@ +package main + +import ( + "io/ioutil" + "os" + "path/filepath" + + "github.com/vicanso/go-charts" +) + +func writeFile(buf []byte) error { + tmpPath := "./tmp" + err := os.MkdirAll(tmpPath, 0700) + if err != nil { + return err + } + + file := filepath.Join(tmpPath, "funnel-chart.png") + err = ioutil.WriteFile(file, buf, 0600) + if err != nil { + return err + } + return nil +} + +func main() { + p, err := charts.NewPainter(charts.PainterOptions{ + Width: 800, + Height: 600, + Type: charts.ChartOutputPNG, + }) + if err != nil { + panic(err) + } + _, err = charts.NewFunnelChart(p, charts.FunnelChartOption{ + Title: charts.TitleOption{ + Text: "Funnel", + }, + Legend: charts.NewLegendOption([]string{ + "Show", + "Click", + "Visit", + "Inquiry", + "Order", + }), + SeriesList: []charts.Series{ + + { + Type: charts.ChartTypeFunnel, + Name: "Show", + Data: charts.NewSeriesDataFromValues([]float64{ + 100, + }), + }, + { + Type: charts.ChartTypeFunnel, + Name: "Click", + Data: charts.NewSeriesDataFromValues([]float64{ + 80, + }), + }, + { + 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, + }), + }, + }, + }).Render() + if err != nil { + panic(err) + } + + buf, err := p.Bytes() + if err != nil { + panic(err) + } + err = writeFile(buf) + if err != nil { + panic(err) + } +} diff --git a/examples/pie_chart/main.go b/examples/pie_chart/main.go new file mode 100644 index 0000000..e69bf60 --- /dev/null +++ b/examples/pie_chart/main.go @@ -0,0 +1,83 @@ +package main + +import ( + "io/ioutil" + "os" + "path/filepath" + + "github.com/vicanso/go-charts" +) + +func writeFile(buf []byte) error { + tmpPath := "./tmp" + err := os.MkdirAll(tmpPath, 0700) + if err != nil { + return err + } + + file := filepath.Join(tmpPath, "pie-chart.png") + err = ioutil.WriteFile(file, buf, 0600) + if err != nil { + return err + } + return nil +} + +func main() { + p, err := charts.NewPainter(charts.PainterOptions{ + Width: 800, + Height: 600, + Type: charts.ChartOutputPNG, + }) + if err != nil { + panic(err) + } + _, err = charts.NewPieChart(p, charts.PieChartOption{ + Title: charts.TitleOption{ + Text: "Rainfall vs Evaporation", + Subtext: "Fake Data", + Left: charts.PositionCenter, + }, + Padding: charts.Box{ + Top: 20, + Right: 20, + Bottom: 20, + Left: 20, + }, + 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%", + }), + }).Render() + if err != nil { + panic(err) + } + + buf, err := p.Bytes() + if err != nil { + panic(err) + } + err = writeFile(buf) + if err != nil { + panic(err) + } +} diff --git a/examples/radar_chart/main.go b/examples/radar_chart/main.go new file mode 100644 index 0000000..077fa48 --- /dev/null +++ b/examples/radar_chart/main.go @@ -0,0 +1,112 @@ +package main + +import ( + "io/ioutil" + "os" + "path/filepath" + + "github.com/vicanso/go-charts" +) + +func writeFile(buf []byte) error { + tmpPath := "./tmp" + err := os.MkdirAll(tmpPath, 0700) + if err != nil { + return err + } + + file := filepath.Join(tmpPath, "radar-chart.png") + err = ioutil.WriteFile(file, buf, 0600) + if err != nil { + return err + } + return nil +} + +func main() { + p, err := charts.NewPainter(charts.PainterOptions{ + Width: 800, + Height: 600, + Type: charts.ChartOutputPNG, + }) + if err != nil { + panic(err) + } + _, err = charts.NewRadarChart(p, charts.RadarChartOption{ + Padding: charts.Box{ + Left: 10, + Top: 10, + Right: 10, + Bottom: 10, + }, + 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, + }), + }, + }, + }).Render() + if err != nil { + panic(err) + } + + buf, err := p.Bytes() + if err != nil { + panic(err) + } + err = writeFile(buf) + if err != nil { + panic(err) + } +} diff --git a/funnel_chart.go b/funnel_chart.go new file mode 100644 index 0000000..c8457dd --- /dev/null +++ b/funnel_chart.go @@ -0,0 +1,172 @@ +// 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" + + "github.com/dustin/go-humanize" + "github.com/golang/freetype/truetype" +) + +type funnelChart struct { + p *Painter + opt *FunnelChartOption +} + +func NewFunnelChart(p *Painter, opt FunnelChartOption) *funnelChart { + if opt.Theme == nil { + opt.Theme = NewTheme("") + } + return &funnelChart{ + p: p, + opt: &opt, + } +} + +type FunnelChartOption struct { + Theme ColorPalette + // The font size + Font *truetype.Font + // The data series list + SeriesList SeriesList + // The padding of line chart + Padding Box + // The option of title + Title TitleOption + // The legend option + Legend LegendOption +} + +func (f *funnelChart) render(result *defaultRenderResult, seriesList SeriesList) (Box, error) { + opt := f.opt + seriesPainter := result.seriesPainter + 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 := opt.Theme + gap := 2 + height := seriesPainter.Height() + width := seriesPainter.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) + + seriesPainter.OverrideDrawingStyle(Style{ + FillColor: color, + }).FillArea(points) + + // 文本 + text := textList[index] + seriesPainter.OverrideTextStyle(Style{ + FontColor: theme.GetTextColor(), + FontSize: labelFontSize, + Font: opt.Font, + }) + textBox := seriesPainter.MeasureText(text) + textX := width>>1 - textBox.Width()>>1 + textY := y + h>>1 + seriesPainter.Text(text, textX, textY) + y += (h + gap) + } + + return f.p.box, nil +} + +func (f *funnelChart) Render() (Box, error) { + p := f.p + opt := f.opt + renderResult, err := defaultRender(p, defaultRenderOption{ + Theme: opt.Theme, + Padding: opt.Padding, + SeriesList: opt.SeriesList, + XAxis: XAxisOption{ + Show: FalseFlag(), + }, + YAxisOptions: []YAxisOption{ + { + Show: FalseFlag(), + }, + }, + TitleOption: opt.Title, + LegendOption: opt.Legend, + }) + if err != nil { + return BoxZero, err + } + seriesList := opt.SeriesList.Filter(ChartTypeFunnel) + return f.render(renderResult, seriesList) +} diff --git a/horizontal_bar_chart.go b/horizontal_bar_chart.go index 87ca9ae..c98d688 100644 --- a/horizontal_bar_chart.go +++ b/horizontal_bar_chart.go @@ -92,13 +92,9 @@ func (h *horizontalBarChart) render(result *defaultRenderResult, seriesList Seri Size: seriesPainter.Width(), }) - for i := range seriesList { - series := seriesList[i] - index := series.index - if index == 0 { - index = i - } - seriesColor := theme.GetSeriesColor(index) + for index := range seriesList { + series := seriesList[index] + seriesColor := theme.GetSeriesColor(series.index) divideValues := yRange.AutoDivide() for j, item := range series.Data { if j >= yRange.divideCount { @@ -108,8 +104,8 @@ func (h *horizontalBarChart) render(result *defaultRenderResult, seriesList Seri j = yRange.divideCount - j - 1 y := divideValues[j] y += margin - if i != 0 { - y += i * (barHeight + barMargin) + if index != 0 { + y += index * (barHeight + barMargin) } w := int(xRange.getHeight(item.Value)) diff --git a/legend.go b/legend.go index 65793c9..cf8d417 100644 --- a/legend.go +++ b/legend.go @@ -56,6 +56,8 @@ type LegendOption struct { FontSize float64 // FontColor color of legend text FontColor Color + // The flag for show legend, set this to *false will hide legend + Show *bool } func NewLegendOption(labels []string, left ...string) LegendOption { @@ -68,6 +70,17 @@ func NewLegendOption(labels []string, left ...string) LegendOption { return opt } +func (opt *LegendOption) IsEmpty() bool { + isEmpty := true + for _, v := range opt.Data { + if v != "" { + isEmpty = false + break + } + } + return isEmpty +} + func NewLegendPainter(p *Painter, opt LegendOption) *legendPainter { return &legendPainter{ p: p, @@ -78,6 +91,10 @@ func NewLegendPainter(p *Painter, opt LegendOption) *legendPainter { func (l *legendPainter) Render() (Box, error) { opt := l.opt theme := opt.Theme + if opt.IsEmpty() || + (opt.Show != nil && !*opt.Show) { + return BoxZero, nil + } if theme == nil { theme = l.p.theme } @@ -90,7 +107,9 @@ func (l *legendPainter) Render() (Box, error) { if opt.Left == "" { opt.Left = PositionCenter } - p := l.p + p := l.p.Child(PainterPaddingOption(Box{ + Top: 5, + })) p.SetTextStyle(Style{ FontSize: opt.FontSize, FontColor: opt.FontColor, diff --git a/line_chart.go b/line_chart.go index 47a497f..c505a91 100644 --- a/line_chart.go +++ b/line_chart.go @@ -93,7 +93,7 @@ func (l *lineChart) render(result *defaultRenderResult, seriesList SeriesList) ( } for index := range seriesList { series := seriesList[index] - seriesColor := opt.Theme.GetSeriesColor(index) + seriesColor := opt.Theme.GetSeriesColor(series.index) drawingStyle := Style{ StrokeColor: seriesColor, StrokeWidth: defaultStrokeWidth, diff --git a/painter.go b/painter.go index fff6ca7..5a8dd89 100644 --- a/painter.go +++ b/painter.go @@ -628,6 +628,9 @@ func (p *Painter) MultiText(opt MultiTextOption) *Painter { values = autoDivide(width, count) } for index, text := range opt.TextList { + if index%opt.Unit != 0 { + continue + } box := p.MeasureText(text) start := values[index] if positionCenter { diff --git a/pie_chart.go b/pie_chart.go new file mode 100644 index 0000000..c5a2ff2 --- /dev/null +++ b/pie_chart.go @@ -0,0 +1,211 @@ +// 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" +) + +type pieChart struct { + p *Painter + opt *PieChartOption +} + +type PieChartOption struct { + Theme ColorPalette + // The font size + Font *truetype.Font + // The data series list + SeriesList SeriesList + // The padding of line chart + Padding Box + // The option of title + Title TitleOption + // The legend option + Legend LegendOption + // background is filled + backgroundIsFilled bool +} + +func NewPieChart(p *Painter, opt PieChartOption) *pieChart { + if opt.Theme == nil { + opt.Theme = NewTheme("") + } + return &pieChart{ + p: p, + opt: &opt, + } +} + +func (p *pieChart) render(result *defaultRenderResult, seriesList SeriesList) (Box, error) { + opt := p.opt + values := make([]float64, len(seriesList)) + total := float64(0) + radiusValue := "" + for index, series := range 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 BoxZero, errors.New("The sum value of pie chart should gt 0") + } + seriesPainter := result.seriesPainter + cx := seriesPainter.Width() >> 1 + cy := seriesPainter.Height() >> 1 + + diameter := chart.MinInt(seriesPainter.Width(), seriesPainter.Height()) + radius := getRadius(float64(diameter), radiusValue) + + labelLineWidth := 15 + if radius < 50 { + labelLineWidth = 10 + } + labelRadius := radius + float64(labelLineWidth) + seriesNames := opt.Legend.Data + if len(seriesNames) == 0 { + seriesNames = seriesList.Names() + } + theme := opt.Theme + if len(values) == 1 { + seriesPainter.OverrideDrawingStyle(Style{ + StrokeWidth: 1, + StrokeColor: theme.GetSeriesColor(0), + FillColor: theme.GetSeriesColor(0), + }) + seriesPainter.MoveTo(cx, cy). + Circle(radius, cx, cy) + } else { + currentValue := float64(0) + prevEndX := 0 + prevEndY := 0 + for index, v := range values { + seriesPainter.OverrideDrawingStyle(Style{ + StrokeWidth: 1, + StrokeColor: theme.GetSeriesColor(index), + FillColor: theme.GetSeriesColor(index), + }) + seriesPainter.MoveTo(cx, cy) + start := chart.PercentToRadians(currentValue/total) - math.Pi/2 + currentValue += v + percent := (v / total) + delta := chart.PercentToRadians(percent) + seriesPainter.ArcTo(cx, cy, radius, radius, start, delta). + LineTo(cx, cy). + Close(). + FillStroke() + + series := seriesList[index] + // 是否显示label + showLabel := series.Label.Show + if !showLabel { + continue + } + + // 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)) + // 计算是否有重叠,如果有则调整y坐标位置 + if index != 0 && + math.Abs(float64(endx-prevEndX)) < labelFontSize && + math.Abs(float64(endy-prevEndY)) < labelFontSize { + endy -= (labelFontSize << 1) + } + prevEndX = endx + prevEndY = endy + + seriesPainter.MoveTo(startx, starty) + seriesPainter.LineTo(endx, endy) + offset := labelLineWidth + if endx < cx { + offset *= -1 + } + seriesPainter.MoveTo(endx, endy) + endx += offset + seriesPainter.LineTo(endx, endy) + seriesPainter.Stroke() + + textStyle := Style{ + FontColor: theme.GetTextColor(), + FontSize: labelFontSize, + Font: opt.Font, + } + if !series.Label.Color.IsZero() { + textStyle.FontColor = series.Label.Color + } + seriesPainter.OverrideTextStyle(textStyle) + text := NewPieLabelFormatter(seriesNames, series.Label.Formatter)(index, v, percent) + textBox := seriesPainter.MeasureText(text) + textMargin := 3 + x := endx + textMargin + y := endy + textBox.Height()>>1 - 1 + if offset < 0 { + textWidth := textBox.Width() + x = endx - textWidth - textMargin + } + seriesPainter.Text(text, x, y) + } + } + + return p.p.box, nil +} + +func (p *pieChart) Render() (Box, error) { + opt := p.opt + + renderResult, err := defaultRender(p.p, defaultRenderOption{ + Theme: opt.Theme, + Padding: opt.Padding, + SeriesList: opt.SeriesList, + XAxis: XAxisOption{ + Show: FalseFlag(), + }, + YAxisOptions: []YAxisOption{ + { + Show: FalseFlag(), + }, + }, + TitleOption: opt.Title, + LegendOption: opt.Legend, + backgroundIsFilled: opt.backgroundIsFilled, + }) + if err != nil { + return BoxZero, err + } + seriesList := opt.SeriesList.Filter(ChartTypePie) + return p.render(renderResult, seriesList) +} diff --git a/radar_chart.go b/radar_chart.go new file mode 100644 index 0000000..dc93ca8 --- /dev/null +++ b/radar_chart.go @@ -0,0 +1,245 @@ +// 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 radarChart struct { + p *Painter + opt *RadarChartOption +} + +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 ColorPalette + // The font size + Font *truetype.Font + // The data series list + SeriesList SeriesList + // The padding of line chart + Padding Box + // The option of title + Title TitleOption + // The legend option + Legend LegendOption + // The radar indicator list + RadarIndicators []RadarIndicator + // background is filled + backgroundIsFilled bool +} + +func NewRadarChart(p *Painter, opt RadarChartOption) *radarChart { + if opt.Theme == nil { + opt.Theme = NewTheme("") + } + return &radarChart{ + p: p, + opt: &opt, + } +} + +func (r *radarChart) render(result *defaultRenderResult, seriesList SeriesList) (Box, error) { + opt := r.opt + indicators := opt.RadarIndicators + sides := len(indicators) + if sides < 3 { + return BoxZero, errors.New("The count of indicator should be >= 3") + } + maxValues := make([]float64, len(indicators)) + for _, series := range seriesList { + for index, item := range series.Data { + if index < len(maxValues) && item.Value > maxValues[index] { + maxValues[index] = item.Value + } + } + } + for index, indicator := range indicators { + if indicator.Max <= 0 { + indicators[index].Max = maxValues[index] + } + } + + radiusValue := "" + for _, series := range seriesList { + if len(series.Radius) != 0 { + radiusValue = series.Radius + } + } + + seriesPainter := result.seriesPainter + theme := opt.Theme + + cx := seriesPainter.Width() >> 1 + cy := seriesPainter.Height() >> 1 + diameter := chart.MinInt(seriesPainter.Width(), seriesPainter.Height()) + radius := getRadius(float64(diameter), radiusValue) + + divideCount := 5 + divideRadius := float64(int(radius / float64(divideCount))) + radius = divideRadius * float64(divideCount) + + seriesPainter.OverrideDrawingStyle(Style{ + StrokeColor: theme.GetAxisSplitLineColor(), + StrokeWidth: 1, + }) + center := Point{ + X: cx, + Y: cy, + } + for i := 0; i < divideCount; i++ { + seriesPainter.Polygon(center, divideRadius*float64(i+1), sides) + } + points := getPolygonPoints(center, radius, sides) + for _, p := range points { + seriesPainter.MoveTo(center.X, center.Y) + seriesPainter.LineTo(p.X, p.Y) + seriesPainter.Stroke() + } + seriesPainter.OverrideTextStyle(Style{ + FontColor: theme.GetTextColor(), + FontSize: labelFontSize, + Font: opt.Font, + }) + offset := 5 + // 文本生成 + for index, p := range points { + name := indicators[index].Name + b := seriesPainter.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) + } + seriesPainter.Text(name, x, y) + } + + // 雷达图 + angles := getPolygonPointAngles(sides) + maxCount := len(indicators) + for _, series := range seriesList { + linePoints := make([]Point, 0, maxCount) + for j, item := range series.Data { + if j >= maxCount { + continue + } + indicator := 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]) + seriesPainter.OverrideDrawingStyle(Style{ + StrokeColor: color, + StrokeWidth: defaultStrokeWidth, + DotWidth: defaultDotWidth, + DotColor: color, + FillColor: color.WithAlpha(20), + }) + seriesPainter.LineStroke(linePoints). + FillArea(linePoints) + dotWith := 2.0 + seriesPainter.OverrideDrawingStyle(Style{ + StrokeWidth: defaultStrokeWidth, + StrokeColor: color, + FillColor: dotFillColor, + }) + for _, point := range linePoints { + seriesPainter.Circle(dotWith, point.X, point.Y) + seriesPainter.FillStroke() + } + } + + return r.p.box, nil +} + +func (r *radarChart) Render() (Box, error) { + p := r.p + opt := r.opt + renderResult, err := defaultRender(p, defaultRenderOption{ + Theme: opt.Theme, + Padding: opt.Padding, + SeriesList: opt.SeriesList, + XAxis: XAxisOption{ + Show: FalseFlag(), + }, + YAxisOptions: []YAxisOption{ + { + Show: FalseFlag(), + }, + }, + TitleOption: opt.Title, + LegendOption: opt.Legend, + backgroundIsFilled: opt.backgroundIsFilled, + }) + if err != nil { + return BoxZero, err + } + seriesList := opt.SeriesList.Filter(ChartTypeRadar) + return r.render(renderResult, seriesList) +} diff --git a/xaxis.go b/xaxis.go index f06d71f..bfb57cb 100644 --- a/xaxis.go +++ b/xaxis.go @@ -38,8 +38,8 @@ type XAxisOption struct { Theme ColorPalette // The font size of x axis label FontSize float64 - // Hidden x axis - Hidden bool + // The flag for show axis, set this to *false will hide axis + Show *bool // Number of segments that the axis is split into. Note that this number serves only as a recommendation. SplitNumber int // The position of axis, it can be 'top' or 'bottom' @@ -78,6 +78,7 @@ func (opt *XAxisOption) ToAxisOption() AxisOption { FontSize: opt.FontSize, Font: opt.Font, FontColor: opt.FontColor, + Show: opt.Show, SplitLineColor: opt.Theme.GetAxisSplitLineColor(), } if opt.isValueAxis { diff --git a/yaxis.go b/yaxis.go index 609924f..265ac59 100644 --- a/yaxis.go +++ b/yaxis.go @@ -25,6 +25,10 @@ package charts import "github.com/golang/freetype/truetype" type YAxisOption struct { + // The minimun value of axis. + Min *float64 + // The maximum value of axis. + Max *float64 // The font of y axis Font *truetype.Font // The data value of x axis @@ -36,7 +40,13 @@ type YAxisOption struct { // The position of axis, it can be 'left' or 'right' Position string // The color of label - FontColor Color + FontColor Color + // Formatter for y axis text value + Formatter string + // Color for y axis + Color Color + // The flag for show axis, set this to *false will hide axis + Show *bool isCategoryAxis bool } @@ -60,6 +70,7 @@ func (opt *YAxisOption) ToAxisOption() AxisOption { position = PositionRight } axisOpt := AxisOption{ + Formatter: opt.Formatter, Theme: opt.Theme, Data: opt.Data, Position: position, @@ -70,6 +81,11 @@ func (opt *YAxisOption) ToAxisOption() AxisOption { BoundaryGap: FalseFlag(), SplitLineShow: true, SplitLineColor: opt.Theme.GetAxisSplitLineColor(), + Show: opt.Show, + } + if !opt.Color.IsZero() { + axisOpt.FontColor = opt.Color + axisOpt.StrokeColor = opt.Color } if opt.isCategoryAxis { axisOpt.BoundaryGap = TrueFlag() @@ -85,3 +101,13 @@ func NewLeftYAxis(p *Painter, opt YAxisOption) *axisPainter { })) return NewAxisPainter(p, opt.ToAxisOption()) } + +func NewRightYAxis(p *Painter, opt YAxisOption) *axisPainter { + p = p.Child(PainterPaddingOption(Box{ + Bottom: defaultXAxisHeight, + })) + axisOpt := opt.ToAxisOption() + axisOpt.Position = PositionRight + axisOpt.SplitLineShow = false + return NewAxisPainter(p, axisOpt) +} From 38c4978e44d8f92906d192041637d7ecad7c89d3 Mon Sep 17 00:00:00 2001 From: vicanso Date: Fri, 17 Jun 2022 23:37:21 +0800 Subject: [PATCH 19/21] refactor: enhance chart render function --- README.md | 32 ++-- bar_chart.go | 2 +- chart_option.go | 238 +++++++++++++++++++++++++- charts.go | 11 +- echarts.go | 18 ++ examples/bar_chart/main.go | 116 +++++-------- examples/charts/main.go | 59 +++++++ examples/funnel_chart/main.go | 62 ++----- examples/horizontal_bar_chart/main.go | 65 +++---- examples/line_chart/main.go | 135 +++++++-------- examples/pie_chart/main.go | 41 ++--- examples/radar_chart/main.go | 106 ++++-------- funnel_chart.go | 2 +- horizontal_bar_chart.go | 2 +- line_chart.go | 2 +- painter.go | 6 +- pie_chart.go | 2 +- radar_chart.go | 2 +- series.go | 8 + theme.go | 218 ++++++++++++----------- 20 files changed, 665 insertions(+), 462 deletions(-) diff --git a/README.md b/README.md index 22d3205..7affa30 100644 --- a/README.md +++ b/README.md @@ -49,25 +49,21 @@ func writeFile(file string, buf []byte) error { } func chartsRender() ([]byte, error) { - d, err := charts.LineRender([][]float64{ + values := [][]float64{ { - 150, + 120, + 132, + 101, + 134, + 90, 230, - 224, - 218, - 135, - 147, - 260, + 210, }, - }, - // output type - charts.PNGTypeOption(), - // title - charts.TitleOptionFunc(charts.TitleOption{ - Text: "Line", - }), - // x axis - charts.XAxisOptionFunc(charts.NewXAxisOption([]string{ + } + p, err := charts.LineRender( + values, + charts.TitleTextOptionFunc("Line"), + charts.XAxisDataOptionFunc([]string{ "Mon", "Tue", "Wed", @@ -75,12 +71,12 @@ func chartsRender() ([]byte, error) { "Fri", "Sat", "Sun", - })), + }), ) if err != nil { return nil, err } - return d.Bytes() + return p.Bytes() } func echartsRender() ([]byte, error) { diff --git a/bar_chart.go b/bar_chart.go index 8330542..2982829 100644 --- a/bar_chart.go +++ b/bar_chart.go @@ -34,7 +34,7 @@ type barChart struct { func NewBarChart(p *Painter, opt BarChartOption) *barChart { if opt.Theme == nil { - opt.Theme = NewTheme("") + opt.Theme = defaultTheme } return &barChart{ p: p, diff --git a/chart_option.go b/chart_option.go index 0cea754..0bc0a34 100644 --- a/chart_option.go +++ b/chart_option.go @@ -66,6 +66,182 @@ type ChartOption struct { Children []ChartOption } +// 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 + } +} + +// TitleTextOptionFunc set title text of chart +func TitleTextOptionFunc(text string) OptionFunc { + return func(opt *ChartOption) { + opt.Title.Text = text + } +} + +// LegendOptionFunc set legend of chart +func LegendOptionFunc(legend LegendOption) OptionFunc { + return func(opt *ChartOption) { + opt.Legend = legend + } +} + +// LegendLabelsOptionFunc set legend labels of chart +func LegendLabelsOptionFunc(labels []string, left ...string) OptionFunc { + return func(opt *ChartOption) { + opt.Legend = NewLegendOption(labels, left...) + } +} + +// XAxisOptionFunc set x axis of chart +func XAxisOptionFunc(xAxisOption XAxisOption) OptionFunc { + return func(opt *ChartOption) { + opt.XAxis = xAxisOption + } +} + +// XAxisDataOptionFunc set x axis data of chart +func XAxisDataOptionFunc(data []string, boundaryGap ...*bool) OptionFunc { + return func(opt *ChartOption) { + opt.XAxis = NewXAxisOption(data, boundaryGap...) + } +} + +// YAxisOptionFunc set y axis of chart, support two y axis +func YAxisOptionFunc(yAxisOption ...YAxisOption) OptionFunc { + return func(opt *ChartOption) { + opt.YAxisOptions = yAxisOption + } +} + +// YAxisDataOptionFunc set y axis data of chart +func YAxisDataOptionFunc(data []string) OptionFunc { + return func(opt *ChartOption) { + opt.YAxisOptions = NewYAxisOptions(data) + } +} + +// 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 Box) OptionFunc { + return func(opt *ChartOption) { + opt.Padding = padding + } +} + +// BoxOptionFunc set box of chart +func BoxOptionFunc(box Box) OptionFunc { + return func(opt *ChartOption) { + opt.Box = box + } +} + +// PieSeriesShowLabel set pie series show label +func PieSeriesShowLabel() OptionFunc { + return func(opt *ChartOption) { + for index := range opt.SeriesList { + opt.SeriesList[index].Label.Show = true + } + } +} + +// 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(names []string, values []float64) OptionFunc { + return func(opt *ChartOption) { + if len(names) != len(values) { + return + } + indicators := make([]RadarIndicator, len(names)) + for index, name := range names { + indicators[index] = RadarIndicator{ + Name: name, + Max: values[index], + } + } + opt.RadarIndicators = indicators + } +} + +// BackgroundColorOptionFunc set background color of chart +func BackgroundColorOptionFunc(color Color) OptionFunc { + return func(opt *ChartOption) { + opt.BackgroundColor = color + } +} + +// MarkLineOptionFunc set mark line for series of chart +func MarkLineOptionFunc(seriesIndex int, markLineTypes ...string) OptionFunc { + return func(opt *ChartOption) { + if len(opt.SeriesList) <= seriesIndex { + return + } + opt.SeriesList[seriesIndex].MarkLine = NewMarkLine(markLineTypes...) + } +} + +// MarkPointOptionFunc set mark point for series of chart +func MarkPointOptionFunc(seriesIndex int, markPointTypes ...string) OptionFunc { + return func(opt *ChartOption) { + if len(opt.SeriesList) <= seriesIndex { + return + } + opt.SeriesList[seriesIndex].MarkPoint = NewMarkPoint(markPointTypes...) + } +} + func (o *ChartOption) fillDefault() { t := NewTheme(o.Theme) o.theme = t @@ -90,11 +266,11 @@ func (o *ChartOption) fillDefault() { o.BackgroundColor = t.GetBackgroundColor() } if o.Padding.IsZero() { - o.Padding = chart.Box{ - Top: 10, - Right: 10, - Bottom: 10, - Left: 10, + o.Padding = Box{ + Top: 20, + Right: 20, + Bottom: 20, + Left: 20, } } // legend与series name的关联 @@ -118,3 +294,55 @@ func (o *ChartOption) fillDefault() { }) } } + +// LineRender line chart render +func LineRender(values [][]float64, opts ...OptionFunc) (*Painter, error) { + seriesList := NewSeriesListDataFromValues(values, ChartTypeLine) + return Render(ChartOption{ + SeriesList: seriesList, + }, opts...) +} + +// BarRender bar chart render +func BarRender(values [][]float64, opts ...OptionFunc) (*Painter, error) { + seriesList := NewSeriesListDataFromValues(values, ChartTypeBar) + return Render(ChartOption{ + SeriesList: seriesList, + }, opts...) +} + +// HorizontalBarRender horizontal bar chart render +func HorizontalBarRender(values [][]float64, opts ...OptionFunc) (*Painter, error) { + seriesList := NewSeriesListDataFromValues(values, ChartTypeHorizontalBar) + return Render(ChartOption{ + SeriesList: seriesList, + }, opts...) +} + +// PieRender pie chart render +func PieRender(values []float64, opts ...OptionFunc) (*Painter, error) { + return Render(ChartOption{ + SeriesList: NewPieSeriesList(values), + }, opts...) +} + +// RadarRender radar chart render +func RadarRender(values [][]float64, opts ...OptionFunc) (*Painter, error) { + seriesList := NewSeriesListDataFromValues(values, ChartTypeRadar) + return Render(ChartOption{ + SeriesList: seriesList, + }, opts...) +} + +// FunnelRender funnel chart render +func FunnelRender(values []float64, opts ...OptionFunc) (*Painter, 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/charts.go b/charts.go index 51e247a..5759367 100644 --- a/charts.go +++ b/charts.go @@ -239,7 +239,10 @@ func doRender(renderers ...Renderer) error { return nil } -func Render(opt ChartOption) (*Painter, error) { +func Render(opt ChartOption, opts ...OptionFunc) (*Painter, error) { + for _, fn := range opts { + fn(&opt) + } opt.fillDefault() isChild := true @@ -398,6 +401,12 @@ func Render(opt ChartOption) (*Painter, error) { } for _, item := range opt.Children { item.Parent = p + if item.Theme == "" { + item.Theme = opt.Theme + } + if item.FontFamily == "" { + item.FontFamily = opt.FontFamily + } _, err = Render(item) if err != nil { return nil, err diff --git a/echarts.go b/echarts.go index ac28436..d2602b3 100644 --- a/echarts.go +++ b/echarts.go @@ -130,6 +130,7 @@ type EChartsXAxisData struct { BoundaryGap *bool `json:"boundaryGap"` SplitNumber int `json:"splitNumber"` Data []string `json:"data"` + Type string `json:"type"` } type EChartsXAxis struct { Data []EChartsXAxisData @@ -155,6 +156,7 @@ type EChartsYAxisData struct { Color string `json:"color"` } `json:"lineStyle"` } `json:"axisLine"` + Data []string `json:"data"` } type EChartsYAxis struct { Data []EChartsYAxisData `json:"data"` @@ -453,6 +455,21 @@ func (eo *EChartsOption) ToOption() ChartOption { Box: eo.Box, SeriesList: eo.Series.ToSeriesList(), } + isHorizontalChart := false + for _, item := range eo.XAxis.Data { + if item.Type == "value" { + isHorizontalChart = true + } + } + if isHorizontalChart { + for index := range o.SeriesList { + series := o.SeriesList[index] + if series.Type == ChartTypeBar { + o.SeriesList[index].Type = ChartTypeHorizontalBar + } + } + } + if len(eo.XAxis.Data) != 0 { xAxisData := eo.XAxis.Data[0] o.XAxis = XAxisOption{ @@ -468,6 +485,7 @@ func (eo *EChartsOption) ToOption() ChartOption { Max: item.Max, Formatter: item.AxisLabel.Formatter, Color: parseColor(item.AxisLine.LineStyle.Color), + Data: item.Data, } } o.YAxisOptions = yAxisOptions diff --git a/examples/bar_chart/main.go b/examples/bar_chart/main.go index 5d5da2a..c9f1d58 100644 --- a/examples/bar_chart/main.go +++ b/examples/bar_chart/main.go @@ -24,26 +24,39 @@ func writeFile(buf []byte) error { } func main() { - p, err := charts.NewPainter(charts.PainterOptions{ - Width: 800, - Height: 600, - Type: charts.ChartOutputPNG, - }) - if err != nil { - panic(err) + values := [][]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, + }, + { + 2.6, + 5.9, + 9.0, + 26.4, + 28.7, + 70.7, + 175.6, + 182.2, + 48.7, + 18.8, + 6.0, + 2.3, + }, } - _, err = charts.NewBarChart(p, charts.BarChartOption{ - Title: charts.TitleOption{ - Text: "Rainfall vs Evaporation", - Subtext: "Fake Data", - }, - Padding: charts.Box{ - Top: 20, - Right: 20, - Bottom: 20, - Left: 20, - }, - XAxis: charts.NewXAxisOption([]string{ + p, err := charts.BarRender( + values, + charts.XAxisDataOptionFunc([]string{ "Jan", "Feb", "Mar", @@ -57,61 +70,24 @@ func main() { "Nov", "Dec", }), - Legend: charts.NewLegendOption([]string{ + charts.LegendLabelsOptionFunc([]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, - ), - }, + charts.MarkLineOptionFunc(0, charts.SeriesMarkDataTypeAverage), + charts.MarkPointOptionFunc(0, charts.SeriesMarkDataTypeMax, + charts.SeriesMarkDataTypeMin), + // custom option func + func(opt *charts.ChartOption) { + opt.SeriesList[1].MarkPoint = charts.NewMarkPoint( + charts.SeriesMarkDataTypeMax, + charts.SeriesMarkDataTypeMin, + ) + opt.SeriesList[1].MarkLine = charts.NewMarkLine( + charts.SeriesMarkDataTypeAverage, + ) }, - }).Render() + ) if err != nil { panic(err) } diff --git a/examples/charts/main.go b/examples/charts/main.go index 3a625f7..b370b69 100644 --- a/examples/charts/main.go +++ b/examples/charts/main.go @@ -73,6 +73,7 @@ func handler(w http.ResponseWriter, req *http.Request, chartOptions []charts.Cha bytesList := make([][]byte, 0) for _, opt := range chartOptions { opt.Theme = theme + opt.Type = charts.ChartOutputSVG d, err := charts.Render(opt) if err != nil { panic(err) @@ -1055,6 +1056,64 @@ func echartsHandler(w http.ResponseWriter, req *http.Request) { } ] }`, + `{ + "title": { + "text": "World Population" + }, + "tooltip": { + "trigger": "axis", + "axisPointer": { + "type": "shadow" + } + }, + "legend": {}, + "grid": { + "left": "3%", + "right": "4%", + "bottom": "3%", + "containLabel": true + }, + "xAxis": { + "type": "value" + }, + "yAxis": { + "type": "category", + "data": [ + "Brazil", + "Indonesia", + "USA", + "India", + "China", + "World" + ] + }, + "series": [ + { + "name": "2011", + "type": "bar", + "data": [ + 18203, + 23489, + 29034, + 104970, + 131744, + 630230 + ] + }, + { + "name": "2012", + "type": "bar", + "data": [ + 19325, + 23438, + 31000, + 121594, + 134141, + 681807 + ] + } + ] + }`, `{ "title": { "text": "Rainfall vs Evaporation", diff --git a/examples/funnel_chart/main.go b/examples/funnel_chart/main.go index eb753fd..6b17614 100644 --- a/examples/funnel_chart/main.go +++ b/examples/funnel_chart/main.go @@ -24,64 +24,24 @@ func writeFile(buf []byte) error { } func main() { - p, err := charts.NewPainter(charts.PainterOptions{ - Width: 800, - Height: 600, - Type: charts.ChartOutputPNG, - }) - if err != nil { - panic(err) + values := []float64{ + 100, + 80, + 60, + 40, + 20, } - _, err = charts.NewFunnelChart(p, charts.FunnelChartOption{ - Title: charts.TitleOption{ - Text: "Funnel", - }, - Legend: charts.NewLegendOption([]string{ + p, err := charts.FunnelRender( + values, + charts.TitleTextOptionFunc("Funnel"), + charts.LegendLabelsOptionFunc([]string{ "Show", "Click", "Visit", "Inquiry", "Order", }), - SeriesList: []charts.Series{ - - { - Type: charts.ChartTypeFunnel, - Name: "Show", - Data: charts.NewSeriesDataFromValues([]float64{ - 100, - }), - }, - { - Type: charts.ChartTypeFunnel, - Name: "Click", - Data: charts.NewSeriesDataFromValues([]float64{ - 80, - }), - }, - { - 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, - }), - }, - }, - }).Render() + ) if err != nil { panic(err) } diff --git a/examples/horizontal_bar_chart/main.go b/examples/horizontal_bar_chart/main.go index eecd9ec..6b206b0 100644 --- a/examples/horizontal_bar_chart/main.go +++ b/examples/horizontal_bar_chart/main.go @@ -24,29 +24,38 @@ func writeFile(buf []byte) error { } func main() { - p, err := charts.NewPainter(charts.PainterOptions{ - Width: 800, - Height: 600, - Type: charts.ChartOutputPNG, - }) - if err != nil { - panic(err) - } - _, err = charts.NewHorizontalBarChart(p, charts.HorizontalBarChartOption{ - Title: charts.TitleOption{ - Text: "World Population", + values := [][]float64{ + { + 18203, + 23489, + 29034, + 104970, + 131744, + 630230, }, - Padding: charts.Box{ + { + 19325, + 23438, + 31000, + 121594, + 134141, + 681807, + }, + } + p, err := charts.HorizontalBarRender( + values, + charts.TitleTextOptionFunc("World Population"), + charts.PaddingOptionFunc(charts.Box{ Top: 20, Right: 40, Bottom: 20, Left: 20, - }, - Legend: charts.NewLegendOption([]string{ + }), + charts.LegendLabelsOptionFunc([]string{ "2011", "2012", }), - YAxisOptions: charts.NewYAxisOptions([]string{ + charts.YAxisDataOptionFunc([]string{ "Brazil", "Indonesia", "USA", @@ -54,31 +63,7 @@ func main() { "China", "World", }), - SeriesList: []charts.Series{ - { - Type: charts.ChartTypeHorizontalBar, - Data: charts.NewSeriesDataFromValues([]float64{ - 18203, - 23489, - 29034, - 104970, - 131744, - 630230, - }), - }, - { - Type: charts.ChartTypeHorizontalBar, - Data: charts.NewSeriesDataFromValues([]float64{ - 19325, - 23438, - 31000, - 121594, - 134141, - 681807, - }), - }, - }, - }).Render() + ) if err != nil { panic(err) } diff --git a/examples/line_chart/main.go b/examples/line_chart/main.go index 414f676..435da78 100644 --- a/examples/line_chart/main.go +++ b/examples/line_chart/main.go @@ -24,35 +24,57 @@ func writeFile(buf []byte) error { } func main() { - p, err := charts.NewPainter(charts.PainterOptions{ - Width: 800, - Height: 600, - Type: charts.ChartOutputPNG, - }) - if err != nil { - panic(err) + values := [][]float64{ + { + 120, + 132, + 101, + 134, + 90, + 230, + 210, + }, + { + 220, + 182, + 191, + 234, + 290, + 330, + 310, + }, + { + 150, + 232, + 201, + 154, + 190, + 330, + 410, + }, + { + 320, + 332, + 301, + 334, + 390, + 330, + 320, + }, + { + 820, + 932, + 901, + 934, + 1290, + 1330, + 1320, + }, } - _, err = charts.NewLineChart(p, charts.LineChartOption{ - Padding: charts.Box{ - Left: 10, - Top: 10, - Right: 10, - Bottom: 10, - }, - Title: charts.TitleOption{ - Text: "Line", - }, - Legend: charts.LegendOption{ - Data: []string{ - "Email", - "Union Ads", - "Video Ads", - "Direct", - "Search Engine", - }, - Left: charts.PositionCenter, - }, - XAxis: charts.NewXAxisOption([]string{ + p, err := charts.LineRender( + values, + charts.TitleTextOptionFunc("Line"), + charts.XAxisDataOptionFunc([]string{ "Mon", "Tue", "Wed", @@ -61,54 +83,15 @@ func main() { "Sat", "Sun", }), - SeriesList: charts.SeriesList{ - 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, - }), - }, - }).Render() + charts.LegendLabelsOptionFunc([]string{ + "Email", + "Union Ads", + "Video Ads", + "Direct", + "Search Engine", + }, charts.PositionCenter), + ) + if err != nil { panic(err) } diff --git a/examples/pie_chart/main.go b/examples/pie_chart/main.go index e69bf60..8a98e57 100644 --- a/examples/pie_chart/main.go +++ b/examples/pie_chart/main.go @@ -24,27 +24,27 @@ func writeFile(buf []byte) error { } func main() { - p, err := charts.NewPainter(charts.PainterOptions{ - Width: 800, - Height: 600, - Type: charts.ChartOutputPNG, - }) - if err != nil { - panic(err) + values := []float64{ + 1048, + 735, + 580, + 484, + 300, } - _, err = charts.NewPieChart(p, charts.PieChartOption{ - Title: charts.TitleOption{ + p, err := charts.PieRender( + values, + charts.TitleOptionFunc(charts.TitleOption{ Text: "Rainfall vs Evaporation", Subtext: "Fake Data", Left: charts.PositionCenter, - }, - Padding: charts.Box{ + }), + charts.PaddingOptionFunc(charts.Box{ Top: 20, Right: 20, Bottom: 20, Left: 20, - }, - Legend: charts.LegendOption{ + }), + charts.LegendOptionFunc(charts.LegendOption{ Orient: charts.OrientVertical, Data: []string{ "Search Engine", @@ -54,20 +54,9 @@ func main() { "Video Ads", }, Left: charts.PositionLeft, - }, - SeriesList: charts.NewPieSeriesList([]float64{ - 1048, - 735, - 580, - 484, - 300, - }, charts.PieSeriesOption{ - Label: charts.SeriesLabel{ - Show: true, - }, - Radius: "35%", }), - }).Render() + charts.PieSeriesShowLabel(), + ) if err != nil { panic(err) } diff --git a/examples/radar_chart/main.go b/examples/radar_chart/main.go index 077fa48..9550951 100644 --- a/examples/radar_chart/main.go +++ b/examples/radar_chart/main.go @@ -24,79 +24,47 @@ func writeFile(buf []byte) error { } func main() { - p, err := charts.NewPainter(charts.PainterOptions{ - Width: 800, - Height: 600, - Type: charts.ChartOutputPNG, - }) - if err != nil { - panic(err) + values := [][]float64{ + { + 4200, + 3000, + 20000, + 35000, + 50000, + 18000, + }, + { + 5000, + 14000, + 28000, + 26000, + 42000, + 21000, + }, } - _, err = charts.NewRadarChart(p, charts.RadarChartOption{ - Padding: charts.Box{ - Left: 10, - Top: 10, - Right: 10, - Bottom: 10, - }, - Title: charts.TitleOption{ - Text: "Basic Radar Chart", - }, - Legend: charts.NewLegendOption([]string{ + p, err := charts.RadarRender( + values, + charts.TitleTextOptionFunc("Basic Radar Chart"), + charts.LegendLabelsOptionFunc([]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, - }), - }, - }, - }).Render() + charts.RadarIndicatorOptionFunc([]string{ + "Sales", + "Administration", + "Information Technology", + "Customer Support", + "Development", + "Marketing", + }, []float64{ + 6500, + 16000, + 30000, + 38000, + 52000, + 25000, + }), + ) if err != nil { panic(err) } diff --git a/funnel_chart.go b/funnel_chart.go index c8457dd..63b3504 100644 --- a/funnel_chart.go +++ b/funnel_chart.go @@ -36,7 +36,7 @@ type funnelChart struct { func NewFunnelChart(p *Painter, opt FunnelChartOption) *funnelChart { if opt.Theme == nil { - opt.Theme = NewTheme("") + opt.Theme = defaultTheme } return &funnelChart{ p: p, diff --git a/horizontal_bar_chart.go b/horizontal_bar_chart.go index c98d688..fb23734 100644 --- a/horizontal_bar_chart.go +++ b/horizontal_bar_chart.go @@ -52,7 +52,7 @@ type HorizontalBarChartOption struct { func NewHorizontalBarChart(p *Painter, opt HorizontalBarChartOption) *horizontalBarChart { if opt.Theme == nil { - opt.Theme = NewTheme("") + opt.Theme = defaultTheme } return &horizontalBarChart{ p: p, diff --git a/line_chart.go b/line_chart.go index c505a91..0dc0fd8 100644 --- a/line_chart.go +++ b/line_chart.go @@ -34,7 +34,7 @@ type lineChart struct { func NewLineChart(p *Painter, opt LineChartOption) *lineChart { if opt.Theme == nil { - opt.Theme = NewTheme("") + opt.Theme = defaultTheme } return &lineChart{ p: p, diff --git a/painter.go b/painter.go index 5a8dd89..0bacd3c 100644 --- a/painter.go +++ b/painter.go @@ -149,9 +149,9 @@ func NewPainter(opts PainterOptions, opt ...PainterOption) (*Painter, error) { } font = f } - fn := chart.SVG - if opts.Type == ChartOutputPNG { - fn = chart.PNG + fn := chart.PNG + if opts.Type == ChartOutputSVG { + fn = chart.SVG } width := opts.Width height := opts.Height diff --git a/pie_chart.go b/pie_chart.go index c5a2ff2..972b4c1 100644 --- a/pie_chart.go +++ b/pie_chart.go @@ -53,7 +53,7 @@ type PieChartOption struct { func NewPieChart(p *Painter, opt PieChartOption) *pieChart { if opt.Theme == nil { - opt.Theme = NewTheme("") + opt.Theme = defaultTheme } return &pieChart{ p: p, diff --git a/radar_chart.go b/radar_chart.go index dc93ca8..610d5f7 100644 --- a/radar_chart.go +++ b/radar_chart.go @@ -64,7 +64,7 @@ type RadarChartOption struct { func NewRadarChart(p *Painter, opt RadarChartOption) *radarChart { if opt.Theme == nil { - opt.Theme = NewTheme("") + opt.Theme = defaultTheme } return &radarChart{ p: p, diff --git a/series.go b/series.go index 4808bcb..905c140 100644 --- a/series.go +++ b/series.go @@ -36,6 +36,14 @@ type SeriesData struct { Style Style } +func NewSeriesListDataFromValues(values [][]float64, chartType ...string) SeriesList { + seriesList := make(SeriesList, len(values)) + for index, value := range values { + seriesList[index] = NewSeriesFromValues(value, chartType...) + } + return seriesList +} + func NewSeriesFromValues(values []float64, chartType ...string) Series { s := Series{ Data: NewSeriesDataFromValues(values), diff --git a/theme.go b/theme.go index 544588a..26786b9 100644 --- a/theme.go +++ b/theme.go @@ -55,10 +55,21 @@ type themeColorPalette struct { font *truetype.Font } +type ThemeOption struct { + IsDarkMode bool + AxisStrokeColor Color + AxisSplitLineColor Color + BackgroundColor Color + TextColor Color + SeriesColors []Color +} + var palettes = map[string]ColorPalette{} const defaultFontSize = 12.0 +var defaultTheme ColorPalette + func init() { echartSeriesColors := []Color{ parseColor("#5470c6"), @@ -93,121 +104,134 @@ func init() { } AddTheme( ThemeDark, - true, - Color{ - R: 185, - G: 184, - B: 206, - A: 255, + ThemeOption{ + IsDarkMode: true, + AxisStrokeColor: Color{ + R: 185, + G: 184, + B: 206, + A: 255, + }, + AxisSplitLineColor: Color{ + R: 72, + G: 71, + B: 83, + A: 255, + }, + BackgroundColor: Color{ + R: 16, + G: 12, + B: 42, + A: 255, + }, + TextColor: Color{ + R: 238, + G: 238, + B: 238, + A: 255, + }, + SeriesColors: echartSeriesColors, }, - Color{ - R: 72, - G: 71, - B: 83, - A: 255, - }, - Color{ - R: 16, - G: 12, - B: 42, - A: 255, - }, - Color{ - R: 238, - G: 238, - B: 238, - A: 255, - }, - echartSeriesColors, ) AddTheme( ThemeLight, - false, - Color{ - R: 110, - G: 112, - B: 121, - A: 255, + ThemeOption{ + IsDarkMode: false, + AxisStrokeColor: Color{ + R: 110, + G: 112, + B: 121, + A: 255, + }, + AxisSplitLineColor: Color{ + R: 224, + G: 230, + B: 242, + A: 255, + }, + BackgroundColor: drawing.ColorWhite, + TextColor: Color{ + R: 70, + G: 70, + B: 70, + A: 255, + }, + SeriesColors: echartSeriesColors, }, - 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, - Color{ - R: 110, - G: 112, - B: 121, - A: 255, + ThemeOption{ + IsDarkMode: false, + AxisStrokeColor: Color{ + R: 110, + G: 112, + B: 121, + A: 255, + }, + AxisSplitLineColor: Color{ + R: 224, + G: 230, + B: 242, + A: 255, + }, + BackgroundColor: drawing.ColorWhite, + TextColor: drawing.Color{ + R: 70, + G: 70, + B: 70, + A: 255, + }, + SeriesColors: antSeriesColors, }, - 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, + ThemeOption{ + IsDarkMode: true, + AxisStrokeColor: Color{ + R: 185, + G: 184, + B: 206, + A: 255, + }, + AxisSplitLineColor: Color{ + R: 68, + G: 67, + B: 67, + A: 255, + }, + BackgroundColor: drawing.Color{ + R: 31, + G: 29, + B: 29, + A: 255, + }, + TextColor: Color{ + R: 216, + G: 217, + B: 218, + A: 255, + }, + SeriesColors: grafanaSeriesColors, }, - 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, ) + SetDefaultTheme(ThemeLight) } -func AddTheme(name string, isDarkMode bool, axisStrokeColor, axisSplitLineColor, backgroundColor, textColor drawing.Color, seriesColors []drawing.Color) { +func SetDefaultTheme(name string) { + defaultTheme = NewTheme(name) +} + +func AddTheme(name string, opt ThemeOption) { palettes[name] = &themeColorPalette{ - isDarkMode: isDarkMode, - axisStrokeColor: axisStrokeColor, - axisSplitLineColor: axisSplitLineColor, - backgroundColor: backgroundColor, - textColor: textColor, - seriesColors: seriesColors, + isDarkMode: opt.IsDarkMode, + axisStrokeColor: opt.AxisStrokeColor, + axisSplitLineColor: opt.AxisSplitLineColor, + backgroundColor: opt.BackgroundColor, + textColor: opt.TextColor, + seriesColors: opt.SeriesColors, } } From 5db24de7ed177121f13cd2a8d62b18426e420a46 Mon Sep 17 00:00:00 2001 From: vicanso Date: Sat, 18 Jun 2022 08:55:46 +0800 Subject: [PATCH 20/21] refactor: add example for chinese --- README_zh.md | 4 +- assets/go-charts.png | Bin 372652 -> 340210 bytes axis.go | 3 + charts.go | 1 + examples/chinese/main.go | 118 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 examples/chinese/main.go diff --git a/README_zh.md b/README_zh.md index 57d9db4..2d16b04 100644 --- a/README_zh.md +++ b/README_zh.md @@ -220,4 +220,6 @@ BenchmarkMultiChartSVGRender-8 367 3356325 ns/op 默认使用的字符为`roboto`为英文字体库,因此如果需要显示中文字符需要增加中文字体库,`InstallFont`函数可添加对应的字体库,成功添加之后则指定`title.textStyle.fontFamily`即可。 在浏览器中使用`svg`时,如果指定的`fontFamily`不支持中文字符,展示的中文并不会乱码,但是会导致在计算字符宽度等错误。 -[中文字库noto-cjk](https://github.com/googlefonts/noto-cjk) \ No newline at end of file +字体文件可以在[中文字库noto-cjk](https://github.com/googlefonts/noto-cjk)下载,注意下载时选择字体格式为 `ttf` 格式,如果选用 `otf` 格式可能会加载失败。 + +示例见 [examples/chinese/main.go](examples/chinese/main.go) \ No newline at end of file diff --git a/assets/go-charts.png b/assets/go-charts.png index 5ead96171cd7989ba1666b8492f341425a701a95..a80e24112b8cb0717c1866b20152a56f3a50cf21 100644 GIT binary patch literal 340210 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%0D6~1LH>ENm6 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%0ARZYXFO_3h|Utj(s1Qb9QiHJ92ZS!~CvAoR66V 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Ȧ@}U3x+-;Jv|;DY=kz{9BKN5wNwjfH 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

D!I1{Rz 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≈kK5r4!2Copr|{}UV54CsWxJ_fI742yP3c!t+r{T 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

jh85;|DNbP7O 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 Date: Sat, 18 Jun 2022 09:17:16 +0800 Subject: [PATCH 21/21] docs: update documents --- README.md | 378 +++++++++++++++++++++--- README_zh.md | 405 ++++++++++++++++++++++---- examples/bar_chart/main.go | 2 +- examples/charts/main.go | 2 +- examples/chinese/main.go | 2 +- examples/funnel_chart/main.go | 2 +- examples/horizontal_bar_chart/main.go | 2 +- examples/line_chart/main.go | 2 +- examples/painter/main.go | 2 +- examples/pie_chart/main.go | 2 +- examples/radar_chart/main.go | 2 +- go.mod | 8 +- go.sum | 15 +- 13 files changed, 695 insertions(+), 129 deletions(-) diff --git a/README.md b/README.md index 7affa30..a58adb4 100644 --- a/README.md +++ b/README.md @@ -21,35 +21,19 @@ These chart types are supported: `line`, `bar`, `pie`, `radar` or `funnel`. ## Example -The example is for `golang option` and `echarts option`, more examples can be found in the `./examples/` directory. +More examples can be found in the [./examples/](./examples/) directory. + +### Line Chart ```go package main import ( - "os" - "path/filepath" - - charts "github.com/vicanso/go-charts" + charts "github.com/vicanso/go-charts/v2" ) -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 = os.WriteFile(file, buf, 0600) - if err != nil { - return err - } - return nil -} - -func chartsRender() ([]byte, error) { - values := [][]float64{ +func main() { +values := [][]float64{ { 120, 132, @@ -59,6 +43,18 @@ func chartsRender() ([]byte, error) { 230, 210, }, + { + // snip... + }, + { + // snip... + }, + { + // snip... + }, + { + // snip... + }, } p, err := charts.LineRender( values, @@ -72,15 +68,323 @@ func chartsRender() ([]byte, error) { "Sat", "Sun", }), + charts.LegendLabelsOptionFunc([]string{ + "Email", + "Union Ads", + "Video Ads", + "Direct", + "Search Engine", + }, charts.PositionCenter), + ) + + if err != nil { + panic(err) + } + + buf, err := p.Bytes() + if err != nil { + panic(err) + } + // snip... +} +``` + +### Bar Chart + +```go +package main + +import ( + "github.com/vicanso/go-charts/v2" +) + +func main() { + values := [][]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, + }, + { + // snip... + }, + } + p, err := charts.BarRender( + values, + charts.XAxisDataOptionFunc([]string{ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + }), + charts.LegendLabelsOptionFunc([]string{ + "Rainfall", + "Evaporation", + }, charts.PositionRight), + charts.MarkLineOptionFunc(0, charts.SeriesMarkDataTypeAverage), + charts.MarkPointOptionFunc(0, charts.SeriesMarkDataTypeMax, + charts.SeriesMarkDataTypeMin), + // custom option func + func(opt *charts.ChartOption) { + opt.SeriesList[1].MarkPoint = charts.NewMarkPoint( + charts.SeriesMarkDataTypeMax, + charts.SeriesMarkDataTypeMin, + ) + opt.SeriesList[1].MarkLine = charts.NewMarkLine( + charts.SeriesMarkDataTypeAverage, + ) + }, ) if err != nil { - return nil, err + panic(err) } - return p.Bytes() -} -func echartsRender() ([]byte, error) { - return charts.RenderEChartsToPNG(`{ + buf, err := p.Bytes() + if err != nil { + panic(err) + } + // snip... +} +``` + +### Horizontal Bar Chart + +```go +package main + +import ( + "github.com/vicanso/go-charts/v2" +) + +func main() { + values := [][]float64{ + { + 18203, + 23489, + 29034, + 104970, + 131744, + 630230, + }, + { + // snip... + }, + } + p, err := charts.HorizontalBarRender( + values, + charts.TitleTextOptionFunc("World Population"), + charts.PaddingOptionFunc(charts.Box{ + Top: 20, + Right: 40, + Bottom: 20, + Left: 20, + }), + charts.LegendLabelsOptionFunc([]string{ + "2011", + "2012", + }), + charts.YAxisDataOptionFunc([]string{ + "Brazil", + "Indonesia", + "USA", + "India", + "China", + "World", + }), + ) + if err != nil { + panic(err) + } + + buf, err := p.Bytes() + if err != nil { + panic(err) + } + // snip... +} +``` + +### Pie Chart + +```go +package main + +import ( + "github.com/vicanso/go-charts/v2" +) + +func main() { + values := []float64{ + 1048, + 735, + 580, + 484, + 300, + } + p, err := charts.PieRender( + values, + charts.TitleOptionFunc(charts.TitleOption{ + Text: "Rainfall vs Evaporation", + Subtext: "Fake Data", + Left: charts.PositionCenter, + }), + charts.PaddingOptionFunc(charts.Box{ + Top: 20, + Right: 20, + Bottom: 20, + Left: 20, + }), + charts.LegendOptionFunc(charts.LegendOption{ + Orient: charts.OrientVertical, + Data: []string{ + "Search Engine", + "Direct", + "Email", + "Union Ads", + "Video Ads", + }, + Left: charts.PositionLeft, + }), + charts.PieSeriesShowLabel(), + ) + if err != nil { + panic(err) + } + + buf, err := p.Bytes() + if err != nil { + panic(err) + } + // snip... +} +``` + +### Radar Chart + +```go +package main + +import ( + "github.com/vicanso/go-charts/v2" +) + +func main() { + values := [][]float64{ + { + 4200, + 3000, + 20000, + 35000, + 50000, + 18000, + }, + { + // snip... + }, + } + p, err := charts.RadarRender( + values, + charts.TitleTextOptionFunc("Basic Radar Chart"), + charts.LegendLabelsOptionFunc([]string{ + "Allocated Budget", + "Actual Spending", + }), + charts.RadarIndicatorOptionFunc([]string{ + "Sales", + "Administration", + "Information Technology", + "Customer Support", + "Development", + "Marketing", + }, []float64{ + 6500, + 16000, + 30000, + 38000, + 52000, + 25000, + }), + ) + if err != nil { + panic(err) + } + + buf, err := p.Bytes() + if err != nil { + panic(err) + } + // snip... +} +``` + +### Funnel Chart + +```go +package main + +import ( + "github.com/vicanso/go-charts/v2" +) + +func main() { + values := []float64{ + 100, + 80, + 60, + 40, + 20, + } + p, err := charts.FunnelRender( + values, + charts.TitleTextOptionFunc("Funnel"), + charts.LegendLabelsOptionFunc([]string{ + "Show", + "Click", + "Visit", + "Inquiry", + "Order", + }), + ) + if err != nil { + panic(err) + } + + buf, err := p.Bytes() + if err != nil { + panic(err) + } + // snip... +} +``` + +### ECharts Render + +```go +package main + +import ( + "github.com/vicanso/go-charts/v2" +) + +func main() { + buf, err := charts.RenderEChartsToPNG(`{ "title": { "text": "Line" }, @@ -93,25 +397,7 @@ func echartsRender() ([]byte, error) { } ] }`) -} - -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) - } - } + // snip... } ``` diff --git a/README_zh.md b/README_zh.md index 2d16b04..0e883bf 100644 --- a/README_zh.md +++ b/README_zh.md @@ -21,53 +21,44 @@ 下面的示例为`go-charts`两种方式的参数配置:golang的参数配置、echarts的JSON配置,输出相同的折线图。 -更多的示例参考:`./examples/`目录 +更多的示例参考:[./examples/](./examples/)目录 +### Line Chart ```go package main import ( - "os" - "path/filepath" - - charts "github.com/vicanso/go-charts" + charts "github.com/vicanso/go-charts/v2" ) -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 = os.WriteFile(file, buf, 0600) - if err != nil { - return err - } - return nil -} - -func chartsRender() ([]byte, error) { - d, err := charts.LineRender([][]float64{ +func main() { +values := [][]float64{ { - 150, + 120, + 132, + 101, + 134, + 90, 230, - 224, - 218, - 135, - 147, - 260, + 210, }, - }, - // output type - charts.PNGTypeOption(), - // title - charts.TitleOptionFunc(charts.TitleOption{ - Text: "Line", - }), - // x axis - charts.XAxisOptionFunc(charts.NewXAxisOption([]string{ + { + // snip... + }, + { + // snip... + }, + { + // snip... + }, + { + // snip... + }, + } + p, err := charts.LineRender( + values, + charts.TitleTextOptionFunc("Line"), + charts.XAxisDataOptionFunc([]string{ "Mon", "Tue", "Wed", @@ -75,16 +66,324 @@ func chartsRender() ([]byte, error) { "Fri", "Sat", "Sun", - })), + }), + charts.LegendLabelsOptionFunc([]string{ + "Email", + "Union Ads", + "Video Ads", + "Direct", + "Search Engine", + }, charts.PositionCenter), + ) + + if err != nil { + panic(err) + } + + buf, err := p.Bytes() + if err != nil { + panic(err) + } + // snip... +} +``` + +### Bar Chart + +```go +package main + +import ( + "github.com/vicanso/go-charts/v2" +) + +func main() { + values := [][]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, + }, + { + // snip... + }, + } + p, err := charts.BarRender( + values, + charts.XAxisDataOptionFunc([]string{ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + }), + charts.LegendLabelsOptionFunc([]string{ + "Rainfall", + "Evaporation", + }, charts.PositionRight), + charts.MarkLineOptionFunc(0, charts.SeriesMarkDataTypeAverage), + charts.MarkPointOptionFunc(0, charts.SeriesMarkDataTypeMax, + charts.SeriesMarkDataTypeMin), + // custom option func + func(opt *charts.ChartOption) { + opt.SeriesList[1].MarkPoint = charts.NewMarkPoint( + charts.SeriesMarkDataTypeMax, + charts.SeriesMarkDataTypeMin, + ) + opt.SeriesList[1].MarkLine = charts.NewMarkLine( + charts.SeriesMarkDataTypeAverage, + ) + }, ) if err != nil { - return nil, err + panic(err) } - return d.Bytes() -} -func echartsRender() ([]byte, error) { - return charts.RenderEChartsToPNG(`{ + buf, err := p.Bytes() + if err != nil { + panic(err) + } + // snip... +} +``` + +### Horizontal Bar Chart + +```go +package main + +import ( + "github.com/vicanso/go-charts/v2" +) + +func main() { + values := [][]float64{ + { + 18203, + 23489, + 29034, + 104970, + 131744, + 630230, + }, + { + // snip... + }, + } + p, err := charts.HorizontalBarRender( + values, + charts.TitleTextOptionFunc("World Population"), + charts.PaddingOptionFunc(charts.Box{ + Top: 20, + Right: 40, + Bottom: 20, + Left: 20, + }), + charts.LegendLabelsOptionFunc([]string{ + "2011", + "2012", + }), + charts.YAxisDataOptionFunc([]string{ + "Brazil", + "Indonesia", + "USA", + "India", + "China", + "World", + }), + ) + if err != nil { + panic(err) + } + + buf, err := p.Bytes() + if err != nil { + panic(err) + } + // snip... +} +``` + +### Pie Chart + +```go +package main + +import ( + "github.com/vicanso/go-charts/v2" +) + +func main() { + values := []float64{ + 1048, + 735, + 580, + 484, + 300, + } + p, err := charts.PieRender( + values, + charts.TitleOptionFunc(charts.TitleOption{ + Text: "Rainfall vs Evaporation", + Subtext: "Fake Data", + Left: charts.PositionCenter, + }), + charts.PaddingOptionFunc(charts.Box{ + Top: 20, + Right: 20, + Bottom: 20, + Left: 20, + }), + charts.LegendOptionFunc(charts.LegendOption{ + Orient: charts.OrientVertical, + Data: []string{ + "Search Engine", + "Direct", + "Email", + "Union Ads", + "Video Ads", + }, + Left: charts.PositionLeft, + }), + charts.PieSeriesShowLabel(), + ) + if err != nil { + panic(err) + } + + buf, err := p.Bytes() + if err != nil { + panic(err) + } + // snip... +} +``` + +### Radar Chart + +```go +package main + +import ( + "github.com/vicanso/go-charts/v2" +) + +func main() { + values := [][]float64{ + { + 4200, + 3000, + 20000, + 35000, + 50000, + 18000, + }, + { + // snip... + }, + } + p, err := charts.RadarRender( + values, + charts.TitleTextOptionFunc("Basic Radar Chart"), + charts.LegendLabelsOptionFunc([]string{ + "Allocated Budget", + "Actual Spending", + }), + charts.RadarIndicatorOptionFunc([]string{ + "Sales", + "Administration", + "Information Technology", + "Customer Support", + "Development", + "Marketing", + }, []float64{ + 6500, + 16000, + 30000, + 38000, + 52000, + 25000, + }), + ) + if err != nil { + panic(err) + } + + buf, err := p.Bytes() + if err != nil { + panic(err) + } + // snip... +} +``` + +### Funnel Chart + +```go +package main + +import ( + "github.com/vicanso/go-charts/v2" +) + +func main() { + values := []float64{ + 100, + 80, + 60, + 40, + 20, + } + p, err := charts.FunnelRender( + values, + charts.TitleTextOptionFunc("Funnel"), + charts.LegendLabelsOptionFunc([]string{ + "Show", + "Click", + "Visit", + "Inquiry", + "Order", + }), + ) + if err != nil { + panic(err) + } + + buf, err := p.Bytes() + if err != nil { + panic(err) + } + // snip... +} +``` + +### ECharts Render + +```go +package main + +import ( + "github.com/vicanso/go-charts/v2" +) + +func main() { + buf, err := charts.RenderEChartsToPNG(`{ "title": { "text": "Line" }, @@ -97,25 +396,7 @@ func echartsRender() ([]byte, error) { } ] }`) -} - -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) - } - } + // snip... } ``` diff --git a/examples/bar_chart/main.go b/examples/bar_chart/main.go index c9f1d58..c559a76 100644 --- a/examples/bar_chart/main.go +++ b/examples/bar_chart/main.go @@ -5,7 +5,7 @@ import ( "os" "path/filepath" - "github.com/vicanso/go-charts" + "github.com/vicanso/go-charts/v2" ) func writeFile(buf []byte) error { diff --git a/examples/charts/main.go b/examples/charts/main.go index b370b69..0e1d48e 100644 --- a/examples/charts/main.go +++ b/examples/charts/main.go @@ -5,7 +5,7 @@ import ( "net/http" "strconv" - charts "github.com/vicanso/go-charts" + charts "github.com/vicanso/go-charts/v2" ) var html = ` diff --git a/examples/chinese/main.go b/examples/chinese/main.go index 13724aa..bb7cc00 100644 --- a/examples/chinese/main.go +++ b/examples/chinese/main.go @@ -5,7 +5,7 @@ import ( "os" "path/filepath" - "github.com/vicanso/go-charts" + "github.com/vicanso/go-charts/v2" ) func writeFile(buf []byte) error { diff --git a/examples/funnel_chart/main.go b/examples/funnel_chart/main.go index 6b17614..8f21db6 100644 --- a/examples/funnel_chart/main.go +++ b/examples/funnel_chart/main.go @@ -5,7 +5,7 @@ import ( "os" "path/filepath" - "github.com/vicanso/go-charts" + "github.com/vicanso/go-charts/v2" ) func writeFile(buf []byte) error { diff --git a/examples/horizontal_bar_chart/main.go b/examples/horizontal_bar_chart/main.go index 6b206b0..8b996b6 100644 --- a/examples/horizontal_bar_chart/main.go +++ b/examples/horizontal_bar_chart/main.go @@ -5,7 +5,7 @@ import ( "os" "path/filepath" - "github.com/vicanso/go-charts" + "github.com/vicanso/go-charts/v2" ) func writeFile(buf []byte) error { diff --git a/examples/line_chart/main.go b/examples/line_chart/main.go index 435da78..45ff894 100644 --- a/examples/line_chart/main.go +++ b/examples/line_chart/main.go @@ -5,7 +5,7 @@ import ( "os" "path/filepath" - "github.com/vicanso/go-charts" + "github.com/vicanso/go-charts/v2" ) func writeFile(buf []byte) error { diff --git a/examples/painter/main.go b/examples/painter/main.go index cf2bb81..304361d 100644 --- a/examples/painter/main.go +++ b/examples/painter/main.go @@ -5,7 +5,7 @@ import ( "os" "path/filepath" - charts "github.com/vicanso/go-charts" + charts "github.com/vicanso/go-charts/v2" "github.com/wcharczuk/go-chart/v2/drawing" ) diff --git a/examples/pie_chart/main.go b/examples/pie_chart/main.go index 8a98e57..3721ed1 100644 --- a/examples/pie_chart/main.go +++ b/examples/pie_chart/main.go @@ -5,7 +5,7 @@ import ( "os" "path/filepath" - "github.com/vicanso/go-charts" + "github.com/vicanso/go-charts/v2" ) func writeFile(buf []byte) error { diff --git a/examples/radar_chart/main.go b/examples/radar_chart/main.go index 9550951..51f7409 100644 --- a/examples/radar_chart/main.go +++ b/examples/radar_chart/main.go @@ -5,7 +5,7 @@ import ( "os" "path/filepath" - "github.com/vicanso/go-charts" + "github.com/vicanso/go-charts/v2" ) func writeFile(buf []byte) error { diff --git a/go.mod b/go.mod index 610af22..66145c7 100644 --- a/go.mod +++ b/go.mod @@ -1,17 +1,17 @@ -module github.com/vicanso/go-charts +module github.com/vicanso/go-charts/v2 go 1.17 require ( github.com/dustin/go-humanize v1.0.0 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 - github.com/stretchr/testify v1.7.1 + github.com/stretchr/testify v1.7.2 github.com/wcharczuk/go-chart/v2 v2.1.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9 // indirect - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect + golang.org/x/image v0.0.0-20220617043117-41969df76e82 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index d88f473..5f953b0 100644 --- a/go.sum +++ b/go.sum @@ -8,18 +8,17 @@ github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGw github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/wcharczuk/go-chart/v2 v2.1.0 h1:tY2slqVQ6bN+yHSnDYwZebLQFkphK4WNrVwnt7CJZ2I= github.com/wcharczuk/go-chart/v2 v2.1.0/go.mod h1:yx7MvAVNcP/kN9lKXM/NTce4au4DFN99j6i1OwDclNA= golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9 h1:LRtI4W37N+KFebI/qV0OFiLUv4GLOWeEW5hn/KEJvxE= -golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20220617043117-41969df76e82 h1:KpZB5pUSBvrHltNEdK/tw0xlPeD13M6M6aGP32gKqiw= +golang.org/x/image v0.0.0-20220617043117-41969df76e82/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=