diff --git a/axis.go b/axis.go index db32e70..5937ef0 100644 --- a/axis.go +++ b/axis.go @@ -42,10 +42,18 @@ type ( const axisStrokeWidth = 1 -func GetXAxisAndValues(xAxis XAxis, theme string) (chart.XAxis, []float64) { - xValues := make([]float64, len(xAxis.Data)) - ticks := make([]chart.Tick, len(xAxis.Data)) - for index, key := range xAxis.Data { +func GetXAxisAndValues(xAxis XAxis, tickPosition chart.TickPosition, theme string) (chart.XAxis, []float64) { + data := xAxis.Data + // 如果居中,则需要多添加一个值 + if tickPosition == chart.TickPositionBetweenTicks { + data = append([]string{ + "", + }, data...) + } + + xValues := make([]float64, len(data)) + ticks := make([]chart.Tick, len(data)) + for index, key := range data { f := float64(index) xValues[index] = f ticks[index] = chart.Tick{ @@ -60,7 +68,8 @@ func GetXAxisAndValues(xAxis XAxis, theme string) (chart.XAxis, []float64) { }, xValues } return chart.XAxis{ - Ticks: ticks, + Ticks: ticks, + TickPosition: tickPosition, Style: chart.Style{ FontColor: AxisColorLight, StrokeColor: AxisColorLight, diff --git a/bar_series.go b/bar_series.go index 847fbe4..9e965b9 100644 --- a/bar_series.go +++ b/bar_series.go @@ -22,14 +22,26 @@ package charts -import "github.com/wcharczuk/go-chart/v2" +import ( + "github.com/wcharczuk/go-chart/v2" +) + +const defaultBarMargin = 10 type BarSeries struct { BaseSeries + Count int + Index int + // 间隔 + Margin int + // 偏移量 + Offset int + // 宽度 + BarWidth int } func (bs BarSeries) Render(r chart.Renderer, canvasBox chart.Box, xrange, yrange chart.Range, defaults chart.Style) { - if bs.Len() == 0 { + if bs.Len() == 0 || bs.Count <= 0 { return } style := bs.Style.InheritFrom(defaults) @@ -40,18 +52,25 @@ func (bs BarSeries) Render(r chart.Renderer, canvasBox chart.Box, xrange, yrange cb := canvasBox.Bottom cl := canvasBox.Left + margin := bs.Margin + if margin <= 0 { + margin = defaultBarMargin + } + barWidth := bs.BarWidth + if barWidth <= 0 { + barWidth = canvasBox.Width() / (bs.Len() * bs.Count) + } for i := 0; i < bs.Len(); i++ { vx, vy := bs.GetValues(i) - x := cl + xrange.Translate(vx) + x := cl + xrange.Translate(vx) + bs.Index*(margin+barWidth) + bs.Offset y := cb - yrange.Translate(vy) chart.Draw.Box(r, chart.Box{ - Left: x, - Top: y, - // TODO 计算宽度 - Right: x + 10, + Left: x, + Top: y, + Right: x + barWidth, Bottom: canvasBox.Bottom - 1, }, style) } diff --git a/base_series.go b/base_series.go index 86e0659..673c0a6 100644 --- a/base_series.go +++ b/base_series.go @@ -37,8 +37,9 @@ var ( // BaseSeries represents a line on a chart. type BaseSeries struct { - Name string - Style chart.Style + Name string + Style chart.Style + TickPosition chart.TickPosition YAxis chart.YAxisType @@ -61,16 +62,26 @@ func (cs BaseSeries) GetStyle() chart.Style { // Len returns the number of elements in the series. func (cs BaseSeries) Len() int { - return len(cs.XValues) + offset := 0 + if cs.TickPosition == chart.TickPositionBetweenTicks { + offset = -1 + } + return len(cs.XValues) + offset } // GetValues gets the x,y values at a given index. func (cs BaseSeries) GetValues(index int) (float64, float64) { + if cs.TickPosition == chart.TickPositionBetweenTicks { + index++ + } return cs.XValues[index], cs.YValues[index] } // GetFirstValues gets the first x,y values. func (cs BaseSeries) GetFirstValues() (float64, float64) { + if cs.TickPosition == chart.TickPositionBetweenTicks { + return cs.XValues[1], cs.YValues[1] + } return cs.XValues[0], cs.YValues[0] } diff --git a/charts.go b/charts.go index 9476b6d..5d0e0c4 100644 --- a/charts.go +++ b/charts.go @@ -69,7 +69,10 @@ func ToSVG(c *chart.Chart) ([]byte, error) { return render(c, chart.SVG) } func New(opt Option) *chart.Chart { - xAxis, xValues := GetXAxisAndValues(opt.XAxis, opt.Theme) + tickPosition := chart.TickPositionBetweenTicks + // tickPosition = chart.TickPositionUnset + + xAxis, xValues := GetXAxisAndValues(opt.XAxis, tickPosition, opt.Theme) legendSize := len(opt.Legend.Data) for index, item := range opt.Series { @@ -87,7 +90,7 @@ func New(opt Option) *chart.Chart { Height: opt.Height, XAxis: xAxis, YAxis: GetYAxis(opt.Theme), - Series: GetSeries(opt.Series, opt.Theme), + Series: GetSeries(opt.Series, tickPosition, opt.Theme), } // 设置secondary的样式 c.YAxisSecondary.Style = c.YAxis.Style diff --git a/line_series.go b/line_series.go new file mode 100644 index 0000000..5b7c9fd --- /dev/null +++ b/line_series.go @@ -0,0 +1,40 @@ +// MIT License + +// Copyright (c) 2021 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 ( + "github.com/wcharczuk/go-chart/v2" +) + +type LineSeries struct { + BaseSeries +} + +func (bs LineSeries) Render(r chart.Renderer, canvasBox chart.Box, xrange, yrange chart.Range, defaults chart.Style) { + style := bs.Style.InheritFrom(defaults) + // 如果是居中,画线时重新调整 + if bs.TickPosition == chart.TickPositionBetweenTicks { + xrange = wrapRange(xrange, bs.TickPosition) + } + chart.Draw.LineSeries(r, canvasBox, xrange, yrange, style, bs) +} diff --git a/range.go b/range.go new file mode 100644 index 0000000..b062e51 --- /dev/null +++ b/range.go @@ -0,0 +1,52 @@ +// MIT License + +// Copyright (c) 2021 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 ( + "github.com/wcharczuk/go-chart/v2" +) + +type Range struct { + TickPosition chart.TickPosition + chart.ContinuousRange +} + +func wrapRange(r chart.Range, tickPosition chart.TickPosition) chart.Range { + xr, ok := r.(*chart.ContinuousRange) + if !ok { + return r + } + return &Range{ + TickPosition: tickPosition, + ContinuousRange: *xr, + } +} + +// Translate maps a given value into the ContinuousRange space. +func (r Range) Translate(value float64) int { + v := r.ContinuousRange.Translate(value) + if r.TickPosition == chart.TickPositionBetweenTicks { + v -= int(float64(r.Domain) / (r.GetDelta() * 2)) + } + return v +} diff --git a/series.go b/series.go index bc05d06..f0caf36 100644 --- a/series.go +++ b/series.go @@ -49,8 +49,15 @@ func getSeriesColor(theme string, index int) drawing.Color { return SeriesColorsLight[index%len(SeriesColorsLight)] } -func GetSeries(series []Series, theme string) []chart.Series { +func GetSeries(series []Series, tickPosition chart.TickPosition, theme string) []chart.Series { arr := make([]chart.Series, len(series)) + barCount := 0 + barIndex := 0 + for _, item := range series { + if item.Type == SeriesBar { + barCount++ + } + } for index, item := range series { style := chart.Style{ StrokeWidth: lineStrokeWidth, @@ -58,23 +65,28 @@ func GetSeries(series []Series, theme string) []chart.Series { DotColor: getSeriesColor(theme, index), DotWidth: dotWith, } + item.Data = append([]float64{ + 0.0, + }, item.Data...) + baseSeries := BaseSeries{ + Name: item.Name, + XValues: item.XValues, + Style: style, + YValues: item.Data, + TickPosition: tickPosition, + } // TODO 判断类型 switch item.Type { case SeriesBar: arr[index] = BarSeries{ - BaseSeries: BaseSeries{ - Name: item.Name, - XValues: item.XValues, - Style: style, - YValues: item.Data, - }, + Count: barCount, + Index: barIndex, + BaseSeries: baseSeries, } + barIndex++ default: - arr[index] = chart.ContinuousSeries{ - Name: item.Name, - XValues: item.XValues, - Style: style, - YValues: item.Data, + arr[index] = LineSeries{ + BaseSeries: baseSeries, } } }