diff --git a/axis.go b/axis.go index 48f92a3..0947111 100644 --- a/axis.go +++ b/axis.go @@ -43,6 +43,8 @@ type ( type YAxisOption struct { Formater chart.ValueFormatter Disabled bool + Min *float64 + Max *float64 } const axisStrokeWidth = 1 @@ -113,14 +115,9 @@ func defaultFloatFormater(v interface{}) string { func GetSecondaryYAxis(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: formater, + yAxis := chart.YAxis{ + Range: &chart.ContinuousRange{}, + ValueFormatter: defaultFloatFormater, AxisType: chart.YAxisSecondary, GridMajorStyle: chart.Style{ StrokeColor: strokeColor, @@ -137,22 +134,35 @@ func GetSecondaryYAxis(theme string, option *YAxisOption) chart.YAxis { StrokeWidth: axisStrokeWidth, }, } + setYAxis(&yAxis, option) + return yAxis +} + +func setYAxis(yAxis *chart.YAxis, option *YAxisOption) { + if option == nil { + return + } + if option.Formater != nil { + yAxis.ValueFormatter = option.Formater + } + if option.Max != nil { + yAxis.Range.SetMax(*option.Max) + } + if option.Min != nil { + yAxis.Range.SetMin(*option.Min) + } } func GetYAxis(theme string, option *YAxisOption) chart.YAxis { - // strokeColor := getGridColor(theme) - formater := defaultFloatFormater disabled := false if option != nil { - if option.Formater != nil { - formater = option.Formater - } disabled = option.Disabled } hidden := chart.Hidden() yAxis := chart.YAxis{ - ValueFormatter: formater, + Range: &chart.ContinuousRange{}, + ValueFormatter: defaultFloatFormater, AxisType: chart.YAxisPrimary, GridMajorStyle: hidden, GridMinorStyle: hidden, @@ -167,5 +177,6 @@ func GetYAxis(theme string, option *YAxisOption) chart.YAxis { yAxis.Range = &HiddenRange{} yAxis.Style.Hidden = true } + setYAxis(&yAxis, option) return yAxis } diff --git a/charts.go b/charts.go index 0ed8ec4..7cabd7f 100644 --- a/charts.go +++ b/charts.go @@ -46,7 +46,9 @@ type ( Style chart.Style } Legend struct { - Data []string + Data []string + Align string + Padding chart.Box } Options struct { Width int @@ -177,6 +179,8 @@ func New(opt Options) (Graph, error) { Theme: opt.Theme, TextPosition: LegendTextPositionRight, IconDraw: DefaultLegendIconDraw, + Align: opt.Legend.Align, + Padding: opt.Legend.Padding, }), } } diff --git a/echarts.go b/echarts.go index 0db130c..d8625af 100644 --- a/echarts.go +++ b/echarts.go @@ -42,6 +42,17 @@ type ECharsSeriesData struct { } type _ECharsSeriesData ECharsSeriesData +func convertToArray(data []byte) []byte { + data = bytes.TrimSpace(data) + if len(data) == 0 { + return nil + } + if data[0] != '[' { + data = []byte("[" + string(data) + "]") + } + return data +} + func (es *ECharsSeriesData) UnmarshalJSON(data []byte) error { data = bytes.TrimSpace(data) if len(data) == 0 { @@ -66,11 +77,57 @@ func (es *ECharsSeriesData) UnmarshalJSON(data []byte) error { return nil } +type EChartsPadding struct { + box chart.Box +} + +func (ep *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: + ep.box = chart.Box{ + Left: arr[0], + Top: arr[0], + Bottom: arr[0], + Right: arr[0], + } + case 2: + ep.box = chart.Box{ + Top: arr[0], + Bottom: arr[0], + Left: arr[1], + Right: arr[1], + } + default: + result := make([]int, 4) + copy(result, arr) + // 上右下左 + ep.box = chart.Box{ + Top: arr[0], + Right: arr[1], + Bottom: arr[2], + Left: arr[3], + } + } + return nil +} + type EChartsYAxis struct { Data []struct { - Min int `json:"min"` - Max int `json:"max"` - Interval int `json:"interval"` + Min *float64 `json:"min"` + Max *float64 `json:"max"` + Interval int `json:"interval"` AxisLabel struct { Formatter string `json:"formatter"` } `json:"axisLabel"` @@ -78,34 +135,50 @@ type EChartsYAxis struct { } func (ey *EChartsYAxis) UnmarshalJSON(data []byte) error { - data = bytes.TrimSpace(data) + data = convertToArray(data) if len(data) == 0 { return nil } - if data[0] != '[' { - data = []byte("[" + string(data) + "]") - } return json.Unmarshal(data, &ey.Data) } +type EChartsXAxis struct { + Data []struct { + Type string `json:"type"` + BoundaryGap *bool `json:"boundaryGap"` + SplitNumber int `json:"splitNumber"` + Data []string `json:"data"` + } +} + +func (ex *EChartsXAxis) UnmarshalJSON(data []byte) error { + data = convertToArray(data) + if len(data) == 0 { + return nil + } + return json.Unmarshal(data, &ex.Data) +} + type ECharsOptions struct { Theme string `json:"theme"` Title struct { - Text string `json:"text"` + Text string `json:"text"` + // 暂不支持(go-chart默认title只能居中) + TextAlign string `json:"textAlign"` TextStyle struct { - Color string `json:"color"` - FontFamily string `json:"fontFamily"` + Color string `json:"color"` + // TODO 字体支持 + FontFamily string `json:"fontFamily"` + FontSize float64 `json:"fontSize"` + Height float64 `json:"height"` } `json:"textStyle"` } `json:"title"` - XAxis struct { - Type string `json:"type"` - BoundaryGap *bool `json:"boundaryGap"` - SplitNumber int `json:"splitNumber"` - Data []string `json:"data"` - } `json:"xAxis"` + XAxis EChartsXAxis `json:"xAxis"` YAxis EChartsYAxis `json:"yAxis"` Legend struct { - Data []string `json:"data"` + Data []string `json:"data"` + Align string `json:"align"` + Padding EChartsPadding `json:"padding"` } `json:"legend"` Series []struct { Data []ECharsSeriesData `json:"data"` @@ -167,23 +240,46 @@ func (e *ECharsOptions) ToOptions() Options { o := Options{ Theme: e.Theme, } + titleTextStyle := e.Title.TextStyle o.Title = Title{ Text: e.Title.Text, + Style: chart.Style{ + FontColor: parseColor(titleTextStyle.Color), + FontSize: titleTextStyle.FontSize, + }, } - o.XAxis = XAxis{ - Type: e.XAxis.Type, - Data: e.XAxis.Data, - SplitNumber: e.XAxis.SplitNumber, + if titleTextStyle.FontSize != 0 && titleTextStyle.Height > titleTextStyle.FontSize { + padding := int(titleTextStyle.Height-titleTextStyle.FontSize) / 2 + o.Title.Style.Padding.Top = padding + o.Title.Style.Padding.Bottom = padding + } + + boundaryGap := false + if len(e.XAxis.Data) != 0 { + xAxis := e.XAxis.Data[0] + o.XAxis = XAxis{ + Type: xAxis.Type, + Data: xAxis.Data, + SplitNumber: xAxis.SplitNumber, + } + if xAxis.BoundaryGap == nil || *xAxis.BoundaryGap { + boundaryGap = true + } } o.Legend = Legend{ - Data: e.Legend.Data, + Data: e.Legend.Data, + Align: e.Legend.Align, + Padding: e.Legend.Padding.box, } if len(e.YAxis.Data) != 0 { yAxisOptions := make([]*YAxisOption, len(e.YAxis.Data)) for index, item := range e.YAxis.Data { - opt := &YAxisOption{} + opt := &YAxisOption{ + Max: item.Max, + Min: item.Min, + } template := item.AxisLabel.Formatter if template != "" { opt.Formater = func(v interface{}) string { @@ -200,7 +296,7 @@ func (e *ECharsOptions) ToOptions() Options { o.Series = series - if e.XAxis.BoundaryGap == nil || *e.XAxis.BoundaryGap { + if boundaryGap { tickPosition = chart.TickPositionBetweenTicks } o.TickPosition = tickPosition diff --git a/examples/main.go b/examples/main.go index 49d797f..c07b49d 100644 --- a/examples/main.go +++ b/examples/main.go @@ -42,6 +42,19 @@ var chartOptions = []map[string]string{ { "title": "折线图", "option": `{ + "title": { + "text": "line", + "textAlign": "left", + "textStyle": { + "color": "#333", + "fontSize": 24, + "height": 40 + } + }, + "yAxis": { + "min": 0, + "max": 300 + }, "xAxis": { "type": "category", "data": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] @@ -61,6 +74,8 @@ var chartOptions = []map[string]string{ "text": "Multi Line" }, "legend": { + "align": "left", + "padding": [5, 0, 0, 50], "data": ["Email", "Union Ads", "Video Ads", "Direct", "Search Engine"] }, "xAxis": { @@ -288,6 +303,9 @@ var chartOptions = []map[string]string{ func render(theme string) ([]byte, error) { data := bytes.Buffer{} for _, m := range chartOptions { + // if m["title"] != "多柱状图" { + // continue + // } chartHTML := []byte(`

{{title}}

{{option}}
diff --git a/legend.go b/legend.go index e5cc823..a9f73d1 100644 --- a/legend.go +++ b/legend.go @@ -28,6 +28,7 @@ import ( type LegendOption struct { Style chart.Style + Padding chart.Box Align string TextPosition string Theme string @@ -61,7 +62,7 @@ func DefaultLegendIconDraw(r chart.Renderer, opt LegendIconDrawOption) { stokeWidth := opt.Style.GetStrokeWidth() r.SetStrokeWidth(stokeWidth) height := opt.Box.Bottom - opt.Box.Top - ly := (height / 2) + 1 + ly := opt.Box.Top - (height / 2) + 2 r.MoveTo(opt.Box.Left, ly) r.LineTo(opt.Box.Right, ly) r.Stroke() @@ -126,11 +127,14 @@ func LegendCustomize(c *chart.Chart, opt LegendOption) chart.Renderable { left = (cb.Width() - labelWidth) / 2 } + left += opt.Padding.Left + top := opt.Padding.Top + legendBox := chart.Box{ Left: left, Right: left + labelWidth, - Top: 0, - Bottom: 0 + legendBoxHeight, + Top: top, + Bottom: top + legendBoxHeight, } chart.Draw.Box(r, legendBox, legendDefaults) @@ -142,7 +146,7 @@ func LegendCustomize(c *chart.Chart, opt LegendOption) chart.Renderable { lineTextGap := 5 startX := legendBox.Left + legendStyle.Padding.Left - ty := legendYMargin + legendStyle.Padding.Top + textHeight + ty := top + legendYMargin + legendStyle.Padding.Top + textHeight var label string var x int iconDraw := opt.IconDraw