feat: support line chart draw function

This commit is contained in:
vicanso 2022-01-29 11:16:34 +08:00
parent 4ac419fce9
commit ccdaf70dcb
34 changed files with 1780 additions and 4672 deletions

249
legend.go
View file

@ -1,249 +0,0 @@
// MIT License
// Copyright (c) 2021 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 {
Style chart.Style
Padding chart.Box
Left string
Right string
Top string
Bottom string
Align string
Theme string
IconDraw LegendIconDraw
}
type LegendIconDrawOption struct {
Box chart.Box
Style chart.Style
Index int
Theme string
}
const (
LegendAlignLeft = "left"
LegendAlignRight = "right"
)
type LegendIconDraw func(r chart.Renderer, opt LegendIconDrawOption)
func DefaultLegendIconDraw(r chart.Renderer, opt LegendIconDrawOption) {
if opt.Box.IsZero() {
return
}
r.SetStrokeColor(opt.Style.GetStrokeColor())
strokeWidth := opt.Style.GetStrokeWidth()
r.SetStrokeWidth(strokeWidth)
height := opt.Box.Bottom - opt.Box.Top
ly := opt.Box.Top - (height / 2) + 2
r.MoveTo(opt.Box.Left, ly)
r.LineTo(opt.Box.Right, ly)
r.Stroke()
r.SetFillColor(getBackgroundColor(opt.Theme))
r.Circle(5, (opt.Box.Left+opt.Box.Right)/2, ly)
r.FillStroke()
}
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
}
func getLegendLeft(canvasWidth, legendBoxWidth int, opt LegendOption) int {
left := (canvasWidth - legendBoxWidth) / 2
leftValue := opt.Left
if leftValue == "auto" || leftValue == "center" {
leftValue = ""
}
if leftValue == "left" {
leftValue = "0"
}
rightValue := opt.Right
if rightValue == "auto" || leftValue == "center" {
rightValue = ""
}
if rightValue == "right" {
rightValue = "0"
}
if leftValue == "" && rightValue == "" {
return left
}
if leftValue != "" {
percent := convertPercent(leftValue)
if percent >= 0 {
return int(float64(canvasWidth) * percent)
}
v, _ := strconv.Atoi(leftValue)
return v
}
if rightValue != "" {
percent := convertPercent(rightValue)
if percent >= 0 {
return canvasWidth - legendBoxWidth - int(float64(canvasWidth)*percent)
}
v, _ := strconv.Atoi(rightValue)
return canvasWidth - legendBoxWidth - v
}
return left
}
func getLegendTop(height, legendBoxHeight int, opt LegendOption) int {
// TODO 支持top的处理
return 0
}
func NewLegendCustomize(series []chart.Series, opt LegendOption) chart.Renderable {
return func(r chart.Renderer, cb chart.Box, chartDefaults chart.Style) {
legendDefaults := chart.Style{
FontColor: getTextColor(opt.Theme),
FontSize: 8.0,
StrokeColor: chart.DefaultAxisColor,
}
legendStyle := opt.Style.InheritFrom(chartDefaults.InheritFrom(legendDefaults))
r.SetFont(legendStyle.GetFont())
r.SetFontColor(legendStyle.GetFontColor())
r.SetFontSize(legendStyle.GetFontSize())
var labels []string
var lines []chart.Style
// 计算label和lines
for _, s := range series {
if !s.GetStyle().Hidden {
if _, isAnnotationSeries := s.(chart.AnnotationSeries); !isAnnotationSeries {
labels = append(labels, s.GetName())
lines = append(lines, s.GetStyle())
}
}
}
var textHeight int
var textBox chart.Box
labelWidth := 0
// 计算文本宽度与高度(取最大值)
for x := 0; x < len(labels); x++ {
if len(labels[x]) > 0 {
textBox = r.MeasureText(labels[x])
labelWidth += textBox.Width()
textHeight = chart.MaxInt(textBox.Height(), textHeight)
}
}
legendBoxHeight := textHeight + legendStyle.Padding.Top + legendStyle.Padding.Bottom
chartPadding := cb.Top
legendYMargin := (chartPadding - legendBoxHeight) >> 1
iconWidth := 25
lineTextGap := 5
iconAllWidth := iconWidth * len(labels)
spaceAllWidth := (chart.DefaultMinimumTickHorizontalSpacing + lineTextGap) * (len(labels) - 1)
legendBoxWidth := labelWidth + iconAllWidth + spaceAllWidth
left := getLegendLeft(cb.Width(), legendBoxWidth, opt)
top := getLegendTop(cb.Height(), legendBoxHeight, opt)
left += (opt.Padding.Left + cb.Left)
top += (opt.Padding.Top + cb.Top)
legendBox := chart.Box{
Left: left,
Right: left + legendBoxWidth,
Top: top,
Bottom: top + legendBoxHeight,
}
chart.Draw.Box(r, legendBox, legendDefaults)
r.SetFont(legendStyle.GetFont())
r.SetFontColor(legendStyle.GetFontColor())
r.SetFontSize(legendStyle.GetFontSize())
startX := legendBox.Left + legendStyle.Padding.Left
ty := top + legendYMargin + legendStyle.Padding.Top + textHeight
var label string
var x int
iconDraw := opt.IconDraw
if iconDraw == nil {
iconDraw = DefaultLegendIconDraw
}
align := opt.Align
if align == "" {
align = LegendAlignLeft
}
for index := range labels {
label = labels[index]
if len(label) > 0 {
x = startX
// 如果图例标记靠右展示
if align == LegendAlignRight {
textBox = r.MeasureText(label)
r.Text(label, x, ty)
x = startX + textBox.Width() + lineTextGap
}
// 图标
iconDraw(r, LegendIconDrawOption{
Theme: opt.Theme,
Index: index,
Style: lines[index],
Box: chart.Box{
Left: x,
Top: ty,
Right: x + iconWidth,
Bottom: ty + textHeight,
},
})
x += (iconWidth + lineTextGap)
// 如果图例标记靠左展示
if align == LegendAlignLeft {
textBox = r.MeasureText(label)
r.Text(label, x, ty)
x += textBox.Width()
}
// 计算下一个legend的位置
startX = x + chart.DefaultMinimumTickHorizontalSpacing
}
}
}
}