feat: support top setting for title

This commit is contained in:
vicanso 2022-01-02 09:45:12 +08:00
parent 25e9984ad8
commit 72896d1c3f
5 changed files with 379 additions and 4 deletions

View file

@ -57,6 +57,7 @@ func main() {
- `title` 图表标题,包括标题内容、高度、颜色等 - `title` 图表标题,包括标题内容、高度、颜色等
- `title.text` 标题内容 - `title.text` 标题内容
- `title.left` 标题与容器左侧的距离,可设置为`left`, `right`, `center`, `20%` 以及 `20` 这样的具体数值 - `title.left` 标题与容器左侧的距离,可设置为`left`, `right`, `center`, `20%` 以及 `20` 这样的具体数值
- `title.top` 标题与容器顶部的距离,暂仅支持具体数值,如`20`
- `title.textStyle.color` 标题文字颜色 - `title.textStyle.color` 标题文字颜色
- `title.textStyle.fontSize` 标题文字字体大小 - `title.textStyle.fontSize` 标题文字字体大小
- `title.textStyle.height` 标题高度 - `title.textStyle.height` 标题高度
@ -87,7 +88,7 @@ func main() {
简单的图表生成PNG在20ms左右而SVG的性能则更快性能上比起使用`chrome headless`加载`echarts`图表展示页面,截图生成的方式大幅度提升,基本能满足简单的图表生成需求。 简单的图表生成PNG在20ms左右而SVG的性能则更快性能上比起使用`chrome headless`加载`echarts`图表展示页面再截图生成的方式大幅度提升,满足简单的图表生成需求。
```bash ```bash
goos: darwin goos: darwin

View file

@ -49,6 +49,7 @@ type (
Style chart.Style Style chart.Style
Font *truetype.Font Font *truetype.Font
Left string Left string
Top string
} }
Legend struct { Legend struct {
Data []string Data []string
@ -194,10 +195,11 @@ func newPieChart(opt Options) *chart.PieChart {
if opt.Title.Left == "" { if opt.Title.Left == "" {
opt.Title.Left = "center" opt.Title.Left = "center"
} }
themeColorPalette := &ThemeColorPalette{ titleColor := drawing.ColorBlack
Theme: opt.Theme, if opt.Theme == ThemeDark {
titleColor = drawing.ColorWhite
} }
titleRender := newTitleRenderable(opt.Title, p.GetFont(), themeColorPalette.TextColor()) titleRender := newTitleRenderable(opt.Title, p.GetFont(), titleColor)
if titleRender != nil { if titleRender != nil {
p.Elements = []chart.Renderable{ p.Elements = []chart.Renderable{
titleRender, titleRender,

View file

@ -182,6 +182,7 @@ type ECharsOptions struct {
Title struct { Title struct {
Text string `json:"text"` Text string `json:"text"`
Left Position `json:"left"` Left Position `json:"left"`
Top Position `json:"top"`
TextStyle struct { TextStyle struct {
Color string `json:"color"` Color string `json:"color"`
FontFamily string `json:"fontFamily"` FontFamily string `json:"fontFamily"`
@ -282,6 +283,7 @@ func (e *ECharsOptions) ToOptions() Options {
o.Title = Title{ o.Title = Title{
Text: e.Title.Text, Text: e.Title.Text,
Left: string(e.Title.Left), Left: string(e.Title.Left),
Top: string(e.Title.Top),
Style: chart.Style{ Style: chart.Style{
FontColor: parseColor(titleTextStyle.Color), FontColor: parseColor(titleTextStyle.Color),
FontSize: titleTextStyle.FontSize, FontSize: titleTextStyle.FontSize,

365
examples/demo/main.go Normal file
View file

@ -0,0 +1,365 @@
package main
import (
"bytes"
"net/http"
charts "github.com/vicanso/go-charts"
)
var html = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<link type="text/css" rel="styleSheet" href="https://unpkg.com/normalize.css@8.0.1/normalize.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style>
.charts {
width: 830px;
margin: 10px auto;
overflow: hidden;
}
.grid {
float: left;
margin-right: 10px;
}
.grid svg {
margin-bottom: 10px;
}
h1 {
text-align: center;
}
pre {
width: 100%;
margin: auto auto 20px auto;
max-height: 300px;
overflow: auto;
display: block;
}
svg{
margin: auto auto 50px auto;
display: block;
}
</style>
<title>go-charts</title>
</head>
<body>
<div class="charts">{{body}}</div>
</body>
</html>
`
var chartOptions = []map[string]string{
{
"option": `{
"title": {
"text": "Line",
"left": "center"
},
"yAxis": {
"min": 0,
"max": 300
},
"xAxis": {
"type": "category",
"data": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
},
"series": [
{
"data": [150, 230, 224, 218, 135, 147, 260],
"type": "line"
}
]
}`,
},
{
"option": `{
"legend": {
"align": "left",
"left": 0,
"data": ["Email", "Union Ads", "Video Ads", "Direct", "Search Engine"]
},
"xAxis": {
"type": "category",
"boundaryGap": false,
"data": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
},
"series": [
{
"type": "line",
"data": [120, 132, 101, 134, 90, 230, 210]
},
{
"data": [220, 182, 191, 234, 290, 330, 310]
},
{
"data": [150, 232, 201, 154, 190, 330, 410]
},
{
"data": [320, 332, 301, 334, 390, 330, 320]
},
{
"data": [820, 932, 901, 934, 1290, 1330, 1320]
}
]
}`,
},
{
"title": "柱状图(自定义颜色)",
"option": `{
"xAxis": {
"type": "category",
"data": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
},
"series": [
{
"data": [
120,
{
"value": 200,
"itemStyle": {
"color": "#a90000"
}
},
150,
80,
70,
110,
130
],
"type": "bar"
}
]
}`,
},
{
"title": "多柱状图",
"option": `{
"title": {
"text": "Rainfall vs Evaporation",
"top": 10
},
"legend": {
"data": ["Rainfall", "Evaporation"]
},
"xAxis": {
"type": "category",
"splitNumber": 12,
"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]
},
{
"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]
}
]
}`,
},
{
"title": "折柱混合",
"option": `{
"legend": {
"data": [
"Evaporation",
"Precipitation",
"Temperature"
]
},
"xAxis": {
"type": "category",
"data": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
},
"yAxis": [
{
"type": "value",
"name": "Precipitation",
"min": 0,
"max": 250,
"interval": 50,
"axisLabel": {
"formatter": "{value} ml"
}
},
{
"type": "value",
"name": "Temperature",
"min": 0,
"max": 25,
"interval": 5,
"axisLabel": {
"formatter": "{value} °C"
}
}
],
"series": [
{
"name": "Evaporation",
"type": "bar",
"itemStyle": {
"color": "#0052d9"
},
"data": [2, 4.9, 7, 23.2, 25.6, 76.7, 135.6]
},
{
"name": "Precipitation",
"type": "bar",
"data": [2.6, 5.9, 9, 26.4, 28.7, 70.7, 175.6]
},
{
"name": "Temperature",
"type": "line",
"yAxisIndex": 1,
"data": [2, 2.2, 3.3, 4.5, 6.3, 10.2, 20.3]
}
]
}`,
},
{
"title": "降雨量",
"option": `{
"title": {
"text": "Rainfall",
"left": "right"
},
"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": "饼图",
"option": `{
"title": {
"text": "Referer of a Website"
},
"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"
}
]
}
]
}`,
},
}
type renderOptions struct {
theme string
width int
height int
onlyCharts bool
}
func render(opts renderOptions) ([]byte, error) {
data := bytes.Buffer{}
for _, m := range chartOptions {
chartHTML := []byte(`<div class="grid">
{{svg}}
</div>`)
o, err := charts.ParseECharsOptions(m["option"])
if err != nil {
return nil, err
}
if opts.width > 0 {
o.Width = opts.width
}
if opts.height > 0 {
o.Height = opts.height
}
for _, theme := range []string{
charts.ThemeDark,
charts.ThemeLight,
} {
o.Theme = theme
g, err := charts.New(o)
if err != nil {
return nil, err
}
buf, err := charts.ToSVG(g)
if err != nil {
return nil, err
}
buf = bytes.ReplaceAll(chartHTML, []byte("{{svg}}"), buf)
data.Write(buf)
}
}
return data.Bytes(), nil
}
func indexHandler(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
return
}
query := r.URL.Query()
opts := renderOptions{
theme: query.Get("theme"),
width: 400,
height: 200,
onlyCharts: true,
}
buf, err := render(opts)
if err != nil {
w.WriteHeader(400)
w.Write([]byte(err.Error()))
return
}
data := bytes.ReplaceAll([]byte(html), []byte("{{body}}"), buf)
w.Header().Set("Content-Type", "text/html")
w.Write(data)
}
func main() {
http.HandleFunc("/", indexHandler)
http.ListenAndServe(":3012", nil)
}

View file

@ -88,6 +88,11 @@ func NewTitleCustomize(title Title) chart.Renderable {
} }
titleY := cb.Top + title.Style.Padding.GetTop(chart.DefaultTitleTop) + (textHeight >> 1) titleY := cb.Top + title.Style.Padding.GetTop(chart.DefaultTitleTop) + (textHeight >> 1)
// TOP 暂只支持数值
if title.Top != "" {
value, _ := strconv.Atoi(title.Top)
titleY += value
}
for _, item := range measureOptions { for _, item := range measureOptions {
x := titleX + (textWidth-item.width)>>1 x := titleX + (textWidth-item.width)>>1