refactor: support dark theme
This commit is contained in:
parent
8f7587561f
commit
ead48fef8e
7 changed files with 568 additions and 95 deletions
95
axis.go
95
axis.go
|
|
@ -25,7 +25,6 @@ package charts
|
||||||
import (
|
import (
|
||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
"github.com/wcharczuk/go-chart/v2"
|
"github.com/wcharczuk/go-chart/v2"
|
||||||
"github.com/wcharczuk/go-chart/v2/drawing"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
|
@ -41,6 +40,10 @@ type (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type YAxisOption struct {
|
||||||
|
Formater chart.ValueFormatter
|
||||||
|
}
|
||||||
|
|
||||||
const axisStrokeWidth = 1
|
const axisStrokeWidth = 1
|
||||||
|
|
||||||
func GetXAxisAndValues(xAxis XAxis, tickPosition chart.TickPosition, theme string) (chart.XAxis, []float64) {
|
func GetXAxisAndValues(xAxis XAxis, tickPosition chart.TickPosition, theme string) (chart.XAxis, []float64) {
|
||||||
|
|
@ -85,24 +88,29 @@ func GetXAxisAndValues(xAxis XAxis, tickPosition chart.TickPosition, theme strin
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO
|
|
||||||
if theme == ThemeDark {
|
|
||||||
return chart.XAxis{
|
|
||||||
Ticks: ticks,
|
|
||||||
}, xValues
|
|
||||||
}
|
|
||||||
return chart.XAxis{
|
return chart.XAxis{
|
||||||
Ticks: ticks,
|
Ticks: ticks,
|
||||||
TickPosition: tickPosition,
|
TickPosition: tickPosition,
|
||||||
Style: chart.Style{
|
Style: chart.Style{
|
||||||
FontColor: AxisColorLight,
|
FontColor: getAxisColor(theme),
|
||||||
StrokeColor: AxisColorLight,
|
StrokeColor: getAxisColor(theme),
|
||||||
StrokeWidth: axisStrokeWidth,
|
StrokeWidth: axisStrokeWidth,
|
||||||
},
|
},
|
||||||
}, xValues
|
}, xValues
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetSecondaryYAxis(theme string) chart.YAxis {
|
func defaultFloatFormater(v interface{}) string {
|
||||||
|
value, ok := v.(float64)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if value >= 10 {
|
||||||
|
return humanize.CommafWithDigits(value, 0)
|
||||||
|
}
|
||||||
|
return humanize.CommafWithDigits(value, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSecondaryYAxis(theme string, option *YAxisOption) chart.YAxis {
|
||||||
// TODO
|
// TODO
|
||||||
if theme == ThemeDark {
|
if theme == ThemeDark {
|
||||||
return chart.YAxis{}
|
return chart.YAxis{}
|
||||||
|
|
@ -113,41 +121,46 @@ func GetSecondaryYAxis(theme string) chart.YAxis {
|
||||||
// B: 241,
|
// B: 241,
|
||||||
// A: 255,
|
// A: 255,
|
||||||
// }
|
// }
|
||||||
return chart.YAxis{
|
formater := defaultFloatFormater
|
||||||
ValueFormatter: func(v interface{}) string {
|
if option != nil {
|
||||||
value, ok := v.(float64)
|
if option.Formater != nil {
|
||||||
if !ok {
|
formater = option.Formater
|
||||||
return ""
|
}
|
||||||
}
|
|
||||||
return humanize.Commaf(value)
|
|
||||||
},
|
|
||||||
AxisType: chart.YAxisPrimary,
|
|
||||||
GridMajorStyle: chart.Hidden(),
|
|
||||||
GridMinorStyle: chart.Hidden(),
|
|
||||||
Style: chart.Hidden(),
|
|
||||||
}
|
}
|
||||||
|
hidden := chart.Hidden()
|
||||||
|
return chart.YAxis{
|
||||||
|
ValueFormatter: formater,
|
||||||
|
AxisType: chart.YAxisPrimary,
|
||||||
|
GridMajorStyle: hidden,
|
||||||
|
GridMinorStyle: hidden,
|
||||||
|
Style: chart.Style{
|
||||||
|
FontColor: getAxisColor(theme),
|
||||||
|
// alpha 0,隐藏
|
||||||
|
StrokeColor: hiddenColor,
|
||||||
|
StrokeWidth: axisStrokeWidth,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
// c := chart.Hidden()
|
||||||
|
// return chart.YAxis{
|
||||||
|
// ValueFormatter: defaultFloatFormater,
|
||||||
|
// AxisType: chart.YAxisPrimary,
|
||||||
|
// GridMajorStyle: c,
|
||||||
|
// GridMinorStyle: c,
|
||||||
|
// Style: c,
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetYAxis(theme string) chart.YAxis {
|
func GetYAxis(theme string, option *YAxisOption) chart.YAxis {
|
||||||
// TODO
|
strokeColor := getGridColor(theme)
|
||||||
if theme == ThemeDark {
|
formater := defaultFloatFormater
|
||||||
return chart.YAxis{}
|
if option != nil {
|
||||||
}
|
if option.Formater != nil {
|
||||||
strokeColor := drawing.Color{
|
formater = option.Formater
|
||||||
R: 224,
|
}
|
||||||
G: 230,
|
|
||||||
B: 241,
|
|
||||||
A: 255,
|
|
||||||
}
|
}
|
||||||
return chart.YAxis{
|
return chart.YAxis{
|
||||||
ValueFormatter: func(v interface{}) string {
|
ValueFormatter: formater,
|
||||||
value, ok := v.(float64)
|
AxisType: chart.YAxisSecondary,
|
||||||
if !ok {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return humanize.Commaf(value)
|
|
||||||
},
|
|
||||||
AxisType: chart.YAxisSecondary,
|
|
||||||
GridMajorStyle: chart.Style{
|
GridMajorStyle: chart.Style{
|
||||||
StrokeColor: strokeColor,
|
StrokeColor: strokeColor,
|
||||||
StrokeWidth: axisStrokeWidth,
|
StrokeWidth: axisStrokeWidth,
|
||||||
|
|
@ -157,7 +170,7 @@ func GetYAxis(theme string) chart.YAxis {
|
||||||
StrokeWidth: axisStrokeWidth,
|
StrokeWidth: axisStrokeWidth,
|
||||||
},
|
},
|
||||||
Style: chart.Style{
|
Style: chart.Style{
|
||||||
FontColor: AxisColorLight,
|
FontColor: getAxisColor(theme),
|
||||||
// alpha 0,隐藏
|
// alpha 0,隐藏
|
||||||
StrokeColor: hiddenColor,
|
StrokeColor: hiddenColor,
|
||||||
StrokeWidth: axisStrokeWidth,
|
StrokeWidth: axisStrokeWidth,
|
||||||
|
|
|
||||||
50
charts.go
50
charts.go
|
|
@ -53,7 +53,7 @@ type (
|
||||||
Height int
|
Height int
|
||||||
Theme string
|
Theme string
|
||||||
XAxis XAxis
|
XAxis XAxis
|
||||||
YAxisList []chart.YAxis
|
YAxisOptions []*YAxisOption
|
||||||
Series []Series
|
Series []Series
|
||||||
Title Title
|
Title Title
|
||||||
Legend Legend
|
Legend Legend
|
||||||
|
|
@ -72,7 +72,7 @@ func (o *Options) validate() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, item := range o.Series {
|
for _, item := range o.Series {
|
||||||
if len(item.Data) != xAxisCount {
|
if item.Type != SeriesPie && len(item.Data) != xAxisCount {
|
||||||
return errors.New("series and xAxis is not matched")
|
return errors.New("series and xAxis is not matched")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -109,18 +109,6 @@ func New(opt Options) (Graph, error) {
|
||||||
if height <= 0 {
|
if height <= 0 {
|
||||||
height = DefaultChartHeight
|
height = DefaultChartHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
xAxis, xValues := GetXAxisAndValues(opt.XAxis, tickPosition, opt.Theme)
|
|
||||||
|
|
||||||
legendSize := len(opt.Legend.Data)
|
|
||||||
for index, item := range opt.Series {
|
|
||||||
if len(item.XValues) == 0 {
|
|
||||||
opt.Series[index].XValues = xValues
|
|
||||||
}
|
|
||||||
if index < legendSize && opt.Series[index].Name == "" {
|
|
||||||
opt.Series[index].Name = opt.Legend.Data[index]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if opt.Series[0].Type == SeriesPie {
|
if opt.Series[0].Type == SeriesPie {
|
||||||
values := make(chart.Values, len(opt.Series))
|
values := make(chart.Values, len(opt.Series))
|
||||||
for index, item := range opt.Series {
|
for index, item := range opt.Series {
|
||||||
|
|
@ -142,21 +130,49 @@ func New(opt Options) (Graph, error) {
|
||||||
return g, nil
|
return g, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
xAxis, xValues := GetXAxisAndValues(opt.XAxis, tickPosition, opt.Theme)
|
||||||
|
|
||||||
|
legendSize := len(opt.Legend.Data)
|
||||||
|
for index, item := range opt.Series {
|
||||||
|
if len(item.XValues) == 0 {
|
||||||
|
opt.Series[index].XValues = xValues
|
||||||
|
}
|
||||||
|
if index < legendSize && opt.Series[index].Name == "" {
|
||||||
|
opt.Series[index].Name = opt.Legend.Data[index]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var yAxisOption *YAxisOption
|
||||||
|
if len(opt.YAxisOptions) != 0 {
|
||||||
|
yAxisOption = opt.YAxisOptions[0]
|
||||||
|
}
|
||||||
|
var secondaryYAxisOption *YAxisOption
|
||||||
|
if len(opt.YAxisOptions) > 1 {
|
||||||
|
secondaryYAxisOption = opt.YAxisOptions[1]
|
||||||
|
}
|
||||||
|
|
||||||
g := &chart.Chart{
|
g := &chart.Chart{
|
||||||
|
ColorPalette: &ThemeColorPalette{
|
||||||
|
Theme: opt.Theme,
|
||||||
|
},
|
||||||
Title: opt.Title.Text,
|
Title: opt.Title.Text,
|
||||||
TitleStyle: opt.Title.Style,
|
TitleStyle: opt.Title.Style,
|
||||||
Width: width,
|
Width: width,
|
||||||
Height: height,
|
Height: height,
|
||||||
XAxis: xAxis,
|
XAxis: xAxis,
|
||||||
YAxis: GetYAxis(opt.Theme),
|
YAxis: GetYAxis(opt.Theme, yAxisOption),
|
||||||
YAxisSecondary: GetSecondaryYAxis(opt.Theme),
|
YAxisSecondary: GetSecondaryYAxis(opt.Theme, secondaryYAxisOption),
|
||||||
Series: GetSeries(opt.Series, tickPosition, opt.Theme),
|
Series: GetSeries(opt.Series, tickPosition, opt.Theme),
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置secondary的样式
|
// 设置secondary的样式
|
||||||
if legendSize != 0 {
|
if legendSize != 0 {
|
||||||
g.Elements = []chart.Renderable{
|
g.Elements = []chart.Renderable{
|
||||||
DefaultLegend(g),
|
LegendCustomize(g, LegendOption{
|
||||||
|
Theme: opt.Theme,
|
||||||
|
TextPosition: LegendTextPositionRight,
|
||||||
|
IconDraw: DefaultLegendIconDraw,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return g, nil
|
return g, nil
|
||||||
|
|
|
||||||
95
echarts.go
95
echarts.go
|
|
@ -27,6 +27,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart/v2"
|
"github.com/wcharczuk/go-chart/v2"
|
||||||
)
|
)
|
||||||
|
|
@ -36,6 +37,7 @@ type EChartStyle struct {
|
||||||
}
|
}
|
||||||
type ECharsSeriesData struct {
|
type ECharsSeriesData struct {
|
||||||
Value float64 `json:"value"`
|
Value float64 `json:"value"`
|
||||||
|
Name string `json:"name"`
|
||||||
ItemStyle EChartStyle `json:"itemStyle"`
|
ItemStyle EChartStyle `json:"itemStyle"`
|
||||||
}
|
}
|
||||||
type _ECharsSeriesData ECharsSeriesData
|
type _ECharsSeriesData ECharsSeriesData
|
||||||
|
|
@ -58,6 +60,7 @@ func (es *ECharsSeriesData) UnmarshalJSON(data []byte) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
es.Name = v.Name
|
||||||
es.Value = v.Value
|
es.Value = v.Value
|
||||||
es.ItemStyle = v.ItemStyle
|
es.ItemStyle = v.ItemStyle
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -86,6 +89,7 @@ func (ey *EChartsYAxis) UnmarshalJSON(data []byte) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ECharsOptions struct {
|
type ECharsOptions struct {
|
||||||
|
Theme string `json:"theme"`
|
||||||
Title struct {
|
Title struct {
|
||||||
Text string `json:"text"`
|
Text string `json:"text"`
|
||||||
TextStyle struct {
|
TextStyle struct {
|
||||||
|
|
@ -104,30 +108,34 @@ type ECharsOptions struct {
|
||||||
Data []string `json:"data"`
|
Data []string `json:"data"`
|
||||||
} `json:"legend"`
|
} `json:"legend"`
|
||||||
Series []struct {
|
Series []struct {
|
||||||
Data []ECharsSeriesData `json:"data"`
|
Data []ECharsSeriesData `json:"data"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
|
YAxisIndex int `json:"yAxisIndex"`
|
||||||
} `json:"series"`
|
} `json:"series"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ECharsOptions) ToOptions() Options {
|
func convertEChartsSeries(e *ECharsOptions) ([]Series, chart.TickPosition) {
|
||||||
o := Options{}
|
|
||||||
o.Title = Title{
|
|
||||||
Text: e.Title.Text,
|
|
||||||
}
|
|
||||||
|
|
||||||
o.XAxis = XAxis{
|
|
||||||
Type: e.XAxis.Type,
|
|
||||||
Data: e.XAxis.Data,
|
|
||||||
SplitNumber: e.XAxis.SplitNumber,
|
|
||||||
}
|
|
||||||
|
|
||||||
o.Legend = Legend{
|
|
||||||
Data: e.Legend.Data,
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO 生成yAxis
|
|
||||||
|
|
||||||
tickPosition := chart.TickPositionUnset
|
tickPosition := chart.TickPositionUnset
|
||||||
|
|
||||||
|
if len(e.Series) == 0 {
|
||||||
|
return nil, tickPosition
|
||||||
|
}
|
||||||
|
seriesType := e.Series[0].Type
|
||||||
|
if seriesType == SeriesPie {
|
||||||
|
series := make([]Series, len(e.Series[0].Data))
|
||||||
|
for index, item := range e.Series[0].Data {
|
||||||
|
series[index] = Series{
|
||||||
|
Data: []SeriesData{
|
||||||
|
{
|
||||||
|
Value: item.Value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Type: seriesType,
|
||||||
|
Name: item.Name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return series, tickPosition
|
||||||
|
}
|
||||||
series := make([]Series, len(e.Series))
|
series := make([]Series, len(e.Series))
|
||||||
for index, item := range e.Series {
|
for index, item := range e.Series {
|
||||||
// bar默认tick居中
|
// bar默认tick居中
|
||||||
|
|
@ -146,11 +154,54 @@ func (e *ECharsOptions) ToOptions() Options {
|
||||||
}
|
}
|
||||||
data[j] = sd
|
data[j] = sd
|
||||||
}
|
}
|
||||||
|
yAxisType := chart.YAxisPrimary
|
||||||
|
if item.YAxisIndex != 0 {
|
||||||
|
yAxisType = chart.YAxisSecondary
|
||||||
|
}
|
||||||
series[index] = Series{
|
series[index] = Series{
|
||||||
Data: data,
|
YAxis: yAxisType,
|
||||||
Type: item.Type,
|
Data: data,
|
||||||
|
Type: item.Type,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return series, tickPosition
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ECharsOptions) ToOptions() Options {
|
||||||
|
o := Options{
|
||||||
|
Theme: e.Theme,
|
||||||
|
}
|
||||||
|
o.Title = Title{
|
||||||
|
Text: e.Title.Text,
|
||||||
|
}
|
||||||
|
|
||||||
|
o.XAxis = XAxis{
|
||||||
|
Type: e.XAxis.Type,
|
||||||
|
Data: e.XAxis.Data,
|
||||||
|
SplitNumber: e.XAxis.SplitNumber,
|
||||||
|
}
|
||||||
|
|
||||||
|
o.Legend = Legend{
|
||||||
|
Data: e.Legend.Data,
|
||||||
|
}
|
||||||
|
if len(e.YAxis.Data) != 0 {
|
||||||
|
yAxisOptions := make([]*YAxisOption, len(e.YAxis.Data))
|
||||||
|
for index, item := range e.YAxis.Data {
|
||||||
|
opt := &YAxisOption{}
|
||||||
|
template := item.AxisLabel.Formatter
|
||||||
|
if template != "" {
|
||||||
|
opt.Formater = func(v interface{}) string {
|
||||||
|
str := defaultFloatFormater(v)
|
||||||
|
return strings.ReplaceAll(template, "{value}", str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
yAxisOptions[index] = opt
|
||||||
|
}
|
||||||
|
o.YAxisOptions = yAxisOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
series, tickPosition := convertEChartsSeries(e)
|
||||||
|
|
||||||
o.Series = series
|
o.Series = series
|
||||||
|
|
||||||
if e.XAxis.BoundaryGap == nil || *e.XAxis.BoundaryGap {
|
if e.XAxis.BoundaryGap == nil || *e.XAxis.BoundaryGap {
|
||||||
|
|
|
||||||
337
examples/main.go
Normal file
337
examples/main.go
Normal file
|
|
@ -0,0 +1,337 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
charts "github.com/vicanso/echarts"
|
||||||
|
)
|
||||||
|
|
||||||
|
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>
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
width: 800px;
|
||||||
|
margin: auto auto 20px auto;
|
||||||
|
max-height: 300px;
|
||||||
|
overflow: auto;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
svg{
|
||||||
|
margin: auto auto 50px auto;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<title>go-echarts</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{{body}}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
||||||
|
|
||||||
|
var chartOptions = []map[string]string{
|
||||||
|
{
|
||||||
|
"title": "折线图",
|
||||||
|
"option": `{
|
||||||
|
"xAxis": {
|
||||||
|
"type": "category",
|
||||||
|
"data": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
|
||||||
|
},
|
||||||
|
"series": [
|
||||||
|
{
|
||||||
|
"data": [150, 230, 224, 218, 135, 147, 260],
|
||||||
|
"type": "line"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "多折线图",
|
||||||
|
"option": `{
|
||||||
|
"title": {
|
||||||
|
"text": "Multi Line"
|
||||||
|
},
|
||||||
|
"legend": {
|
||||||
|
"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, 200, 150, 80, 70, 110, 130],
|
||||||
|
"type": "bar"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"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",
|
||||||
|
"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": "降雨量"
|
||||||
|
},
|
||||||
|
"legend": {
|
||||||
|
"data": ["GZ", "SH"]
|
||||||
|
},
|
||||||
|
"xAxis": {
|
||||||
|
"type": "category",
|
||||||
|
"splitNumber": 12,
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func render(theme string) ([]byte, error) {
|
||||||
|
data := bytes.Buffer{}
|
||||||
|
for _, m := range chartOptions {
|
||||||
|
if m["title"] != "折柱混合" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
chartHTML := []byte(`<div>
|
||||||
|
<h1>{{title}}</h1>
|
||||||
|
<pre>{{option}}</pre>
|
||||||
|
{{svg}}
|
||||||
|
</div>`)
|
||||||
|
o, err := charts.ParseECharsOptions(m["option"])
|
||||||
|
o.Theme = theme
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
buf = bytes.ReplaceAll(buf, []byte("{{title}}"), []byte(m["title"]))
|
||||||
|
buf = bytes.ReplaceAll(buf, []byte("{{option}}"), []byte(m["option"]))
|
||||||
|
data.Write(buf)
|
||||||
|
}
|
||||||
|
return data.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func indexHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
buf, err := render(r.URL.Query().Get("theme"))
|
||||||
|
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)
|
||||||
|
}
|
||||||
11
legend.go
11
legend.go
|
|
@ -65,22 +65,15 @@ func DefaultLegendIconDraw(r chart.Renderer, opt LegendIconDrawOption) {
|
||||||
r.MoveTo(opt.Box.Left, ly)
|
r.MoveTo(opt.Box.Left, ly)
|
||||||
r.LineTo(opt.Box.Right, ly)
|
r.LineTo(opt.Box.Right, ly)
|
||||||
r.Stroke()
|
r.Stroke()
|
||||||
r.SetFillColor(chart.ColorWhite)
|
r.SetFillColor(getBackgroundColor(opt.Theme))
|
||||||
r.Circle(5, (opt.Box.Left+opt.Box.Right)/2, ly)
|
r.Circle(5, (opt.Box.Left+opt.Box.Right)/2, ly)
|
||||||
r.FillStroke()
|
r.FillStroke()
|
||||||
}
|
}
|
||||||
|
|
||||||
func DefaultLegend(c *chart.Chart) chart.Renderable {
|
|
||||||
return LegendCustomize(c, LegendOption{
|
|
||||||
TextPosition: LegendTextPositionRight,
|
|
||||||
IconDraw: DefaultLegendIconDraw,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func LegendCustomize(c *chart.Chart, opt LegendOption) chart.Renderable {
|
func LegendCustomize(c *chart.Chart, opt LegendOption) chart.Renderable {
|
||||||
return func(r chart.Renderer, cb chart.Box, chartDefaults chart.Style) {
|
return func(r chart.Renderer, cb chart.Box, chartDefaults chart.Style) {
|
||||||
legendDefaults := chart.Style{
|
legendDefaults := chart.Style{
|
||||||
FontColor: chart.DefaultTextColor,
|
FontColor: getTextColor(opt.Theme),
|
||||||
FontSize: 8.0,
|
FontSize: 8.0,
|
||||||
StrokeColor: chart.DefaultAxisColor,
|
StrokeColor: chart.DefaultAxisColor,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ type Series struct {
|
||||||
Name string
|
Name string
|
||||||
Data []SeriesData
|
Data []SeriesData
|
||||||
XValues []float64
|
XValues []float64
|
||||||
|
YAxis chart.YAxisType
|
||||||
}
|
}
|
||||||
|
|
||||||
const lineStrokeWidth = 2
|
const lineStrokeWidth = 2
|
||||||
|
|
@ -102,6 +103,7 @@ func GetSeries(series []Series, tickPosition chart.TickPosition, theme string) [
|
||||||
Style: style,
|
Style: style,
|
||||||
YValues: yValues,
|
YValues: yValues,
|
||||||
TickPosition: tickPosition,
|
TickPosition: tickPosition,
|
||||||
|
YAxis: item.YAxis,
|
||||||
}
|
}
|
||||||
// TODO 判断类型
|
// TODO 判断类型
|
||||||
switch item.Type {
|
switch item.Type {
|
||||||
|
|
|
||||||
73
theme.go
73
theme.go
|
|
@ -37,6 +37,50 @@ var AxisColorLight = drawing.Color{
|
||||||
B: 121,
|
B: 121,
|
||||||
A: 255,
|
A: 255,
|
||||||
}
|
}
|
||||||
|
var AxisColorDark = drawing.Color{
|
||||||
|
R: 185,
|
||||||
|
G: 184,
|
||||||
|
B: 206,
|
||||||
|
A: 255,
|
||||||
|
}
|
||||||
|
|
||||||
|
var BackgroundColorDark = drawing.Color{
|
||||||
|
R: 16,
|
||||||
|
G: 12,
|
||||||
|
B: 42,
|
||||||
|
A: 255,
|
||||||
|
}
|
||||||
|
|
||||||
|
var TextColorDark = drawing.Color{
|
||||||
|
R: 204,
|
||||||
|
G: 204,
|
||||||
|
B: 204,
|
||||||
|
A: 255,
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAxisColor(theme string) drawing.Color {
|
||||||
|
if theme == ThemeDark {
|
||||||
|
return AxisColorDark
|
||||||
|
}
|
||||||
|
return AxisColorLight
|
||||||
|
}
|
||||||
|
|
||||||
|
func getGridColor(theme string) drawing.Color {
|
||||||
|
if theme == ThemeDark {
|
||||||
|
return drawing.Color{
|
||||||
|
R: 72,
|
||||||
|
G: 71,
|
||||||
|
B: 83,
|
||||||
|
A: 255,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return drawing.Color{
|
||||||
|
R: 224,
|
||||||
|
G: 230,
|
||||||
|
B: 241,
|
||||||
|
A: 255,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var SeriesColorsLight = []drawing.Color{
|
var SeriesColorsLight = []drawing.Color{
|
||||||
{
|
{
|
||||||
|
|
@ -71,12 +115,26 @@ var SeriesColorsLight = []drawing.Color{
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getBackgroundColor(theme string) drawing.Color {
|
||||||
|
if theme == ThemeDark {
|
||||||
|
return BackgroundColorDark
|
||||||
|
}
|
||||||
|
return chart.DefaultBackgroundColor
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTextColor(theme string) drawing.Color {
|
||||||
|
if theme == ThemeDark {
|
||||||
|
return TextColorDark
|
||||||
|
}
|
||||||
|
return chart.DefaultTextColor
|
||||||
|
}
|
||||||
|
|
||||||
type ThemeColorPalette struct {
|
type ThemeColorPalette struct {
|
||||||
Theme string
|
Theme string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tp ThemeColorPalette) BackgroundColor() drawing.Color {
|
func (tp ThemeColorPalette) BackgroundColor() drawing.Color {
|
||||||
return chart.DefaultBackgroundColor
|
return getBackgroundColor(tp.Theme)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tp ThemeColorPalette) BackgroundStrokeColor() drawing.Color {
|
func (tp ThemeColorPalette) BackgroundStrokeColor() drawing.Color {
|
||||||
|
|
@ -84,6 +142,9 @@ func (tp ThemeColorPalette) BackgroundStrokeColor() drawing.Color {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tp ThemeColorPalette) CanvasColor() drawing.Color {
|
func (tp ThemeColorPalette) CanvasColor() drawing.Color {
|
||||||
|
if tp.Theme == ThemeDark {
|
||||||
|
return BackgroundColorDark
|
||||||
|
}
|
||||||
return chart.DefaultCanvasColor
|
return chart.DefaultCanvasColor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -92,11 +153,14 @@ func (tp ThemeColorPalette) CanvasStrokeColor() drawing.Color {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tp ThemeColorPalette) AxisStrokeColor() drawing.Color {
|
func (tp ThemeColorPalette) AxisStrokeColor() drawing.Color {
|
||||||
|
if tp.Theme == ThemeDark {
|
||||||
|
return BackgroundColorDark
|
||||||
|
}
|
||||||
return chart.DefaultAxisColor
|
return chart.DefaultAxisColor
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tp ThemeColorPalette) TextColor() drawing.Color {
|
func (tp ThemeColorPalette) TextColor() drawing.Color {
|
||||||
return chart.DefaultTextColor
|
return getTextColor(tp.Theme)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tp ThemeColorPalette) GetSeriesColor(index int) drawing.Color {
|
func (tp ThemeColorPalette) GetSeriesColor(index int) drawing.Color {
|
||||||
|
|
@ -104,9 +168,6 @@ func (tp ThemeColorPalette) GetSeriesColor(index int) drawing.Color {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSeriesColor(theme string, index int) drawing.Color {
|
func getSeriesColor(theme string, index int) drawing.Color {
|
||||||
// TODO
|
|
||||||
if theme == ThemeDark {
|
|
||||||
}
|
|
||||||
return SeriesColorsLight[index%len(SeriesColorsLight)]
|
return SeriesColorsLight[index%len(SeriesColorsLight)]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -117,6 +178,6 @@ func parseColor(color string) drawing.Color {
|
||||||
if strings.HasPrefix(color, "#") {
|
if strings.HasPrefix(color, "#") {
|
||||||
return drawing.ColorFromHex(color[1:])
|
return drawing.ColorFromHex(color[1:])
|
||||||
}
|
}
|
||||||
// TODO
|
// TODO rgba
|
||||||
return drawing.Color{}
|
return drawing.Color{}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue