diff --git a/bar_chart.go b/bar_chart.go index 26f8da5..797f710 100644 --- a/bar_chart.go +++ b/bar_chart.go @@ -62,13 +62,6 @@ type BarChartOption struct { Legend LegendOption } -type barChartLabelRenderOption struct { - Text string - Style Style - X int - Y int -} - func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (Box, error) { p := b.p opt := b.opt @@ -100,11 +93,12 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B markPointPainter := NewMarkPointPainter(seriesPainter) markLinePainter := NewMarkLinePainter(seriesPainter) + labelPainter := NewSeriesLabelPainter(seriesPainter) rendererList := []Renderer{ + labelPainter, markPointPainter, markLinePainter, } - labelRenderOptions := make([]barChartLabelRenderOption, 0) for index := range seriesList { series := seriesList[index] yRange := result.axisRanges[series.AxisIndex] @@ -168,8 +162,7 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B } textBox := seriesPainter.MeasureText(text) - - labelRenderOptions = append(labelRenderOptions, barChartLabelRenderOption{ + labelPainter.Add(LabelValue{ Text: text, Style: labelStyle, X: x + (barWidth-textBox.Width())>>1, @@ -192,10 +185,6 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B Range: yRange, }) } - for _, labelOpt := range labelRenderOptions { - seriesPainter.OverrideTextStyle(labelOpt.Style) - seriesPainter.Text(labelOpt.Text, labelOpt.X, labelOpt.Y) - } // 最大、最小的mark point err := doRender(rendererList...) if err != nil { diff --git a/line_chart.go b/line_chart.go index cdec280..bf39ae2 100644 --- a/line_chart.go +++ b/line_chart.go @@ -97,7 +97,9 @@ func (l *lineChart) render(result *defaultRenderResult, seriesList SeriesList) ( } markPointPainter := NewMarkPointPainter(seriesPainter) markLinePainter := NewMarkLinePainter(seriesPainter) + labelPainter := NewSeriesLabelPainter(seriesPainter) rendererList := []Renderer{ + labelPainter, markPointPainter, markLinePainter, } @@ -105,6 +107,8 @@ func (l *lineChart) render(result *defaultRenderResult, seriesList SeriesList) ( if strokeWidth == 0 { strokeWidth = defaultStrokeWidth } + seriesNames := seriesList.Names() + theme := opt.Theme for index := range seriesList { series := seriesList[index] seriesColor := opt.Theme.GetSeriesColor(series.index) @@ -125,6 +129,32 @@ func (l *lineChart) render(result *defaultRenderResult, seriesList SeriesList) ( Y: h, } points = append(points, p) + + // 如果label不需要展示,则返回 + if !series.Label.Show { + continue + } + distance := series.Label.Distance + if distance == 0 { + distance = 5 + } + text := NewValueLabelFormatter(seriesNames, series.Label.Formatter)(index, item.Value, -1) + labelStyle := Style{ + FontColor: theme.GetTextColor(), + FontSize: labelFontSize, + Font: opt.Font, + } + if !series.Label.Color.IsZero() { + labelStyle.FontColor = series.Label.Color + } + + textBox := seriesPainter.MeasureText(text) + labelPainter.Add(LabelValue{ + Text: text, + Style: labelStyle, + X: p.X - textBox.Width()>>1, + Y: p.Y - distance, + }) } // 如果需要填充区域 if opt.FillArea { diff --git a/series_label.go b/series_label.go new file mode 100644 index 0000000..c1850bb --- /dev/null +++ b/series_label.go @@ -0,0 +1,56 @@ +// 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 "github.com/wcharczuk/go-chart/v2" + +type LabelValue struct { + Text string + Style Style + X int + Y int +} + +type SeriesLabelPainter struct { + p *Painter + values []LabelValue +} + +func NewSeriesLabelPainter(p *Painter) *SeriesLabelPainter { + return &SeriesLabelPainter{ + p: p, + values: make([]LabelValue, 0), + } +} + +func (o *SeriesLabelPainter) Add(value LabelValue) { + o.values = append(o.values, value) +} + +func (o *SeriesLabelPainter) Render() (Box, error) { + for _, item := range o.values { + o.p.OverrideTextStyle(item.Style) + o.p.Text(item.Text, item.X, item.Y) + } + return chart.BoxZero, nil +}