// 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 ( "bytes" "encoding/json" "fmt" "regexp" "strconv" "github.com/wcharczuk/go-chart/v2" ) func convertToArray(data []byte) []byte { data = bytes.TrimSpace(data) if len(data) == 0 { return nil } if data[0] != '[' { data = []byte("[" + string(data) + "]") } return data } type EChartsPosition string func (p *EChartsPosition) UnmarshalJSON(data []byte) error { if len(data) == 0 { return nil } if regexp.MustCompile(`^\d+`).Match(data) { data = []byte(fmt.Sprintf(`"%s"`, string(data))) } s := (*string)(p) return json.Unmarshal(data, s) } type EChartStyle struct { Color string `json:"color"` } func (es *EChartStyle) ToStyle() chart.Style { color := parseColor(es.Color) return chart.Style{ FillColor: color, FontColor: color, StrokeColor: color, } } type EChartsSeriesData struct { Value float64 `json:"value"` Name string `json:"name"` ItemStyle EChartStyle `json:"itemStyle"` } type _EChartsSeriesData EChartsSeriesData func (es *EChartsSeriesData) UnmarshalJSON(data []byte) error { data = bytes.TrimSpace(data) if len(data) == 0 { return nil } if regexp.MustCompile(`^\d+`).Match(data) { v, err := strconv.ParseFloat(string(data), 64) if err != nil { return err } es.Value = v return nil } v := _EChartsSeriesData{} err := json.Unmarshal(data, &v) if err != nil { return err } es.Name = v.Name es.Value = v.Value es.ItemStyle = v.ItemStyle return nil } type EChartsXAxisData struct { BoundaryGap *bool `json:"boundaryGap"` SplitNumber int `json:"splitNumber"` Data []string `json:"data"` } type EChartsXAxis struct { Data []EChartsXAxisData } func (ex *EChartsXAxis) UnmarshalJSON(data []byte) error { data = convertToArray(data) if len(data) == 0 { return nil } return json.Unmarshal(data, &ex.Data) } type EChartsAxisLabel struct { Formatter string `json:"formatter"` } type EChartsYAxisData struct { Min *float64 `json:"min"` Max *float64 `json:"max"` AxisLabel EChartsAxisLabel `json:"axisLabel"` AxisLine struct { LineStyle struct { Color string } } } type EChartsYAxis struct { Data []EChartsYAxisData `json:"data"` } func (ey *EChartsYAxis) UnmarshalJSON(data []byte) error { data = convertToArray(data) if len(data) == 0 { return nil } return json.Unmarshal(data, &ey.Data) } type EChartsPadding struct { Box chart.Box } func (eb *EChartsPadding) UnmarshalJSON(data []byte) error { data = convertToArray(data) if len(data) == 0 { return nil } arr := make([]int, 0) err := json.Unmarshal(data, &arr) if err != nil { return err } if len(arr) == 0 { return nil } switch len(arr) { case 1: eb.Box = chart.Box{ Left: arr[0], Top: arr[0], Bottom: arr[0], Right: arr[0], } case 2: eb.Box = chart.Box{ Top: arr[0], Bottom: arr[0], Left: arr[1], Right: arr[1], } default: result := make([]int, 4) copy(result, arr) if len(arr) == 3 { result[3] = result[1] } // 上右下左 eb.Box = chart.Box{ Top: result[0], Right: result[1], Bottom: result[2], Left: result[3], } } return nil } type EChartsLabelOption struct { Show bool `json:"show"` Distance int `json:"distance"` Color string `json:"color"` } type EChartsLegend struct { Show *bool `json:"show"` Data []string `json:"data"` Align string `json:"align"` Orient string `json:"orient"` Padding EChartsPadding `json:"padding"` Left EChartsPosition `json:"left"` Top EChartsPosition `json:"top"` TextStyle EChartsTextStyle `json:"textStyle"` } type EChartsMarkPoint struct { SymbolSize int `json:"symbolSize"` Data []struct { Type string `json:"type"` } `json:"data"` } func (emp *EChartsMarkPoint) ToSeriesMarkPoint() SeriesMarkPoint { data := make([]SeriesMarkData, len(emp.Data)) for index, item := range emp.Data { data[index] = SeriesMarkData{ Type: item.Type, } } return SeriesMarkPoint{ Data: data, SymbolSize: emp.SymbolSize, } } type EChartsMarkLine struct { Data []struct { Type string `json:"type"` } `json:"data"` } func (eml *EChartsMarkLine) ToSeriesMarkLine() SeriesMarkLine { data := make([]SeriesMarkData, len(eml.Data)) for index, item := range eml.Data { data[index] = SeriesMarkData{ Type: item.Type, } } return SeriesMarkLine{ Data: data, } } type EChartsSeries struct { Data []EChartsSeriesData `json:"data"` Name string `json:"name"` Type string `json:"type"` Radius string `json:"radius"` YAxisIndex int `json:"yAxisIndex"` ItemStyle EChartStyle `json:"itemStyle"` // label的配置 Label EChartsLabelOption `json:"label"` MarkPoint EChartsMarkPoint `json:"markPoint"` MarkLine EChartsMarkLine `json:"markLine"` } type EChartsSeriesList []EChartsSeries func (esList EChartsSeriesList) ToSeriesList() SeriesList { seriesList := make(SeriesList, len(esList)) for index, item := range esList { data := make([]SeriesData, len(item.Data)) for j, dataItem := range item.Data { data[j] = SeriesData{ Value: dataItem.Value, Style: dataItem.ItemStyle.ToStyle(), } } seriesList[index] = Series{ Type: item.Type, Data: data, YAxisIndex: item.YAxisIndex, Style: item.ItemStyle.ToStyle(), Label: SeriesLabel{ Color: parseColor(item.Label.Color), Show: item.Label.Show, Distance: item.Label.Distance, }, Name: item.Name, Radius: item.Radius, MarkPoint: item.MarkPoint.ToSeriesMarkPoint(), MarkLine: item.MarkLine.ToSeriesMarkLine(), } } return seriesList } type EChartsTextStyle struct { Color string `json:"color"` FontFamily string `json:"fontFamily"` FontSize float64 `json:"fontSize"` } func (et *EChartsTextStyle) ToStyle() chart.Style { return chart.Style{ FontSize: et.FontSize, FontColor: parseColor(et.Color), } } type EChartsOption struct { Type string `json:"type"` Theme string `json:"theme"` FontFamily string `json:"fontFamily"` Padding EChartsPadding `json:"padding"` Box chart.Box `json:"box"` Width int `json:"width"` Height int `json:"height"` Title struct { Text string `json:"text"` Subtext string `json:"subtext"` Left EChartsPosition `json:"left"` Top EChartsPosition `json:"top"` TextStyle EChartsTextStyle `json:"textStyle"` SubtextStyle EChartsTextStyle `json:"subtextStyle"` } `json:"title"` XAxis EChartsXAxis `json:"xAxis"` YAxis EChartsYAxis `json:"yAxis"` Legend EChartsLegend `json:"legend"` Series EChartsSeriesList `json:"series"` Children []EChartsOption `json:"children"` } func (eo *EChartsOption) ToOption() ChartOption { fontFamily := eo.FontFamily if len(fontFamily) == 0 { fontFamily = eo.Title.TextStyle.FontFamily } o := ChartOption{ Type: eo.Type, FontFamily: fontFamily, Theme: eo.Theme, Title: TitleOption{ Text: eo.Title.Text, Subtext: eo.Title.Subtext, Style: eo.Title.TextStyle.ToStyle(), SubtextStyle: eo.Title.SubtextStyle.ToStyle(), Left: string(eo.Title.Left), Top: string(eo.Title.Top), }, Legend: LegendOption{ Show: eo.Legend.Show, Style: eo.Legend.TextStyle.ToStyle(), Data: eo.Legend.Data, Left: string(eo.Legend.Left), Top: string(eo.Legend.Top), Align: eo.Legend.Align, Orient: eo.Legend.Orient, }, Width: eo.Width, Height: eo.Height, Padding: eo.Padding.Box, Box: eo.Box, SeriesList: eo.Series.ToSeriesList(), } if len(eo.XAxis.Data) != 0 { xAxisData := eo.XAxis.Data[0] o.XAxis = XAxisOption{ BoundaryGap: xAxisData.BoundaryGap, Data: xAxisData.Data, SplitNumber: xAxisData.SplitNumber, } } yAxisOptions := make([]YAxisOption, len(eo.YAxis.Data)) for index, item := range eo.YAxis.Data { yAxisOptions[index] = YAxisOption{ Min: item.Min, Max: item.Max, Formatter: item.AxisLabel.Formatter, Color: parseColor(item.AxisLine.LineStyle.Color), } } if len(eo.Children) != 0 { o.Children = make([]ChartOption, len(eo.Children)) for index, item := range eo.Children { o.Children[index] = item.ToOption() } } return o } func renderEcharts(options, outputType string) ([]byte, error) { o := EChartsOption{} err := json.Unmarshal([]byte(options), &o) if err != nil { return nil, err } opt := o.ToOption() opt.Type = outputType d, err := Render(opt) if err != nil { return nil, err } return d.Bytes() } func RenderEChartsToPNG(options string) ([]byte, error) { return renderEcharts(options, "png") } func RenderEChartsToSVG(options string) ([]byte, error) { return renderEcharts(options, "svg") }