From c4b5ac3f4241d6ea2a6e70272f0b3a3aa6653616 Mon Sep 17 00:00:00 2001 From: vicanso Date: Sat, 29 Jan 2022 16:35:45 +0800 Subject: [PATCH] feat: support legend render function --- chart.go | 14 ++++++-- legend.go | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++ line_chart.go | 12 +++++-- title.go | 11 +++--- util.go | 18 +++++++++- 5 files changed, 140 insertions(+), 10 deletions(-) create mode 100644 legend.go diff --git a/chart.go b/chart.go index e0e9a9a..556e2fe 100644 --- a/chart.go +++ b/chart.go @@ -41,7 +41,6 @@ type Point struct { type Series struct { Type string - Name string Data []SeriesData YAxisIndex int Style chart.Style @@ -50,6 +49,7 @@ type Series struct { type ChartOption struct { Theme string Title TitleOption + Legend LegendOption XAxis XAxisOption Width int Height int @@ -60,6 +60,7 @@ type ChartOption struct { } func (o *ChartOption) FillDefault(t *Theme) { + f, _ := chart.GetDefaultFont() if o.BackgroundColor.IsZero() { o.BackgroundColor = t.GetBackgroundColor() } @@ -70,7 +71,7 @@ func (o *ChartOption) FillDefault(t *Theme) { o.Title.Style.FontSize = 14 } if o.Title.Style.Font == nil { - o.Title.Style.Font, _ = chart.GetDefaultFont() + o.Title.Style.Font = f } if o.Title.Style.Padding.IsZero() { o.Title.Style.Padding = chart.Box{ @@ -80,6 +81,15 @@ func (o *ChartOption) FillDefault(t *Theme) { Bottom: 5, } } + if o.Legend.Style.FontSize == 0 { + o.Legend.Style.FontSize = 8 + } + if o.Legend.Style.Font == nil { + o.Legend.Style.Font = f + } + if o.Legend.Style.FontColor.IsZero() { + o.Legend.Style.FontColor = t.GetTitleColor() + } } func (o *ChartOption) getWidth() int { diff --git a/legend.go b/legend.go new file mode 100644 index 0000000..e37baf3 --- /dev/null +++ b/legend.go @@ -0,0 +1,95 @@ +// 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 LegendOption struct { + Style chart.Style + Data []string + Left string + Right string + Align string +} + +func drawLegend(p *Draw, opt *LegendOption, theme *Theme) (chart.Box, error) { + if len(opt.Data) == 0 { + return chart.BoxZero, nil + } + padding := opt.Style.Padding + legendDraw, err := NewDraw(DrawOption{ + Parent: p, + }, PaddingOption(padding)) + if err != nil { + return chart.BoxZero, err + } + r := legendDraw.Render + opt.Style.GetTextOptions().WriteToRenderer(r) + + x := 0 + y := 0 + legendWidth := 30 + legendDotHeight := 5 + textPadding := 5 + legendMargin := 10 + for index, text := range opt.Data { + if index != 0 { + x += legendMargin + } + style := chart.Style{ + StrokeColor: theme.GetSeriesColor(index), + FillColor: theme.GetSeriesColor(index), + StrokeWidth: 2, + } + textBox := r.MeasureText(text) + renderText := func() { + x += textPadding + legendDraw.text(text, x, y+legendDotHeight-1) + x += textBox.Width() + x += textPadding + } + + if opt.Align == PositionRight { + renderText() + } + + style.GetFillAndStrokeOptions().WriteDrawingOptionsToRenderer(r) + 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() + legendBox.Right = legendBox.Left + x + legendBox.Bottom = legendBox.Top + 2*legendDotHeight + + return legendBox, nil +} diff --git a/line_chart.go b/line_chart.go index 553e9b8..33e3ffa 100644 --- a/line_chart.go +++ b/line_chart.go @@ -53,7 +53,13 @@ func NewLineChart(opt LineChartOption) (*Draw, error) { } // 标题 - _, titleHeight, err := drawTitle(d, &opt.Title) + titleBox, err := drawTitle(d, &opt.Title) + if err != nil { + return nil, err + } + + opt.Legend.Style.Padding.Left += titleBox.Right + _, err = drawLegend(d, &opt.Legend, &theme) if err != nil { return nil, err } @@ -66,7 +72,7 @@ func NewLineChart(opt LineChartOption) (*Draw, error) { // 暂时仅支持单一yaxis yRange, err := drawYAxis(d, &opt.ChartOption, &theme, xAxisHeight, chart.Box{ - Top: titleHeight, + Top: titleBox.Height(), }) if err != nil { return nil, err @@ -75,7 +81,7 @@ func NewLineChart(opt LineChartOption) (*Draw, error) { sd, err := NewDraw(DrawOption{ Parent: d, }, PaddingOption(chart.Box{ - Top: titleHeight, + Top: titleBox.Height(), Left: YAxisWidth, })) if err != nil { diff --git a/title.go b/title.go index b37c0da..a6d9a39 100644 --- a/title.go +++ b/title.go @@ -41,9 +41,9 @@ type titleMeasureOption struct { text string } -func drawTitle(d *Draw, opt *TitleOption) (int, int, error) { +func drawTitle(d *Draw, opt *TitleOption) (chart.Box, error) { if len(opt.Text) == 0 { - return 0, 0, nil + return chart.BoxZero, nil } padding := opt.Style.Padding @@ -51,7 +51,7 @@ func drawTitle(d *Draw, opt *TitleOption) (int, int, error) { Parent: d, }, PaddingOption(padding)) if err != nil { - return 0, 0, err + return chart.BoxZero, err } r := titleDraw.Render @@ -107,6 +107,9 @@ func drawTitle(d *Draw, opt *TitleOption) (int, int, error) { titleY += textMaxHeight } height := titleY + padding.Top + padding.Bottom + box := padding.Clone() + box.Right = box.Left + width + box.Bottom = box.Top + height - return width, height, nil + return box, nil } diff --git a/util.go b/util.go index 041b8c5..01590b4 100644 --- a/util.go +++ b/util.go @@ -22,7 +22,12 @@ package charts -import "github.com/wcharczuk/go-chart/v2" +import ( + "strconv" + "strings" + + "github.com/wcharczuk/go-chart/v2" +) func TrueFlag() *bool { t := true @@ -90,3 +95,14 @@ func reverseIntSlice(intList []int) { intList[i], intList[j] = intList[j], intList[i] } } + +func convertPercent(value string) float64 { + if !strings.HasSuffix(value, "%") { + return -1 + } + v, err := strconv.Atoi(strings.ReplaceAll(value, "%", "")) + if err != nil { + return -1 + } + return float64(v) / 100 +}