348 lines
7.4 KiB
Go
348 lines
7.4 KiB
Go
// 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 tableChart struct {
|
|
p *Painter
|
|
opt *TableChartOption
|
|
}
|
|
|
|
// NewTableChart returns a table chart render
|
|
func NewTableChart(p *Painter, opt TableChartOption) *tableChart {
|
|
if opt.Theme == nil {
|
|
opt.Theme = defaultTheme
|
|
}
|
|
return &tableChart{
|
|
p: p,
|
|
opt: &opt,
|
|
}
|
|
}
|
|
|
|
type TableChartOption struct {
|
|
// The output type
|
|
Type string
|
|
// The width of table
|
|
Width int
|
|
// The theme
|
|
Theme ColorPalette
|
|
// The padding of table cell
|
|
Padding Box
|
|
// The header data of table
|
|
Header []string
|
|
// The data of table
|
|
Data [][]string
|
|
// The span of table column
|
|
Spans []int
|
|
// The font size of table
|
|
FontSize float64
|
|
// The font family, which should be installed first
|
|
FontFamily string
|
|
Font *truetype.Font
|
|
// The font color of table
|
|
FontColor Color
|
|
// The background color of header
|
|
HeaderBackgroundColor Color
|
|
// The header font color
|
|
HeaderFontColor Color
|
|
// The background color of row
|
|
RowBackgroundColors []Color
|
|
// The background color
|
|
BackgroundColor Color
|
|
}
|
|
|
|
type TableSetting struct {
|
|
// The color of header
|
|
HeaderColor Color
|
|
// The color of heder text
|
|
HeaderFontColor Color
|
|
// The color of table text
|
|
FontColor Color
|
|
// The color list of row
|
|
RowColors []Color
|
|
// The padding of cell
|
|
Padding Box
|
|
}
|
|
|
|
var TableLightThemeSetting = TableSetting{
|
|
HeaderColor: Color{
|
|
R: 240,
|
|
G: 240,
|
|
B: 240,
|
|
A: 255,
|
|
},
|
|
HeaderFontColor: Color{
|
|
R: 98,
|
|
G: 105,
|
|
B: 118,
|
|
A: 255,
|
|
},
|
|
FontColor: Color{
|
|
R: 70,
|
|
G: 70,
|
|
B: 70,
|
|
A: 255,
|
|
},
|
|
RowColors: []Color{
|
|
drawing.ColorWhite,
|
|
{
|
|
R: 247,
|
|
G: 247,
|
|
B: 247,
|
|
A: 255,
|
|
},
|
|
},
|
|
Padding: Box{
|
|
Left: 10,
|
|
Top: 10,
|
|
Right: 10,
|
|
Bottom: 10,
|
|
},
|
|
}
|
|
|
|
var TableDarkThemeSetting = TableSetting{
|
|
HeaderColor: Color{
|
|
R: 38,
|
|
G: 38,
|
|
B: 42,
|
|
A: 255,
|
|
},
|
|
HeaderFontColor: Color{
|
|
R: 216,
|
|
G: 217,
|
|
B: 218,
|
|
A: 255,
|
|
},
|
|
FontColor: Color{
|
|
R: 216,
|
|
G: 217,
|
|
B: 218,
|
|
A: 255,
|
|
},
|
|
RowColors: []Color{
|
|
{
|
|
R: 24,
|
|
G: 24,
|
|
B: 28,
|
|
A: 255,
|
|
},
|
|
{
|
|
R: 38,
|
|
G: 38,
|
|
B: 42,
|
|
A: 255,
|
|
},
|
|
},
|
|
Padding: Box{
|
|
Left: 10,
|
|
Top: 10,
|
|
Right: 10,
|
|
Bottom: 10,
|
|
},
|
|
}
|
|
|
|
var tableDefaultSetting = TableLightThemeSetting
|
|
|
|
// SetDefaultTableSetting sets the default setting for table
|
|
func SetDefaultTableSetting(setting TableSetting) {
|
|
tableDefaultSetting = setting
|
|
}
|
|
|
|
type renderInfo struct {
|
|
Width int
|
|
Height int
|
|
HeaderHeight int
|
|
RowHeights []int
|
|
}
|
|
|
|
func (t *tableChart) render() (*renderInfo, error) {
|
|
info := renderInfo{
|
|
RowHeights: make([]int, 0),
|
|
}
|
|
p := t.p
|
|
opt := t.opt
|
|
if len(opt.Header) == 0 {
|
|
return nil, errors.New("header can not be nil")
|
|
}
|
|
theme := opt.Theme
|
|
if theme == nil {
|
|
theme = p.theme
|
|
}
|
|
fontSize := opt.FontSize
|
|
if fontSize == 0 {
|
|
fontSize = 12
|
|
}
|
|
fontColor := opt.FontColor
|
|
if fontColor.IsZero() {
|
|
fontColor = tableDefaultSetting.FontColor
|
|
}
|
|
font := opt.Font
|
|
if font == nil {
|
|
font = theme.GetFont()
|
|
}
|
|
headerFontColor := opt.HeaderFontColor
|
|
if opt.HeaderFontColor.IsZero() {
|
|
headerFontColor = tableDefaultSetting.HeaderFontColor
|
|
}
|
|
|
|
spans := opt.Spans
|
|
if len(spans) != len(opt.Header) {
|
|
newSpans := make([]int, len(opt.Header))
|
|
for index := range opt.Header {
|
|
if index >= len(spans) {
|
|
newSpans[index] = 1
|
|
} else {
|
|
newSpans[index] = spans[index]
|
|
}
|
|
}
|
|
spans = newSpans
|
|
}
|
|
|
|
sum := sumInt(spans)
|
|
values := autoDivideSpans(p.Width(), sum, spans)
|
|
height := 0
|
|
textStyle := Style{
|
|
FontSize: fontSize,
|
|
FontColor: headerFontColor,
|
|
FillColor: headerFontColor,
|
|
Font: font,
|
|
}
|
|
p.SetStyle(textStyle)
|
|
|
|
headerHeight := 0
|
|
padding := opt.Padding
|
|
if padding.IsZero() {
|
|
padding = tableDefaultSetting.Padding
|
|
}
|
|
|
|
renderTableCells := func(textList []string, currentHeight int, cellPadding Box) int {
|
|
cellMaxHeight := 0
|
|
paddingHeight := cellPadding.Top + cellPadding.Bottom
|
|
paddingWidth := cellPadding.Left + cellPadding.Right
|
|
for index, text := range textList {
|
|
x := values[index]
|
|
y := currentHeight + cellPadding.Top
|
|
width := values[index+1] - x
|
|
x += cellPadding.Left
|
|
width -= paddingWidth
|
|
box := p.TextFit(text, x, y+int(fontSize), width)
|
|
if box.Height()+paddingHeight > cellMaxHeight {
|
|
cellMaxHeight = box.Height() + paddingHeight
|
|
}
|
|
}
|
|
return cellMaxHeight
|
|
}
|
|
|
|
headerHeight = renderTableCells(opt.Header, height, padding)
|
|
height += headerHeight
|
|
info.HeaderHeight = headerHeight
|
|
|
|
textStyle.FontColor = fontColor
|
|
textStyle.FillColor = fontColor
|
|
p.SetStyle(textStyle)
|
|
for _, textList := range opt.Data {
|
|
cellHeight := renderTableCells(textList, height, padding)
|
|
info.RowHeights = append(info.RowHeights, cellHeight)
|
|
height += cellHeight
|
|
}
|
|
|
|
info.Width = p.Width()
|
|
info.Height = height
|
|
return &info, nil
|
|
}
|
|
|
|
func (t *tableChart) renderWithInfo(info *renderInfo) (Box, error) {
|
|
p := t.p
|
|
opt := t.opt
|
|
if !opt.BackgroundColor.IsZero() {
|
|
p.SetBackground(p.Width(), p.Height(), opt.BackgroundColor)
|
|
}
|
|
headerBGColor := opt.HeaderBackgroundColor
|
|
if headerBGColor.IsZero() {
|
|
headerBGColor = tableDefaultSetting.HeaderColor
|
|
}
|
|
|
|
// 如果设置表头背景色
|
|
p.SetBackground(info.Width, info.HeaderHeight, headerBGColor, true)
|
|
currentHeight := info.HeaderHeight
|
|
rowColors := opt.RowBackgroundColors
|
|
if len(rowColors) == 0 {
|
|
rowColors = tableDefaultSetting.RowColors
|
|
}
|
|
for index, h := range info.RowHeights {
|
|
color := rowColors[index%len(rowColors)]
|
|
child := p.Child(PainterPaddingOption(Box{
|
|
Top: currentHeight,
|
|
}))
|
|
child.SetBackground(p.Width(), h, color, true)
|
|
currentHeight += h
|
|
}
|
|
_, err := t.render()
|
|
if err != nil {
|
|
return BoxZero, err
|
|
}
|
|
|
|
return Box{
|
|
Right: info.Width,
|
|
Bottom: info.Height,
|
|
}, nil
|
|
}
|
|
|
|
func (t *tableChart) Render() (Box, error) {
|
|
p := t.p
|
|
opt := t.opt
|
|
if !opt.BackgroundColor.IsZero() {
|
|
p.SetBackground(p.Width(), p.Height(), opt.BackgroundColor)
|
|
}
|
|
headerBGColor := opt.HeaderBackgroundColor
|
|
if headerBGColor.IsZero() {
|
|
headerBGColor = tableDefaultSetting.HeaderColor
|
|
}
|
|
if opt.Font == nil && opt.FontFamily != "" {
|
|
opt.Font, _ = GetFont(opt.FontFamily)
|
|
}
|
|
|
|
r := p.render
|
|
fn := chart.PNG
|
|
if p.outputType == ChartOutputSVG {
|
|
fn = chart.SVG
|
|
}
|
|
newRender, err := fn(p.Width(), 100)
|
|
if err != nil {
|
|
return BoxZero, err
|
|
}
|
|
p.render = newRender
|
|
info, err := t.render()
|
|
if err != nil {
|
|
return BoxZero, err
|
|
}
|
|
p.render = r
|
|
return t.renderWithInfo(info)
|
|
}
|