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 (
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/wcharczuk/go-chart/v2"
|
||||
"github.com/wcharczuk/go-chart/v2/drawing"
|
||||
)
|
||||
|
||||
type (
|
||||
|
|
@ -41,6 +40,10 @@ type (
|
|||
}
|
||||
)
|
||||
|
||||
type YAxisOption struct {
|
||||
Formater chart.ValueFormatter
|
||||
}
|
||||
|
||||
const axisStrokeWidth = 1
|
||||
|
||||
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{
|
||||
Ticks: ticks,
|
||||
TickPosition: tickPosition,
|
||||
Style: chart.Style{
|
||||
FontColor: AxisColorLight,
|
||||
StrokeColor: AxisColorLight,
|
||||
FontColor: getAxisColor(theme),
|
||||
StrokeColor: getAxisColor(theme),
|
||||
StrokeWidth: axisStrokeWidth,
|
||||
},
|
||||
}, 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
|
||||
if theme == ThemeDark {
|
||||
return chart.YAxis{}
|
||||
|
|
@ -113,41 +121,46 @@ func GetSecondaryYAxis(theme string) chart.YAxis {
|
|||
// B: 241,
|
||||
// A: 255,
|
||||
// }
|
||||
return chart.YAxis{
|
||||
ValueFormatter: func(v interface{}) string {
|
||||
value, ok := v.(float64)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return humanize.Commaf(value)
|
||||
},
|
||||
AxisType: chart.YAxisPrimary,
|
||||
GridMajorStyle: chart.Hidden(),
|
||||
GridMinorStyle: chart.Hidden(),
|
||||
Style: chart.Hidden(),
|
||||
formater := defaultFloatFormater
|
||||
if option != nil {
|
||||
if option.Formater != nil {
|
||||
formater = option.Formater
|
||||
}
|
||||
}
|
||||
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 {
|
||||
// TODO
|
||||
if theme == ThemeDark {
|
||||
return chart.YAxis{}
|
||||
}
|
||||
strokeColor := drawing.Color{
|
||||
R: 224,
|
||||
G: 230,
|
||||
B: 241,
|
||||
A: 255,
|
||||
func GetYAxis(theme string, option *YAxisOption) chart.YAxis {
|
||||
strokeColor := getGridColor(theme)
|
||||
formater := defaultFloatFormater
|
||||
if option != nil {
|
||||
if option.Formater != nil {
|
||||
formater = option.Formater
|
||||
}
|
||||
}
|
||||
return chart.YAxis{
|
||||
ValueFormatter: func(v interface{}) string {
|
||||
value, ok := v.(float64)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return humanize.Commaf(value)
|
||||
},
|
||||
AxisType: chart.YAxisSecondary,
|
||||
ValueFormatter: formater,
|
||||
AxisType: chart.YAxisSecondary,
|
||||
GridMajorStyle: chart.Style{
|
||||
StrokeColor: strokeColor,
|
||||
StrokeWidth: axisStrokeWidth,
|
||||
|
|
@ -157,7 +170,7 @@ func GetYAxis(theme string) chart.YAxis {
|
|||
StrokeWidth: axisStrokeWidth,
|
||||
},
|
||||
Style: chart.Style{
|
||||
FontColor: AxisColorLight,
|
||||
FontColor: getAxisColor(theme),
|
||||
// alpha 0,隐藏
|
||||
StrokeColor: hiddenColor,
|
||||
StrokeWidth: axisStrokeWidth,
|
||||
|
|
|
|||
50
charts.go
50
charts.go
|
|
@ -53,7 +53,7 @@ type (
|
|||
Height int
|
||||
Theme string
|
||||
XAxis XAxis
|
||||
YAxisList []chart.YAxis
|
||||
YAxisOptions []*YAxisOption
|
||||
Series []Series
|
||||
Title Title
|
||||
Legend Legend
|
||||
|
|
@ -72,7 +72,7 @@ func (o *Options) validate() error {
|
|||
}
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
|
@ -109,18 +109,6 @@ func New(opt Options) (Graph, error) {
|
|||
if height <= 0 {
|
||||
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 {
|
||||
values := make(chart.Values, len(opt.Series))
|
||||
for index, item := range opt.Series {
|
||||
|
|
@ -142,21 +130,49 @@ func New(opt Options) (Graph, error) {
|
|||
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{
|
||||
ColorPalette: &ThemeColorPalette{
|
||||
Theme: opt.Theme,
|
||||
},
|
||||
Title: opt.Title.Text,
|
||||
TitleStyle: opt.Title.Style,
|
||||
Width: width,
|
||||
Height: height,
|
||||
XAxis: xAxis,
|
||||
YAxis: GetYAxis(opt.Theme),
|
||||
YAxisSecondary: GetSecondaryYAxis(opt.Theme),
|
||||
YAxis: GetYAxis(opt.Theme, yAxisOption),
|
||||
YAxisSecondary: GetSecondaryYAxis(opt.Theme, secondaryYAxisOption),
|
||||
Series: GetSeries(opt.Series, tickPosition, opt.Theme),
|
||||
}
|
||||
|
||||
// 设置secondary的样式
|
||||
if legendSize != 0 {
|
||||
g.Elements = []chart.Renderable{
|
||||
DefaultLegend(g),
|
||||
LegendCustomize(g, LegendOption{
|
||||
Theme: opt.Theme,
|
||||
TextPosition: LegendTextPositionRight,
|
||||
IconDraw: DefaultLegendIconDraw,
|
||||
}),
|
||||
}
|
||||
}
|
||||
return g, nil
|
||||
|
|
|
|||
95
echarts.go
95
echarts.go
|
|
@ -27,6 +27,7 @@ import (
|
|||
"encoding/json"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/wcharczuk/go-chart/v2"
|
||||
)
|
||||
|
|
@ -36,6 +37,7 @@ type EChartStyle struct {
|
|||
}
|
||||
type ECharsSeriesData struct {
|
||||
Value float64 `json:"value"`
|
||||
Name string `json:"name"`
|
||||
ItemStyle EChartStyle `json:"itemStyle"`
|
||||
}
|
||||
type _ECharsSeriesData ECharsSeriesData
|
||||
|
|
@ -58,6 +60,7 @@ func (es *ECharsSeriesData) UnmarshalJSON(data []byte) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
es.Name = v.Name
|
||||
es.Value = v.Value
|
||||
es.ItemStyle = v.ItemStyle
|
||||
return nil
|
||||
|
|
@ -86,6 +89,7 @@ func (ey *EChartsYAxis) UnmarshalJSON(data []byte) error {
|
|||
}
|
||||
|
||||
type ECharsOptions struct {
|
||||
Theme string `json:"theme"`
|
||||
Title struct {
|
||||
Text string `json:"text"`
|
||||
TextStyle struct {
|
||||
|
|
@ -104,30 +108,34 @@ type ECharsOptions struct {
|
|||
Data []string `json:"data"`
|
||||
} `json:"legend"`
|
||||
Series []struct {
|
||||
Data []ECharsSeriesData `json:"data"`
|
||||
Type string `json:"type"`
|
||||
Data []ECharsSeriesData `json:"data"`
|
||||
Type string `json:"type"`
|
||||
YAxisIndex int `json:"yAxisIndex"`
|
||||
} `json:"series"`
|
||||
}
|
||||
|
||||
func (e *ECharsOptions) ToOptions() Options {
|
||||
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
|
||||
|
||||
func convertEChartsSeries(e *ECharsOptions) ([]Series, chart.TickPosition) {
|
||||
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))
|
||||
for index, item := range e.Series {
|
||||
// bar默认tick居中
|
||||
|
|
@ -146,11 +154,54 @@ func (e *ECharsOptions) ToOptions() Options {
|
|||
}
|
||||
data[j] = sd
|
||||
}
|
||||
yAxisType := chart.YAxisPrimary
|
||||
if item.YAxisIndex != 0 {
|
||||
yAxisType = chart.YAxisSecondary
|
||||
}
|
||||
series[index] = Series{
|
||||
Data: data,
|
||||
Type: item.Type,
|
||||
YAxis: yAxisType,
|
||||
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
|
||||
|
||||
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.LineTo(opt.Box.Right, ly)
|
||||
r.Stroke()
|
||||
r.SetFillColor(chart.ColorWhite)
|
||||
r.SetFillColor(getBackgroundColor(opt.Theme))
|
||||
r.Circle(5, (opt.Box.Left+opt.Box.Right)/2, ly)
|
||||
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 {
|
||||
return func(r chart.Renderer, cb chart.Box, chartDefaults chart.Style) {
|
||||
legendDefaults := chart.Style{
|
||||
FontColor: chart.DefaultTextColor,
|
||||
FontColor: getTextColor(opt.Theme),
|
||||
FontSize: 8.0,
|
||||
StrokeColor: chart.DefaultAxisColor,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ type Series struct {
|
|||
Name string
|
||||
Data []SeriesData
|
||||
XValues []float64
|
||||
YAxis chart.YAxisType
|
||||
}
|
||||
|
||||
const lineStrokeWidth = 2
|
||||
|
|
@ -102,6 +103,7 @@ func GetSeries(series []Series, tickPosition chart.TickPosition, theme string) [
|
|||
Style: style,
|
||||
YValues: yValues,
|
||||
TickPosition: tickPosition,
|
||||
YAxis: item.YAxis,
|
||||
}
|
||||
// TODO 判断类型
|
||||
switch item.Type {
|
||||
|
|
|
|||
73
theme.go
73
theme.go
|
|
@ -37,6 +37,50 @@ var AxisColorLight = drawing.Color{
|
|||
B: 121,
|
||||
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{
|
||||
{
|
||||
|
|
@ -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 {
|
||||
Theme string
|
||||
}
|
||||
|
||||
func (tp ThemeColorPalette) BackgroundColor() drawing.Color {
|
||||
return chart.DefaultBackgroundColor
|
||||
return getBackgroundColor(tp.Theme)
|
||||
}
|
||||
|
||||
func (tp ThemeColorPalette) BackgroundStrokeColor() drawing.Color {
|
||||
|
|
@ -84,6 +142,9 @@ func (tp ThemeColorPalette) BackgroundStrokeColor() drawing.Color {
|
|||
}
|
||||
|
||||
func (tp ThemeColorPalette) CanvasColor() drawing.Color {
|
||||
if tp.Theme == ThemeDark {
|
||||
return BackgroundColorDark
|
||||
}
|
||||
return chart.DefaultCanvasColor
|
||||
}
|
||||
|
||||
|
|
@ -92,11 +153,14 @@ func (tp ThemeColorPalette) CanvasStrokeColor() drawing.Color {
|
|||
}
|
||||
|
||||
func (tp ThemeColorPalette) AxisStrokeColor() drawing.Color {
|
||||
if tp.Theme == ThemeDark {
|
||||
return BackgroundColorDark
|
||||
}
|
||||
return chart.DefaultAxisColor
|
||||
}
|
||||
|
||||
func (tp ThemeColorPalette) TextColor() drawing.Color {
|
||||
return chart.DefaultTextColor
|
||||
return getTextColor(tp.Theme)
|
||||
}
|
||||
|
||||
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 {
|
||||
// TODO
|
||||
if theme == ThemeDark {
|
||||
}
|
||||
return SeriesColorsLight[index%len(SeriesColorsLight)]
|
||||
}
|
||||
|
||||
|
|
@ -117,6 +178,6 @@ func parseColor(color string) drawing.Color {
|
|||
if strings.HasPrefix(color, "#") {
|
||||
return drawing.ColorFromHex(color[1:])
|
||||
}
|
||||
// TODO
|
||||
// TODO rgba
|
||||
return drawing.Color{}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue