diff --git a/axis.go b/axis.go
index 00a3332..53b5362 100644
--- a/axis.go
+++ b/axis.go
@@ -194,7 +194,7 @@ func (a *axisPainter) Render() (Box, error) {
switch opt.Position {
case PositionTop:
- labelPaddingTop = labelMargin
+ labelPaddingTop = 0
x1 = p.Width()
y0 = labelMargin + int(opt.FontSize)
ticksPaddingTop = int(opt.FontSize)
diff --git a/axis_test.go b/axis_test.go
index fe7f874..17fe8d6 100644
--- a/axis_test.go
+++ b/axis_test.go
@@ -48,10 +48,12 @@ func TestAxis(t *testing.T) {
"Sat",
"Sun",
},
+ SplitLineShow: true,
+ SplitLineColor: drawing.ColorBlack,
}).Render()
return p.Bytes()
},
- result: "",
+ result: "",
},
// 底部x轴文本居左
{
@@ -113,6 +115,48 @@ func TestAxis(t *testing.T) {
},
result: "",
},
+ // 右侧
+ {
+ render: func(p *Painter) ([]byte, error) {
+ _, _ = NewAxisPainter(p, AxisOption{
+ Data: []string{
+ "Mon",
+ "Tue",
+ "Wed",
+ "Thu",
+ "Fri",
+ "Sat",
+ "Sun",
+ },
+ Position: PositionRight,
+ BoundaryGap: FalseFlag(),
+ SplitLineShow: true,
+ SplitLineColor: drawing.ColorBlack,
+ }).Render()
+ return p.Bytes()
+ },
+ result: "",
+ },
+ // 顶部
+ {
+ render: func(p *Painter) ([]byte, error) {
+ _, _ = NewAxisPainter(p, AxisOption{
+ Data: []string{
+ "Mon",
+ "Tue",
+ "Wed",
+ "Thu",
+ "Fri",
+ "Sat",
+ "Sun",
+ },
+ Formatter: "{value} --",
+ Position: PositionTop,
+ }).Render()
+ return p.Bytes()
+ },
+ result: "",
+ },
}
for _, tt := range tests {
diff --git a/bar_chart.go b/bar_chart.go
index 2982829..0ac9f47 100644
--- a/bar_chart.go
+++ b/bar_chart.go
@@ -60,6 +60,13 @@ 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
@@ -95,6 +102,7 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B
markPointPainter,
markLinePainter,
}
+ labelRenderOptions := make([]barChartLabelRenderOption, 0)
for index := range seriesList {
series := seriesList[index]
yRange := result.axisRanges[series.AxisIndex]
@@ -156,9 +164,15 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B
if !series.Label.Color.IsZero() {
labelStyle.FontColor = series.Label.Color
}
- seriesPainter.OverrideTextStyle(labelStyle)
+
textBox := seriesPainter.MeasureText(text)
- seriesPainter.Text(text, x+(barWidth-textBox.Width())>>1, barMaxHeight-h-distance)
+
+ labelRenderOptions = append(labelRenderOptions, barChartLabelRenderOption{
+ Text: text,
+ Style: labelStyle,
+ X: x + (barWidth-textBox.Width())>>1,
+ Y: barMaxHeight - h - distance,
+ })
}
markPointPainter.Add(markPointRenderOption{
@@ -176,6 +190,10 @@ 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/bar_chart_test.go b/bar_chart_test.go
new file mode 100644
index 0000000..138b3ca
--- /dev/null
+++ b/bar_chart_test.go
@@ -0,0 +1,120 @@
+// 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 (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestBarChart(t *testing.T) {
+ assert := assert.New(t)
+
+ tests := []struct {
+ render func(*Painter) ([]byte, error)
+ result string
+ }{
+ {
+ render: func(p *Painter) ([]byte, error) {
+ seriesList := NewSeriesListDataFromValues([][]float64{
+ {
+ 2.0,
+ 4.9,
+ 7.0,
+ 23.2,
+ 25.6,
+ 76.7,
+ 135.6,
+ 162.2,
+ 32.6,
+ 20.0,
+ 6.4,
+ 3.3,
+ },
+ {
+ 2.6,
+ 5.9,
+ 9.0,
+ 26.4,
+ 28.7,
+ 70.7,
+ 175.6,
+ 182.2,
+ 48.7,
+ 18.8,
+ 6.0,
+ 2.3,
+ },
+ })
+ for index := range seriesList {
+ seriesList[index].Label.Show = true
+ }
+ _, err := NewBarChart(p, BarChartOption{
+ Padding: Box{
+ Left: 10,
+ Top: 10,
+ Right: 10,
+ Bottom: 10,
+ },
+ SeriesList: seriesList,
+ XAxis: NewXAxisOption([]string{
+ "Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "May",
+ "Jun",
+ "Jul",
+ "Aug",
+ "Sep",
+ "Oct",
+ "Nov",
+ "Dec",
+ }),
+ YAxisOptions: NewYAxisOptions([]string{
+ "Rainfall",
+ "Evaporation",
+ }),
+ }).Render()
+ if err != nil {
+ return nil, err
+ }
+ return p.Bytes()
+ },
+ result: "",
+ },
+ }
+
+ for _, tt := range tests {
+ p, err := NewPainter(PainterOptions{
+ Type: ChartOutputSVG,
+ Width: 600,
+ Height: 400,
+ }, PainterThemeOption(defaultTheme))
+ assert.Nil(err)
+ data, err := tt.render(p)
+ assert.Nil(err)
+ assert.Equal(tt.result, string(data))
+ }
+}
diff --git a/chart_option.go b/chart_option.go
index 0bc0a34..643c4e7 100644
--- a/chart_option.go
+++ b/chart_option.go
@@ -336,12 +336,7 @@ func RadarRender(values [][]float64, opts ...OptionFunc) (*Painter, error) {
// FunnelRender funnel chart render
func FunnelRender(values []float64, opts ...OptionFunc) (*Painter, error) {
- seriesList := make(SeriesList, len(values))
- for index, value := range values {
- seriesList[index] = NewSeriesFromValues([]float64{
- value,
- }, ChartTypeFunnel)
- }
+ seriesList := NewFunnelSeriesList(values)
return Render(ChartOption{
SeriesList: seriesList,
}, opts...)
diff --git a/funnel_chart.go b/funnel_chart.go
index 63b3504..7c04bfe 100644
--- a/funnel_chart.go
+++ b/funnel_chart.go
@@ -34,6 +34,16 @@ type funnelChart struct {
opt *FunnelChartOption
}
+func NewFunnelSeriesList(values []float64) SeriesList {
+ seriesList := make(SeriesList, len(values))
+ for index, value := range values {
+ seriesList[index] = NewSeriesFromValues([]float64{
+ value,
+ }, ChartTypeFunnel)
+ }
+ return seriesList
+}
+
func NewFunnelChart(p *Painter, opt FunnelChartOption) *funnelChart {
if opt.Theme == nil {
opt.Theme = defaultTheme
diff --git a/funnel_chart_test.go b/funnel_chart_test.go
new file mode 100644
index 0000000..d260bfb
--- /dev/null
+++ b/funnel_chart_test.go
@@ -0,0 +1,79 @@
+// 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 (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestFunnelChart(t *testing.T) {
+ assert := assert.New(t)
+
+ tests := []struct {
+ render func(*Painter) ([]byte, error)
+ result string
+ }{
+ {
+ render: func(p *Painter) ([]byte, error) {
+ _, err := NewFunnelChart(p, FunnelChartOption{
+ SeriesList: NewFunnelSeriesList([]float64{
+ 100,
+ 80,
+ 60,
+ 40,
+ 20,
+ }),
+ Legend: NewLegendOption([]string{
+ "Show",
+ "Click",
+ "Visit",
+ "Inquiry",
+ "Order",
+ }),
+ Title: TitleOption{
+ Text: "Funnel",
+ },
+ }).Render()
+ if err != nil {
+ return nil, err
+ }
+ return p.Bytes()
+ },
+ result: "",
+ },
+ }
+
+ for _, tt := range tests {
+ p, err := NewPainter(PainterOptions{
+ Type: ChartOutputSVG,
+ Width: 600,
+ Height: 400,
+ }, PainterThemeOption(defaultTheme))
+ assert.Nil(err)
+ data, err := tt.render(p)
+ assert.Nil(err)
+ assert.Equal(tt.result, string(data))
+ }
+}