feat: support top setting for title
This commit is contained in:
parent
25e9984ad8
commit
72896d1c3f
5 changed files with 379 additions and 4 deletions
|
|
@ -57,6 +57,7 @@ func main() {
|
|||
- `title` 图表标题,包括标题内容、高度、颜色等
|
||||
- `title.text` 标题内容
|
||||
- `title.left` 标题与容器左侧的距离,可设置为`left`, `right`, `center`, `20%` 以及 `20` 这样的具体数值
|
||||
- `title.top` 标题与容器顶部的距离,暂仅支持具体数值,如`20`
|
||||
- `title.textStyle.color` 标题文字颜色
|
||||
- `title.textStyle.fontSize` 标题文字字体大小
|
||||
- `title.textStyle.height` 标题高度
|
||||
|
|
@ -87,7 +88,7 @@ func main() {
|
|||
|
||||
|
||||
|
||||
简单的图表生成PNG在20ms左右,而SVG的性能则更快,性能上比起使用`chrome headless`加载`echarts`图表展示页面,截图生成的方式大幅度提升,基本能满足简单的图表生成需求。
|
||||
简单的图表生成PNG在20ms左右,而SVG的性能则更快,性能上比起使用`chrome headless`加载`echarts`图表展示页面再截图生成的方式大幅度提升,满足简单的图表生成需求。
|
||||
|
||||
```bash
|
||||
goos: darwin
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ type (
|
|||
Style chart.Style
|
||||
Font *truetype.Font
|
||||
Left string
|
||||
Top string
|
||||
}
|
||||
Legend struct {
|
||||
Data []string
|
||||
|
|
@ -194,10 +195,11 @@ func newPieChart(opt Options) *chart.PieChart {
|
|||
if opt.Title.Left == "" {
|
||||
opt.Title.Left = "center"
|
||||
}
|
||||
themeColorPalette := &ThemeColorPalette{
|
||||
Theme: opt.Theme,
|
||||
titleColor := drawing.ColorBlack
|
||||
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 {
|
||||
p.Elements = []chart.Renderable{
|
||||
titleRender,
|
||||
|
|
|
|||
|
|
@ -182,6 +182,7 @@ type ECharsOptions struct {
|
|||
Title struct {
|
||||
Text string `json:"text"`
|
||||
Left Position `json:"left"`
|
||||
Top Position `json:"top"`
|
||||
TextStyle struct {
|
||||
Color string `json:"color"`
|
||||
FontFamily string `json:"fontFamily"`
|
||||
|
|
@ -282,6 +283,7 @@ func (e *ECharsOptions) ToOptions() Options {
|
|||
o.Title = Title{
|
||||
Text: e.Title.Text,
|
||||
Left: string(e.Title.Left),
|
||||
Top: string(e.Title.Top),
|
||||
Style: chart.Style{
|
||||
FontColor: parseColor(titleTextStyle.Color),
|
||||
FontSize: titleTextStyle.FontSize,
|
||||
|
|
|
|||
365
examples/demo/main.go
Normal file
365
examples/demo/main.go
Normal 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)
|
||||
}
|
||||
5
title.go
5
title.go
|
|
@ -88,6 +88,11 @@ func NewTitleCustomize(title Title) chart.Renderable {
|
|||
}
|
||||
|
||||
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 {
|
||||
x := titleX + (textWidth-item.width)>>1
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue