From 7d66176ea89d10814dd06c649fe8dcbc662bc2aa Mon Sep 17 00:00:00 2001 From: Mike Jensen Date: Tue, 17 Dec 2024 07:01:53 -0700 Subject: [PATCH 1/3] Legend and Title Offset Config Changes This change moves the `Top` and `Left` configuration options for a Legend or Title under the `OffsetStr` struct. In cases where only `Left` was being set with `PositionLeft` / `PositionCenter` / `PositionRight`, now `OffsetLeft` / `OffsetCenter` / `OffsetRight` can be used to set the `Offset` field. In addition sub-structs in configurations should consistently have `WithX` functions to allow easy modifications or a builder pattern. --- README.md | 12 +++--- alias.go | 64 +++++++++++++++++++++++++++++--- axis.go | 2 +- chart_option.go | 4 +- chart_option_test.go | 9 +++-- chartdraw/style.go | 24 ++++++++++++ chartdraw/xaxis.go | 2 + charts_test.go | 8 +++- echarts.go | 14 ++++--- examples/bar_chart/main.go | 3 +- examples/chinese/main.go | 2 +- examples/line_chart-2/main.go | 4 +- examples/line_chart-area/main.go | 2 +- examples/pie_chart/main.go | 4 +- examples/radar_chart/main.go | 2 +- examples/web-charts/main.go | 12 +++--- legend.go | 46 ++++++++++------------- legend_test.go | 22 ++++++----- line_chart_test.go | 4 +- painter.go | 2 +- pie_chart_test.go | 8 ++-- series.go | 2 +- series_label.go | 2 +- title.go | 17 ++++----- title_test.go | 14 ++++--- xaxis.go | 2 +- 26 files changed, 189 insertions(+), 98 deletions(-) diff --git a/README.md b/README.md index 0ff34f9..b3aa054 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Notable improvements in our fork include: * **Axis Improvements:** Significant enhancements to axis rendering, data range selection, and configuration simplification were made in PR [#3](https://github.com/go-analyze/charts/pull/3). * **Theming:** In PR [#4](https://github.com/go-analyze/charts/pull/4) (and some subsequent changes) we introduced `vivid-light` and `vivid-dark` themes for more vibrant visualizations, alongside API changes for greater theme and font control. Long term we plan to make themes easier to mutate and define. -* **Configuration Simplification:** PR [#5](https://github.com/go-analyze/charts/pull/5) began our effort to streamline chart configuration, making names more descriptive and specific while focusing on a theme-centric approach. Documentation on configuration and use is also being improved. (See also [#15](https://github.com/go-analyze/charts/pull/15)) +* **Configuration Simplification:** PR [#5](https://github.com/go-analyze/charts/pull/5) began our effort to streamline chart configuration, making names more descriptive and specific while focusing on a theme-centric approach. Documentation on configuration and use is also being improved. (See also [#15](https://github.com/go-analyze/charts/pull/15), [#20](https://github.com/go-analyze/charts/pull/20)) * **Expanded Testing:** Ongoing test coverage expansions have led to bug discoveries and fixes. This will continue to help ensure that our charts render perfectly for a wide range of configurations and use. Our library is a work in progress, aiming to become a standout choice for Go developers seeking powerful, yet easy-to-use charting tools. We welcome contributions and feedback as we continue to enhance our library's functionality, configurability, and reliability. @@ -89,7 +89,7 @@ func main() { charts.LegendLabelsOptionFunc([]string{ "Email", "Search Engine", - }, charts.PositionCenter), + }), ) // snip... } @@ -146,7 +146,7 @@ func main() { charts.LegendLabelsOptionFunc([]string{ "Rainfall", "Evaporation", - }, charts.PositionRight), + }), charts.MarkLineOptionFunc(0, charts.SeriesMarkDataTypeAverage), charts.MarkPointOptionFunc(0, charts.SeriesMarkDataTypeMax, charts.SeriesMarkDataTypeMin), @@ -240,7 +240,7 @@ func main() { charts.TitleOptionFunc(charts.TitleOption{ Text: "Rainfall vs Evaporation", Subtext: "Fake Data", - Left: charts.PositionCenter, + Offset: charts.OffsetCenter, }), charts.PaddingOptionFunc(charts.Box{ Top: 20, @@ -249,7 +249,7 @@ func main() { Left: 20, }), charts.LegendOptionFunc(charts.LegendOption{ - Orient: charts.OrientVertical, + Veritcal: true, Data: []string{ "Search Engine", "Direct", @@ -257,7 +257,7 @@ func main() { "Union Ads", "Video Ads", }, - Left: charts.PositionLeft, + Offset: charts.OffsetLeft, }), charts.PieSeriesShowLabel(), ) diff --git a/alias.go b/alias.go index 29001ca..6e56db4 100644 --- a/alias.go +++ b/alias.go @@ -1,27 +1,81 @@ package charts import ( + "strconv" + "github.com/go-analyze/charts/chartdraw" "github.com/go-analyze/charts/chartdraw/drawing" ) type Box = chartdraw.Box +type Point = chartdraw.Point type Color = drawing.Color type FontStyle = chartdraw.FontStyle var BoxZero = chartdraw.BoxZero -// Offset provides an ability to configure a shift from the top or left alignments. -type Offset struct { +// OffsetInt provides an ability to configure a shift from the top or left alignments. +type OffsetInt struct { // Left indicates a vertical spacing adjustment from the top. Top int // Left indicates a horizontal spacing adjustment from the left. Left int } -type Point struct { - X int - Y int +func (o OffsetInt) WithTop(val int) OffsetInt { + return OffsetInt{ + Left: o.Left, + Top: val, + } +} + +func (o OffsetInt) WithLeft(val int) OffsetInt { + return OffsetInt{ + Left: val, + Top: o.Top, + } +} + +// OffsetStr provides an ability to configure a shift from the top or left alignments using flexible string inputs. +type OffsetStr struct { + // Left is the distance between the component and the left side of the container. + // It can be pixel value (20), percentage value (20%), or position description: 'left', 'right', 'center'. + Left string + // Top is the distance between the component and the top side of the container. + // It can be pixel value (20), or percentage value (20%). + Top string +} + +var OffsetLeft = OffsetStr{Left: PositionLeft} +var OffsetRight = OffsetStr{Left: PositionRight} +var OffsetCenter = OffsetStr{Left: PositionCenter} + +func (o OffsetStr) WithTop(val string) OffsetStr { + return OffsetStr{ + Left: o.Left, + Top: val, + } +} + +func (o OffsetStr) WithTopI(val int) OffsetStr { + return OffsetStr{ + Left: o.Left, + Top: strconv.Itoa(val), + } +} + +func (o OffsetStr) WithLeft(val string) OffsetStr { + return OffsetStr{ + Left: val, + Top: o.Top, + } +} + +func (o OffsetStr) WithLeftI(val int) OffsetStr { + return OffsetStr{ + Left: strconv.Itoa(val), + Top: o.Top, + } } const ( diff --git a/axis.go b/axis.go index f9a5fa8..1511705 100644 --- a/axis.go +++ b/axis.go @@ -48,7 +48,7 @@ type AxisOption struct { // TextRotation are the radians for rotating the label. TextRotation float64 // LabelOffset is the offset of each label. - LabelOffset Offset + LabelOffset OffsetInt // Unit is a suggestion for how large the axis step is, this is a recommendation only. Larger numbers result in fewer labels. Unit float64 // LabelCount is the number of labels to show on the axis. Specify a smaller number to reduce writing collisions. This value takes priority over Unit. diff --git a/chart_option.go b/chart_option.go index 915496e..75af16b 100644 --- a/chart_option.go +++ b/chart_option.go @@ -120,9 +120,9 @@ func LegendOptionFunc(legend LegendOption) OptionFunc { } // LegendLabelsOptionFunc set legend labels of chart -func LegendLabelsOptionFunc(labels []string, left ...string) OptionFunc { +func LegendLabelsOptionFunc(labels []string) OptionFunc { return func(opt *ChartOption) { - opt.Legend = NewLegendOption(labels, left...) + opt.Legend = NewLegendOption(labels) } } diff --git a/chart_option_test.go b/chart_option_test.go index 07b5dbb..41e6d4b 100644 --- a/chart_option_test.go +++ b/chart_option_test.go @@ -159,7 +159,7 @@ func TestLineRender(t *testing.T) { "Video Ads", "Direct", "Search Engine", - }, PositionCenter), + }), ) require.NoError(t, err) data, err := p.Bytes() @@ -220,11 +220,12 @@ func TestBarRender(t *testing.T) { LegendLabelsOptionFunc([]string{ "Rainfall", "Evaporation", - }, PositionRight), + }), MarkLineOptionFunc(0, SeriesMarkDataTypeAverage), MarkPointOptionFunc(0, SeriesMarkDataTypeMax, SeriesMarkDataTypeMin), // custom option func func(opt *ChartOption) { + opt.Legend.Offset = OffsetRight opt.SeriesList[1].MarkPoint = NewMarkPoint( SeriesMarkDataTypeMax, SeriesMarkDataTypeMin, @@ -306,7 +307,7 @@ func TestPieRender(t *testing.T) { TitleOptionFunc(TitleOption{ Text: "Rainfall vs Evaporation", Subtext: "Fake Data", - Left: PositionCenter, + Offset: OffsetCenter, }), PaddingOptionFunc(Box{ Top: 20, @@ -323,7 +324,7 @@ func TestPieRender(t *testing.T) { "Union Ads", "Video Ads", }, - Left: PositionLeft, + Offset: OffsetLeft, }), PieSeriesShowLabel(), ) diff --git a/chartdraw/style.go b/chartdraw/style.go index 5a46a92..775c403 100644 --- a/chartdraw/style.go +++ b/chartdraw/style.go @@ -290,6 +290,30 @@ func (s FontStyle) GetFont(defaults ...*truetype.Font) *truetype.Font { return s.Font } +func (s FontStyle) WithSize(size float64) FontStyle { + return FontStyle{ + FontSize: size, + FontColor: s.FontColor, + Font: s.Font, + } +} + +func (s FontStyle) WithColor(color drawing.Color) FontStyle { + return FontStyle{ + FontSize: s.FontSize, + FontColor: color, + Font: s.Font, + } +} + +func (s FontStyle) WithFont(font *truetype.Font) FontStyle { + return FontStyle{ + FontSize: s.FontSize, + FontColor: s.FontColor, + Font: font, + } +} + // GetPadding returns the padding. func (s Style) GetPadding(defaults ...Box) Box { if s.Padding.IsZero() { diff --git a/chartdraw/xaxis.go b/chartdraw/xaxis.go index a74ee21..d26b6df 100644 --- a/chartdraw/xaxis.go +++ b/chartdraw/xaxis.go @@ -123,6 +123,7 @@ func (xa XAxis) Measure(r Renderer, canvasBox Box, ra Range, defaults Style, tic Left: left, Right: right, Bottom: bottom, + IsSet: true, } } @@ -174,6 +175,7 @@ func (xa XAxis) Render(r Renderer, canvasBox Box, ra Range, defaults Style, tick Right: tx, Top: canvasBox.Bottom + DefaultXAxisMargin, Bottom: canvasBox.Bottom + DefaultXAxisMargin, + IsSet: true, }, finalTickStyle) ftb := Text.MeasureLines(r, Text.WrapFit(r, t.Label, tx-ltx, finalTickStyle), finalTickStyle) diff --git a/charts_test.go b/charts_test.go index f49a7ef..d30b473 100644 --- a/charts_test.go +++ b/charts_test.go @@ -12,7 +12,9 @@ func BenchmarkMultiChartPNGRender(b *testing.B) { opt := ChartOption{ OutputFormat: ChartOutputPNG, Legend: LegendOption{ - Top: "-90", + Offset: OffsetStr{ + Top: "-90", + }, Data: []string{ "Milk Tea", "Matcha Latte", @@ -121,7 +123,9 @@ func BenchmarkMultiChartSVGRender(b *testing.B) { for i := 0; i < b.N; i++ { opt := ChartOption{ Legend: LegendOption{ - Top: "-90", + Offset: OffsetStr{ + Top: "-90", + }, Data: []string{ "Milk Tea", "Matcha Latte", diff --git a/echarts.go b/echarts.go index 8c55c47..e5066cc 100644 --- a/echarts.go +++ b/echarts.go @@ -399,8 +399,10 @@ func (eo *EChartsOption) ToOption() ChartOption { FontSize: titleSubtextStyle.FontSize, FontColor: titleSubtextStyle.FontColor, }, - Left: string(eo.Title.Left), - Top: string(eo.Title.Top), + Offset: OffsetStr{ + Left: string(eo.Title.Left), + Top: string(eo.Title.Top), + }, }, Legend: LegendOption{ Show: eo.Legend.Show, @@ -408,9 +410,11 @@ func (eo *EChartsOption) ToOption() ChartOption { FontSize: legendTextStyle.FontSize, FontColor: legendTextStyle.FontColor, }, - Data: eo.Legend.Data, - Left: string(eo.Legend.Left), - Top: string(eo.Legend.Top), + Data: eo.Legend.Data, + Offset: OffsetStr{ + Left: string(eo.Legend.Left), + Top: string(eo.Legend.Top), + }, Align: eo.Legend.Align, Orient: eo.Legend.Orient, }, diff --git a/examples/bar_chart/main.go b/examples/bar_chart/main.go index 3ababe7..b7fc7f2 100644 --- a/examples/bar_chart/main.go +++ b/examples/bar_chart/main.go @@ -71,12 +71,13 @@ func main() { charts.LegendLabelsOptionFunc([]string{ "Rainfall", "Evaporation", - }, charts.PositionRight), + }), charts.MarkLineOptionFunc(0, charts.SeriesMarkDataTypeAverage), charts.MarkPointOptionFunc(0, charts.SeriesMarkDataTypeMax, charts.SeriesMarkDataTypeMin), // custom option func func(opt *charts.ChartOption) { + opt.Legend.Offset = charts.OffsetRight opt.SeriesList[1].MarkPoint = charts.NewMarkPoint( charts.SeriesMarkDataTypeMax, charts.SeriesMarkDataTypeMin, diff --git a/examples/chinese/main.go b/examples/chinese/main.go index ce1a104..21ffc1b 100644 --- a/examples/chinese/main.go +++ b/examples/chinese/main.go @@ -95,7 +95,7 @@ func main() { "视频广告", "直接访问", "搜索引擎", - }, charts.PositionCenter), + }), ) if err != nil { panic(err) diff --git a/examples/line_chart-2/main.go b/examples/line_chart-2/main.go index 4d77ef8..1ea944c 100644 --- a/examples/line_chart-2/main.go +++ b/examples/line_chart-2/main.go @@ -60,11 +60,11 @@ func main() { LabelCount: 10, }), func(opt *charts.ChartOption) { - opt.Legend.Left = charts.PositionRight + opt.Legend.Offset = charts.OffsetRight opt.Legend.Align = charts.AlignRight opt.Legend.Orient = charts.OrientVertical opt.Legend.FontStyle.FontSize = 6 - opt.Title.Left = charts.PositionCenter + opt.Title.Offset = charts.OffsetCenter opt.SymbolShow = charts.False() opt.LineStrokeWidth = 1.6 opt.ValueFormatter = func(f float64) string { diff --git a/examples/line_chart-area/main.go b/examples/line_chart-area/main.go index a4f9cca..3953faa 100644 --- a/examples/line_chart-area/main.go +++ b/examples/line_chart-area/main.go @@ -47,7 +47,7 @@ func main() { }), charts.LegendLabelsOptionFunc([]string{ "Email", - }, "50"), + }), func(opt *charts.ChartOption) { opt.Legend.Padding = charts.Box{ Top: 5, diff --git a/examples/pie_chart/main.go b/examples/pie_chart/main.go index efd05c5..c4f7d04 100644 --- a/examples/pie_chart/main.go +++ b/examples/pie_chart/main.go @@ -34,7 +34,7 @@ func main() { charts.TitleOptionFunc(charts.TitleOption{ Text: "Rainfall vs Evaporation", Subtext: "(Fake Data)", - Left: charts.PositionCenter, + Offset: charts.OffsetCenter, FontStyle: charts.FontStyle{ FontSize: 16, }, @@ -57,7 +57,7 @@ func main() { "Union Ads", "Video Ads", }, - Left: charts.PositionLeft, + Offset: charts.OffsetLeft, FontStyle: charts.FontStyle{ FontSize: 12, }, diff --git a/examples/radar_chart/main.go b/examples/radar_chart/main.go index d7e17b7..3d9d8a3 100644 --- a/examples/radar_chart/main.go +++ b/examples/radar_chart/main.go @@ -53,7 +53,7 @@ func main() { "Allocated Budget", "Actual Spending", }, - Left: charts.PositionRight, + Offset: charts.OffsetRight, }), charts.RadarIndicatorOptionFunc([]string{ "Sales", diff --git a/examples/web-charts/main.go b/examples/web-charts/main.go index 9d71f56..d01b722 100644 --- a/examples/web-charts/main.go +++ b/examples/web-charts/main.go @@ -234,7 +234,7 @@ func indexHandler(w http.ResponseWriter, req *http.Request) { Legend: charts.NewLegendOption([]string{ "Highest", "Lowest", - }, charts.PositionRight), + }), XAxis: charts.NewXAxisOption([]string{ "Mon", "Tue", @@ -459,7 +459,7 @@ func indexHandler(w http.ResponseWriter, req *http.Request) { Legend: charts.NewLegendOption([]string{ "Rainfall", "Evaporation", - }, charts.PositionRight), + }), SeriesList: []charts.Series{ { Type: charts.ChartTypeBar, @@ -610,7 +610,7 @@ func indexHandler(w http.ResponseWriter, req *http.Request) { Title: charts.TitleOption{ Text: "Referer of a Website", Subtext: "(Fake Data)", - Left: charts.PositionCenter, + Offset: charts.OffsetCenter, FontStyle: charts.FontStyle{ FontSize: 16, }, @@ -627,7 +627,7 @@ func indexHandler(w http.ResponseWriter, req *http.Request) { "Union Ads", "Video Ads", }, - Left: charts.PositionLeft, + Offset: charts.OffsetLeft, }, SeriesList: charts.NewPieSeriesList([]float64{ 1048, @@ -761,7 +761,9 @@ func indexHandler(w http.ResponseWriter, req *http.Request) { // multiple picture { Legend: charts.LegendOption{ - Top: "-90", + Offset: charts.OffsetStr{ + Top: "-90", + }, Data: []string{ "Milk Tea", "Matcha Latte", diff --git a/legend.go b/legend.go index dae1613..bb4133d 100644 --- a/legend.go +++ b/legend.go @@ -19,18 +19,14 @@ type LegendOption struct { Show *bool // Theme specifies the colors used for the legend. Theme ColorPalette - // Padding specifies space padding around the legend. - Padding Box // Data provides text for the legend. Data []string // FontStyle specifies the font, size, and style for rendering the legend. FontStyle FontStyle - // Left is the distance between legend component and the left side of the container. - // It can be pixel value (20), percentage value (20%), or position description: 'left', 'right', 'center'. - Left string - // Top is the distance between legend component and the top side of the container. - // It can be pixel value (20), or percentage value (20%). - Top string + // Padding specifies space padding around the legend. + Padding Box + // Offset allows you to specify the position of the legend component relative to the left and top side. + Offset OffsetStr // Align is the legend marker and text alignment, it can be 'left' or 'right', default is 'left'. Align string // Orient is the layout orientation of legend, it can be 'horizontal' or 'vertical', default is 'horizontal'. @@ -40,13 +36,10 @@ type LegendOption struct { } // NewLegendOption returns a legend option -func NewLegendOption(labels []string, left ...string) LegendOption { +func NewLegendOption(labels []string) LegendOption { opt := LegendOption{ Data: labels, } - if len(left) != 0 { - opt.Left = left[0] - } return opt } @@ -85,16 +78,17 @@ func (l *legendPainter) Render() (Box, error) { if fontStyle.FontColor.IsZero() { fontStyle.FontColor = theme.GetTextColor() } - if opt.Left == "" { + offset := opt.Offset + if offset.Left == "" { if opt.Orient == OrientVertical { // in the vertical orientation it's more visually appealing to default to the right side or left side if opt.Align != "" { - opt.Left = opt.Align + offset.Left = opt.Align } else { - opt.Left = PositionLeft + offset.Left = PositionLeft } } else { - opt.Left = PositionCenter + offset.Left = PositionCenter } } padding := opt.Padding @@ -108,7 +102,7 @@ func (l *legendPainter) Render() (Box, error) { measureList := make([]Box, len(opt.Data)) width := 0 height := 0 - offset := 20 + builtInSpacing := 20 textOffset := 2 legendWidth := 30 legendHeight := 20 @@ -133,17 +127,17 @@ func (l *legendPainter) Render() (Box, error) { // add padding if opt.Orient == OrientVertical { width = maxTextWidth + textOffset + legendWidth - height = offset * len(opt.Data) + height = builtInSpacing * len(opt.Data) } else { height = legendHeight - offsetValue := (len(opt.Data) - 1) * (offset + textOffset) + offsetValue := (len(opt.Data) - 1) * (builtInSpacing + textOffset) allLegendWidth := len(opt.Data) * legendWidth width += offsetValue + allLegendWidth } // calculate starting position var left int - switch opt.Left { + switch offset.Left { case PositionLeft: // no-op case PositionRight: @@ -151,7 +145,7 @@ func (l *legendPainter) Render() (Box, error) { case PositionCenter: left = (p.Width() - width) >> 1 default: - if v, err := parseFlexibleValue(opt.Left, float64(p.Width())); err != nil { + if v, err := parseFlexibleValue(offset.Left, float64(p.Width())); err != nil { return BoxZero, err } else { left = int(v) @@ -162,8 +156,8 @@ func (l *legendPainter) Render() (Box, error) { } var top int - if opt.Top != "" { - if v, err := parseFlexibleValue(opt.Top, float64(p.Height())); err != nil { + if offset.Top != "" { + if v, err := parseFlexibleValue(offset.Top, float64(p.Height())); err != nil { return BoxZero, fmt.Errorf("unexpected parsing error: %v", err) } else { top = int(v) @@ -215,7 +209,7 @@ func (l *legendPainter) Render() (Box, error) { } } else { // check if item will overrun the right side boundary - itemWidth := x0 + measureList[index].Width() + textOffset + offset + legendWidth + itemWidth := x0 + measureList[index].Width() + textOffset + builtInSpacing + legendWidth if lastIndex == index { itemWidth = x0 + measureList[index].Width() + legendWidth } @@ -238,10 +232,10 @@ func (l *legendPainter) Render() (Box, error) { } if opt.Orient == OrientVertical { - y0 += offset + y0 += builtInSpacing x0 = x } else { - x0 += offset + x0 += builtInSpacing y0 = y } height = y0 - startY + 10 diff --git a/legend_test.go b/legend_test.go index 911f77b..0e1f8e0 100644 --- a/legend_test.go +++ b/legend_test.go @@ -35,8 +35,8 @@ func TestNewLegend(t *testing.T) { name: "position_left", render: func(p *Painter) ([]byte, error) { _, err := NewLegendPainter(p, LegendOption{ - Data: []string{"One", "Two", "Three"}, - Left: PositionLeft, + Data: []string{"One", "Two", "Three"}, + Offset: OffsetLeft, }).Render() if err != nil { return nil, err @@ -52,7 +52,9 @@ func TestNewLegend(t *testing.T) { Data: []string{"One", "Two", "Three"}, Orient: OrientVertical, Icon: IconRect, - Left: "10%", + Offset: OffsetStr{ + Left: "10%", + }, }).Render() if err != nil { return nil, err @@ -99,7 +101,7 @@ func TestNewLegend(t *testing.T) { _, err := NewLegendPainter(p, LegendOption{ Data: []string{"One", "Two Word", "Three Word Item", "Four Words Is Longer"}, Orient: OrientVertical, - Left: PositionRight, + Offset: OffsetRight, }).Render() if err != nil { return nil, err @@ -114,7 +116,7 @@ func TestNewLegend(t *testing.T) { _, err := NewLegendPainter(p, LegendOption{ Data: []string{"One", "Two Word", "Three Word Item", "Four Words Is Longer"}, Orient: OrientVertical, - Left: PositionRight, + Offset: OffsetRight, FontStyle: FontStyle{ FontSize: 6.0, }, @@ -132,7 +134,7 @@ func TestNewLegend(t *testing.T) { _, err := NewLegendPainter(p, LegendOption{ Data: []string{"One", "Two Word", "Three Word Item", "Four Words Is Longer"}, Orient: OrientVertical, - Left: PositionRight, + Offset: OffsetRight, Padding: Box{Top: 120, Left: 120, Right: 120, Bottom: 120}, }).Render() if err != nil { @@ -148,7 +150,7 @@ func TestNewLegend(t *testing.T) { _, err := NewLegendPainter(p, LegendOption{ Data: []string{"One", "Two Word", "Three Word Item", "Four Words Is Longer", "Five Words Is Even Longer", "Six Words Is The Longest Tested"}, - Left: PositionLeft, + Offset: OffsetLeft, }).Render() if err != nil { return nil, err @@ -164,7 +166,9 @@ func TestNewLegend(t *testing.T) { Data: []string{"One", "Two Word", "Three Word Item", "Four Words Is Longer", "Five Words Is Even Longer", "Six Words Is The Longest Tested"}, Orient: OrientVertical, - Left: "440", + Offset: OffsetStr{ + Left: "440", + }, }).Render() if err != nil { return nil, err @@ -208,7 +212,7 @@ func TestNewLegend(t *testing.T) { _, err := NewLegendPainter(p, LegendOption{ Data: []string{"One", "Two Word", "Three Word Item", "Four Words Is Longer"}, Orient: OrientVertical, - Left: PositionLeft, + Offset: OffsetLeft, Align: AlignRight, }).Render() if err != nil { diff --git a/line_chart_test.go b/line_chart_test.go index dcb76c6..289bd00 100644 --- a/line_chart_test.go +++ b/line_chart_test.go @@ -82,7 +82,7 @@ func makeFullLineChartOption() LineChartOption { "Video Ads", "Direct", "Search Engine", - }, PositionCenter), + }), SeriesList: NewSeriesListDataFromValues(values), } } @@ -127,7 +127,7 @@ func makeBasicLineChartOption() LineChartOption { "F", "G", }), - Legend: NewLegendOption([]string{"1", "2"}, PositionCenter), + Legend: NewLegendOption([]string{"1", "2"}), SeriesList: NewSeriesListDataFromValues(values), } } diff --git a/painter.go b/painter.go index a3475c3..2cd11ec 100644 --- a/painter.go +++ b/painter.go @@ -51,7 +51,7 @@ type MultiTextOption struct { CenterLabels bool Align string TextRotation float64 - Offset Offset + Offset OffsetInt // The first text index First int LabelCount int diff --git a/pie_chart_test.go b/pie_chart_test.go index da6c3cd..f2d56b7 100644 --- a/pie_chart_test.go +++ b/pie_chart_test.go @@ -24,7 +24,7 @@ func makeBasicPieChartOption() PieChartOption { Title: TitleOption{ Text: "Rainfall vs Evaporation", Subtext: "Fake Data", - Left: PositionCenter, + Offset: OffsetCenter, }, Padding: Box{ Top: 20, @@ -41,7 +41,7 @@ func makeBasicPieChartOption() PieChartOption { "Union Ads", "Video Ads", }, - Left: PositionLeft, + Offset: OffsetLeft, }, } } @@ -297,8 +297,8 @@ func TestPieChart(t *testing.T) { Radius: "150", }), Title: TitleOption{ - Text: "Fix label K (72586)", - Left: PositionRight, + Text: "Fix label K (72586)", + Offset: OffsetRight, }, Padding: Box{ Top: 20, diff --git a/series.go b/series.go index 26e6124..c1c7e40 100644 --- a/series.go +++ b/series.go @@ -58,7 +58,7 @@ type SeriesLabel struct { // Position defines the label position. Position string // Offset specifies an offset from the position. - Offset Offset + Offset OffsetInt } const ( diff --git a/series_label.go b/series_label.go index 9033414..9961721 100644 --- a/series_label.go +++ b/series_label.go @@ -22,7 +22,7 @@ type LabelValue struct { Radians float64 FontStyle FontStyle Orient string - Offset Offset + Offset OffsetInt } type SeriesLabelPainter struct { diff --git a/title.go b/title.go index 39a234a..a4bd90e 100644 --- a/title.go +++ b/title.go @@ -13,12 +13,8 @@ type TitleOption struct { Text string // Subtext to the title, supporting \n for new lines. Subtext string - // Left is the distance between title component and the left side of the container. - // It can be pixel value (20) or percentage value (20%), or position description: 'left', 'right', 'center'. - Left string - // Top is the distance between title component and the top side of the container. - // It can be pixel value (20) or percentage value (20%). - Top string + // Offset allows you to specify the position of the title component relative to the left and top side. + Offset OffsetStr // FontStyle specifies the font, size, and style for rendering the title. FontStyle FontStyle // SubtextFontStyle specifies the font, size, and style for rendering the subtext. @@ -129,8 +125,9 @@ func (t *titlePainter) Render() (Box, error) { } width := textMaxWidth + offset := opt.Offset titleX := 0 - switch opt.Left { + switch offset.Left { case "", PositionLeft: // no-op case PositionRight: @@ -138,15 +135,15 @@ func (t *titlePainter) Render() (Box, error) { case PositionCenter: titleX = p.Width()>>1 - (textMaxWidth >> 1) default: - if v, err := parseFlexibleValue(opt.Left, float64(p.Width())); err != nil { + if v, err := parseFlexibleValue(offset.Left, float64(p.Width())); err != nil { return BoxZero, err } else { titleX = int(v) } } titleY := 0 - if opt.Top != "" { - if v, err := parseFlexibleValue(opt.Top, float64(p.Height())); err != nil { + if offset.Top != "" { + if v, err := parseFlexibleValue(offset.Top, float64(p.Height())); err != nil { return BoxZero, err } else { titleY = int(v) diff --git a/title_test.go b/title_test.go index fd215e3..22d3708 100644 --- a/title_test.go +++ b/title_test.go @@ -23,8 +23,10 @@ func TestTitleRenderer(t *testing.T) { _, err := NewTitlePainter(p, TitleOption{ Text: "title", Subtext: "subTitle", - Left: "20", - Top: "20", + Offset: OffsetStr{ + Left: "20", + Top: "20", + }, }).Render() if err != nil { return nil, err @@ -39,8 +41,10 @@ func TestTitleRenderer(t *testing.T) { _, err := NewTitlePainter(p, TitleOption{ Text: "title", Subtext: "subTitle", - Left: "20%", - Top: "20", + Offset: OffsetStr{ + Left: "20%", + Top: "20", + }, }).Render() if err != nil { return nil, err @@ -55,7 +59,7 @@ func TestTitleRenderer(t *testing.T) { _, err := NewTitlePainter(p, TitleOption{ Text: "title", Subtext: "subTitle", - Left: PositionRight, + Offset: OffsetRight, }).Render() if err != nil { return nil, err diff --git a/xaxis.go b/xaxis.go index 504d8cc..4323a7b 100644 --- a/xaxis.go +++ b/xaxis.go @@ -19,7 +19,7 @@ type XAxisOption struct { // TextRotation are the radians for rotating the label. TextRotation float64 // LabelOffset is the offset of each label. - LabelOffset Offset + LabelOffset OffsetInt // Unit is a suggestion for how large the axis step is, this is a recommendation only. Larger numbers result in fewer labels. Unit float64 // LabelCount is the number of labels to show on the axis. Specify a smaller number to reduce writing collisions. From 587f2073c20f1f3035d3a70396f373938b44439d Mon Sep 17 00:00:00 2001 From: Mike Jensen Date: Mon, 16 Dec 2024 22:54:48 -0700 Subject: [PATCH 2/3] Change `Orient` configuration from string to `Vertical` bool Instead of specifying "vertical" as a string, the boolean can now be set directly. --- alias.go | 5 ----- axis.go | 11 +++------ bar_chart.go | 7 ++++-- chart_option_test.go | 2 +- charts.go | 2 +- echarts.go | 5 +++-- examples/line_chart-2/main.go | 2 +- examples/pie_chart/main.go | 2 +- examples/web-charts/main.go | 2 +- horizontal_bar_chart.go | 2 +- horizontal_bar_chart_test.go | 14 +++++++++++- legend.go | 14 ++++++------ legend_test.go | 42 +++++++++++++++++------------------ painter.go | 16 ++++++------- pie_chart_test.go | 2 +- series_label.go | 4 ++-- 16 files changed, 68 insertions(+), 64 deletions(-) diff --git a/alias.go b/alias.go index 6e56db4..eeb96c8 100644 --- a/alias.go +++ b/alias.go @@ -106,8 +106,3 @@ const ( AlignRight = "right" AlignCenter = "center" ) - -const ( - OrientHorizontal = "horizontal" - OrientVertical = "vertical" -) diff --git a/axis.go b/axis.go index 1511705..36ce234 100644 --- a/axis.go +++ b/axis.go @@ -150,7 +150,6 @@ func (a *axisPainter) Render() (Box, error) { labelPaddingTop := 0 labelPaddingLeft := 0 labelPaddingRight := 0 - orient := "" textAlign := "" switch opt.Position { @@ -160,31 +159,27 @@ func (a *axisPainter) Render() (Box, error) { y0 = labelMargin + int(opt.FontStyle.FontSize) ticksPaddingTop = int(opt.FontStyle.FontSize) y1 = y0 - orient = OrientHorizontal case PositionLeft: x0 = p.Width() y0 = 0 x1 = p.Width() y1 = p.Height() - orient = OrientVertical textAlign = AlignRight ticksPaddingLeft = textMaxWidth + tickLength labelPaddingRight = width - textMaxWidth case PositionRight: - orient = OrientVertical y1 = p.Height() labelPaddingLeft = width - textMaxWidth default: labelPaddingTop = height x1 = p.Width() - orient = OrientHorizontal } labelCount := opt.LabelCount if labelCount <= 0 { var maxLabelCount int // Add 10px and remove one for some minimal extra padding so that letters don't collide - if orient == OrientVertical { + if isVertical { maxLabelCount = (top.Height() / (textMaxHeight + 10)) - 1 } else { maxLabelCount = (top.Width() / (textMaxWidth + 10)) - 1 @@ -227,7 +222,7 @@ func (a *axisPainter) Render() (Box, error) { LabelCount: labelCount, TickSpaces: tickSpaces, Length: tickLength, - Orient: orient, + Vertical: isVertical, First: opt.DataStartIndex, }) p.LineStroke([]Point{ @@ -245,7 +240,7 @@ func (a *axisPainter) Render() (Box, error) { First: opt.DataStartIndex, Align: textAlign, TextList: opt.Data, - Orient: orient, + Vertical: isVertical, LabelCount: labelCount, LabelSkipCount: opt.LabelSkipCount, CenterLabels: centerLabels, diff --git a/bar_chart.go b/bar_chart.go index 705d31e..f2c903f 100644 --- a/bar_chart.go +++ b/bar_chart.go @@ -117,18 +117,20 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B FillColor: fillColor, }) if flagIs(true, opt.RoundedBarCaps) { - seriesPainter.RoundedRect(chartdraw.Box{ + seriesPainter.RoundedRect(Box{ Top: top, Left: x, Right: x + barWidth, Bottom: barMaxHeight - 1, + IsSet: true, }, barWidth, true, false) } else { - seriesPainter.Rect(chartdraw.Box{ + seriesPainter.Rect(Box{ Top: top, Left: x, Right: x + barWidth, Bottom: barMaxHeight - 1, + IsSet: true, }) } // generate marker point by hand @@ -156,6 +158,7 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B } } labelPainter.Add(LabelValue{ + Vertical: true, // label is above bar Index: index, Value: item.Value, FontStyle: fontStyle, diff --git a/chart_option_test.go b/chart_option_test.go index 41e6d4b..abc0c78 100644 --- a/chart_option_test.go +++ b/chart_option_test.go @@ -316,7 +316,7 @@ func TestPieRender(t *testing.T) { Left: 20, }), LegendOptionFunc(LegendOption{ - Orient: OrientVertical, + Vertical: true, Data: []string{ "Search Engine", "Direct", diff --git a/charts.go b/charts.go index 350a556..ad93aed 100644 --- a/charts.go +++ b/charts.go @@ -135,7 +135,7 @@ func defaultRender(p *Painter, opt defaultRenderOption) (*defaultRenderResult, e top := chartdraw.MaxInt(legendHeight, titleBox.Height()) // if in vertical mode, the legend height is not calculated - if opt.Legend.Orient == OrientVertical { + if opt.Legend.Vertical { top = titleBox.Height() } p = p.Child(PainterPaddingOption(Box{ diff --git a/echarts.go b/echarts.go index e5066cc..4779d15 100644 --- a/echarts.go +++ b/echarts.go @@ -6,6 +6,7 @@ import ( "fmt" "regexp" "strconv" + "strings" "github.com/go-analyze/charts/chartdraw" ) @@ -415,8 +416,8 @@ func (eo *EChartsOption) ToOption() ChartOption { Left: string(eo.Legend.Left), Top: string(eo.Legend.Top), }, - Align: eo.Legend.Align, - Orient: eo.Legend.Orient, + Align: eo.Legend.Align, + Vertical: strings.EqualFold(eo.Legend.Orient, "vertical"), }, RadarIndicators: eo.Radar.Indicator, Width: eo.Width, diff --git a/examples/line_chart-2/main.go b/examples/line_chart-2/main.go index 1ea944c..e7f482b 100644 --- a/examples/line_chart-2/main.go +++ b/examples/line_chart-2/main.go @@ -62,7 +62,7 @@ func main() { func(opt *charts.ChartOption) { opt.Legend.Offset = charts.OffsetRight opt.Legend.Align = charts.AlignRight - opt.Legend.Orient = charts.OrientVertical + opt.Legend.Vertical = true opt.Legend.FontStyle.FontSize = 6 opt.Title.Offset = charts.OffsetCenter opt.SymbolShow = charts.False() diff --git a/examples/pie_chart/main.go b/examples/pie_chart/main.go index c4f7d04..35cba29 100644 --- a/examples/pie_chart/main.go +++ b/examples/pie_chart/main.go @@ -49,7 +49,7 @@ func main() { Left: 20, }), charts.LegendOptionFunc(charts.LegendOption{ - Orient: charts.OrientVertical, + Vertical: true, Data: []string{ "Search Engine", "Direct", diff --git a/examples/web-charts/main.go b/examples/web-charts/main.go index d01b722..d5ed174 100644 --- a/examples/web-charts/main.go +++ b/examples/web-charts/main.go @@ -619,7 +619,7 @@ func indexHandler(w http.ResponseWriter, req *http.Request) { }, }, Legend: charts.LegendOption{ - Orient: charts.OrientVertical, + Vertical: true, Data: []string{ "Search Engine", "Direct", diff --git a/horizontal_bar_chart.go b/horizontal_bar_chart.go index f5394c7..e2dc0a4 100644 --- a/horizontal_bar_chart.go +++ b/horizontal_bar_chart.go @@ -125,7 +125,7 @@ func (h *horizontalBarChart) render(result *defaultRenderResult, seriesList Seri } } labelValue := LabelValue{ - Orient: OrientHorizontal, + Vertical: false, Index: index, Value: item.Value, X: right, diff --git a/horizontal_bar_chart_test.go b/horizontal_bar_chart_test.go index e6d6ccd..7b8c320 100644 --- a/horizontal_bar_chart_test.go +++ b/horizontal_bar_chart_test.go @@ -74,7 +74,6 @@ func TestHorizontalBarChart(t *testing.T) { makeOptions: makeBasicHorizontalBarChartOption, result: "20112012World PopulationWorldChinaIndiaUSAIndonesiaBrazil0144k288k432k576k720k", }, - { name: "custom_fonts", defaultTheme: true, @@ -91,6 +90,19 @@ func TestHorizontalBarChart(t *testing.T) { }, result: "20112012World PopulationWorldChinaIndiaUSAIndonesiaBrazil0144k288k432k576k720k", }, + { + name: "value_labels", + defaultTheme: true, + makeOptions: func() HorizontalBarChartOption { + opt := makeBasicHorizontalBarChartOption() + series := opt.SeriesList + for i := range series { + series[i].Label.Show = true + } + return opt + }, + result: "20112012World PopulationWorldChinaIndiaUSAIndonesiaBrazil0144k288k432k576k720k182032348929034104970131744630230193252343831000121594134141681807", + }, } for i, tt := range tests { diff --git a/legend.go b/legend.go index bb4133d..c2cca63 100644 --- a/legend.go +++ b/legend.go @@ -29,8 +29,8 @@ type LegendOption struct { Offset OffsetStr // Align is the legend marker and text alignment, it can be 'left' or 'right', default is 'left'. Align string - // Orient is the layout orientation of legend, it can be 'horizontal' or 'vertical', default is 'horizontal'. - Orient string + // Vertical can be set to true to set the orientation to be vertical. + Vertical bool // Icon to show next to the labels. Can be 'rect' or 'dot'. Icon string } @@ -80,7 +80,7 @@ func (l *legendPainter) Render() (Box, error) { } offset := opt.Offset if offset.Left == "" { - if opt.Orient == OrientVertical { + if opt.Vertical { // in the vertical orientation it's more visually appealing to default to the right side or left side if opt.Align != "" { offset.Left = opt.Align @@ -116,7 +116,7 @@ func (l *legendPainter) Render() (Box, error) { if b.Height() > itemMaxHeight { itemMaxHeight = b.Height() } - if opt.Orient == OrientVertical { + if opt.Vertical { height += b.Height() } else { width += b.Width() @@ -125,7 +125,7 @@ func (l *legendPainter) Render() (Box, error) { } // add padding - if opt.Orient == OrientVertical { + if opt.Vertical { width = maxTextWidth + textOffset + legendWidth height = builtInSpacing * len(opt.Data) } else { @@ -202,7 +202,7 @@ func (l *legendPainter) Render() (Box, error) { FillColor: color, StrokeColor: color, }) - if opt.Orient == OrientVertical { + if opt.Vertical { if opt.Align == AlignRight { // adjust x0 so that the text will start with a right alignment to the longest line x0 += maxTextWidth - measureList[index].Width() @@ -231,7 +231,7 @@ func (l *legendPainter) Render() (Box, error) { x0 = drawIcon(y0, x0) } - if opt.Orient == OrientVertical { + if opt.Vertical { y0 += builtInSpacing x0 = x } else { diff --git a/legend_test.go b/legend_test.go index 0e1f8e0..a02e428 100644 --- a/legend_test.go +++ b/legend_test.go @@ -49,9 +49,9 @@ func TestNewLegend(t *testing.T) { name: "position_vertical_with_rect", render: func(p *Painter) ([]byte, error) { _, err := NewLegendPainter(p, LegendOption{ - Data: []string{"One", "Two", "Three"}, - Orient: OrientVertical, - Icon: IconRect, + Data: []string{"One", "Two", "Three"}, + Vertical: true, + Icon: IconRect, Offset: OffsetStr{ Left: "10%", }, @@ -99,9 +99,9 @@ func TestNewLegend(t *testing.T) { name: "vertical_right_position", render: func(p *Painter) ([]byte, error) { _, err := NewLegendPainter(p, LegendOption{ - Data: []string{"One", "Two Word", "Three Word Item", "Four Words Is Longer"}, - Orient: OrientVertical, - Offset: OffsetRight, + Data: []string{"One", "Two Word", "Three Word Item", "Four Words Is Longer"}, + Vertical: true, + Offset: OffsetRight, }).Render() if err != nil { return nil, err @@ -114,9 +114,9 @@ func TestNewLegend(t *testing.T) { name: "vertical_right_position_custom_font_size", render: func(p *Painter) ([]byte, error) { _, err := NewLegendPainter(p, LegendOption{ - Data: []string{"One", "Two Word", "Three Word Item", "Four Words Is Longer"}, - Orient: OrientVertical, - Offset: OffsetRight, + Data: []string{"One", "Two Word", "Three Word Item", "Four Words Is Longer"}, + Vertical: true, + Offset: OffsetRight, FontStyle: FontStyle{ FontSize: 6.0, }, @@ -132,10 +132,10 @@ func TestNewLegend(t *testing.T) { name: "vertical_right_position_with_padding", render: func(p *Painter) ([]byte, error) { _, err := NewLegendPainter(p, LegendOption{ - Data: []string{"One", "Two Word", "Three Word Item", "Four Words Is Longer"}, - Orient: OrientVertical, - Offset: OffsetRight, - Padding: Box{Top: 120, Left: 120, Right: 120, Bottom: 120}, + Data: []string{"One", "Two Word", "Three Word Item", "Four Words Is Longer"}, + Vertical: true, + Offset: OffsetRight, + Padding: Box{Top: 120, Left: 120, Right: 120, Bottom: 120}, }).Render() if err != nil { return nil, err @@ -165,7 +165,7 @@ func TestNewLegend(t *testing.T) { _, err := NewLegendPainter(p, LegendOption{ Data: []string{"One", "Two Word", "Three Word Item", "Four Words Is Longer", "Five Words Is Even Longer", "Six Words Is The Longest Tested"}, - Orient: OrientVertical, + Vertical: true, Offset: OffsetStr{ Left: "440", }, @@ -195,9 +195,9 @@ func TestNewLegend(t *testing.T) { name: "vertical_right_alignment", render: func(p *Painter) ([]byte, error) { _, err := NewLegendPainter(p, LegendOption{ - Data: []string{"One", "Two Word", "Three Word Item", "Four Words Is Longer"}, - Orient: OrientVertical, - Align: AlignRight, + Data: []string{"One", "Two Word", "Three Word Item", "Four Words Is Longer"}, + Vertical: true, + Align: AlignRight, }).Render() if err != nil { return nil, err @@ -210,10 +210,10 @@ func TestNewLegend(t *testing.T) { name: "vertical_right_alignment_left_position", render: func(p *Painter) ([]byte, error) { _, err := NewLegendPainter(p, LegendOption{ - Data: []string{"One", "Two Word", "Three Word Item", "Four Words Is Longer"}, - Orient: OrientVertical, - Offset: OffsetLeft, - Align: AlignRight, + Data: []string{"One", "Two Word", "Three Word Item", "Four Words Is Longer"}, + Vertical: true, + Offset: OffsetLeft, + Align: AlignRight, }).Render() if err != nil { return nil, err diff --git a/painter.go b/painter.go index 2cd11ec..e5b4814 100644 --- a/painter.go +++ b/painter.go @@ -40,14 +40,14 @@ type TicksOption struct { // the first tick index First int Length int - Orient string + Vertical bool LabelCount int TickSpaces int } type MultiTextOption struct { TextList []string - Orient string + Vertical bool CenterLabels bool Align string TextRotation float64 @@ -621,8 +621,7 @@ func (p *Painter) Ticks(opt TicksOption) *Painter { return p } var values []int - isVertical := opt.Orient == OrientVertical - if isVertical { + if opt.Vertical { values = autoDivide(p.Height(), opt.TickSpaces) } else { values = autoDivide(p.Width(), opt.TickSpaces) @@ -633,7 +632,7 @@ func (p *Painter) Ticks(opt TicksOption) *Painter { } else if !isTick(len(values)-opt.First, opt.LabelCount+1, index-opt.First) { continue } - if isVertical { + if opt.Vertical { p.LineStroke([]Point{ {X: 0, Y: value}, {X: opt.Length, Y: value}, @@ -656,8 +655,7 @@ func (p *Painter) MultiText(opt MultiTextOption) *Painter { width := p.Width() height := p.Height() var positions []int - isVertical := opt.Orient == OrientVertical - if isVertical { + if opt.Vertical { if opt.CenterLabels { positions = autoDivide(height, count) } else { @@ -679,7 +677,7 @@ func (p *Painter) MultiText(opt MultiTextOption) *Painter { break // positions have one item more than we can map to text, this extra value is used to center against } else if index < opt.First { continue - } else if !isVertical && + } else if !opt.Vertical && index != count-1 && // one off case for last label due to values and label qty difference !isTick(positionCount-opt.First, opt.LabelCount+1, index-opt.First) { continue @@ -699,7 +697,7 @@ func (p *Painter) MultiText(opt MultiTextOption) *Painter { box := p.MeasureText(text) x := 0 y := 0 - if isVertical { + if opt.Vertical { if opt.CenterLabels { start = (positions[index] + positions[index+1]) >> 1 } else { diff --git a/pie_chart_test.go b/pie_chart_test.go index f2d56b7..7c7f04e 100644 --- a/pie_chart_test.go +++ b/pie_chart_test.go @@ -33,7 +33,7 @@ func makeBasicPieChartOption() PieChartOption { Left: 20, }, Legend: LegendOption{ - Orient: OrientVertical, + Vertical: true, Data: []string{ "Search Engine", "Direct", diff --git a/series_label.go b/series_label.go index 9961721..b9b86a1 100644 --- a/series_label.go +++ b/series_label.go @@ -21,7 +21,7 @@ type LabelValue struct { Y int Radians float64 FontStyle FontStyle - Orient string + Vertical bool Offset OffsetInt } @@ -89,7 +89,7 @@ func (o *SeriesLabelPainter) Add(value LabelValue) { Y: value.Y, Radians: value.Radians, } - if value.Orient != OrientHorizontal { + if value.Vertical { renderValue.X -= textBox.Width() >> 1 renderValue.Y -= distance } else { From f84fa28ee341301089c6e4b4f6989f5f99812633 Mon Sep 17 00:00:00 2001 From: Mike Jensen Date: Tue, 17 Dec 2024 08:04:37 -0700 Subject: [PATCH 3/3] Removed functions `NewLegendOption` `NewXAxisOption` `NewYAxisOptions` In general we expect the configuration structs to be made directly by the user. These helper functions were not well documented and outside of how other configuration structs are initialized. For that reason we removed these three functions, and instead expect the struct to be initialized directly. --- bar_chart_test.go | 42 ++++--- chart_option.go | 16 ++- charts_test.go | 36 +++--- examples/web-charts/main.go | 231 +++++++++++++++++++---------------- funnel_chart_test.go | 16 +-- horizontal_bar_chart_test.go | 30 +++-- legend.go | 8 -- line_chart_test.go | 60 +++++---- radar_chart_test.go | 10 +- xaxis.go | 11 -- yaxis.go | 15 --- yaxis_test.go | 4 +- 12 files changed, 255 insertions(+), 224 deletions(-) diff --git a/bar_chart_test.go b/bar_chart_test.go index 9318202..02a59aa 100644 --- a/bar_chart_test.go +++ b/bar_chart_test.go @@ -51,24 +51,30 @@ func makeBasicBarChartOption() BarChartOption { Bottom: 10, }, SeriesList: seriesList, - XAxis: NewXAxisOption([]string{ - "Jan", - "Feb", - "Mar", - "Apr", - "May", - "Jun", - "Jul", - "Aug", - "Sep", - "Oct", - "Nov", - "Dec", - }), - YAxis: NewYAxisOptions([]string{ - "Rainfall", - "Evaporation", - }), + XAxis: XAxisOption{ + Data: []string{ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + }, + }, + YAxis: []YAxisOption{ + { + Data: []string{ + "Rainfall", + "Evaporation", + }, + }, + }, } } diff --git a/chart_option.go b/chart_option.go index 75af16b..fa9238e 100644 --- a/chart_option.go +++ b/chart_option.go @@ -122,7 +122,9 @@ func LegendOptionFunc(legend LegendOption) OptionFunc { // LegendLabelsOptionFunc set legend labels of chart func LegendLabelsOptionFunc(labels []string) OptionFunc { return func(opt *ChartOption) { - opt.Legend = NewLegendOption(labels) + opt.Legend = LegendOption{ + Data: labels, + } } } @@ -134,9 +136,11 @@ func XAxisOptionFunc(xAxisOption XAxisOption) OptionFunc { } // XAxisDataOptionFunc set x-axis data of chart -func XAxisDataOptionFunc(data []string, boundaryGap ...*bool) OptionFunc { +func XAxisDataOptionFunc(data []string) OptionFunc { return func(opt *ChartOption) { - opt.XAxis = NewXAxisOption(data, boundaryGap...) + opt.XAxis = XAxisOption{ + Data: data, + } } } @@ -150,7 +154,11 @@ func YAxisOptionFunc(yAxisOption ...YAxisOption) OptionFunc { // YAxisDataOptionFunc set y-axis data of chart func YAxisDataOptionFunc(data []string) OptionFunc { return func(opt *ChartOption) { - opt.YAxis = NewYAxisOptions(data) + opt.YAxis = []YAxisOption{ + { + Data: data, + }, + } } } diff --git a/charts_test.go b/charts_test.go index d30b473..b543540 100644 --- a/charts_test.go +++ b/charts_test.go @@ -28,14 +28,16 @@ func BenchmarkMultiChartPNGRender(b *testing.B) { Bottom: 10, Left: 10, }, - XAxis: NewXAxisOption([]string{ - "2012", - "2013", - "2014", - "2015", - "2016", - "2017", - }), + XAxis: XAxisOption{ + Data: []string{ + "2012", + "2013", + "2014", + "2015", + "2016", + "2017", + }, + }, YAxis: []YAxisOption{ { @@ -139,14 +141,16 @@ func BenchmarkMultiChartSVGRender(b *testing.B) { Bottom: 10, Left: 10, }, - XAxis: NewXAxisOption([]string{ - "2012", - "2013", - "2014", - "2015", - "2016", - "2017", - }), + XAxis: XAxisOption{ + Data: []string{ + "2012", + "2013", + "2014", + "2015", + "2016", + "2017", + }, + }, YAxis: []YAxisOption{ { Min: FloatPointer(0), diff --git a/examples/web-charts/main.go b/examples/web-charts/main.go index d5ed174..971b76f 100644 --- a/examples/web-charts/main.go +++ b/examples/web-charts/main.go @@ -163,15 +163,17 @@ func indexHandler(w http.ResponseWriter, req *http.Request) { }, Padding: charts.Box{Left: 100}, }, - XAxis: charts.NewXAxisOption([]string{ - "Mon", - "Tue", - "Wed", - "Thu", - "Fri", - "Sat", - "Sun", - }), + XAxis: charts.XAxisOption{ + Data: []string{ + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + "Sun", + }, + }, SeriesList: []charts.Series{ charts.NewSeriesFromValues([]float64{ 120, @@ -231,19 +233,24 @@ func indexHandler(w http.ResponseWriter, req *http.Request) { Right: 30, Bottom: 20, }, - Legend: charts.NewLegendOption([]string{ - "Highest", - "Lowest", - }), - XAxis: charts.NewXAxisOption([]string{ - "Mon", - "Tue", - "Wed", - "Thu", - "Fri", - "Sat", - "Sun", - }, charts.False()), + Legend: charts.LegendOption{ + Data: []string{ + "Highest", + "Lowest", + }, + }, + XAxis: charts.XAxisOption{ + Data: []string{ + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + "Sun", + }, + BoundaryGap: charts.False(), + }, SeriesList: []charts.Series{ { Data: charts.NewSeriesDataFromValues([]float64{ @@ -279,18 +286,20 @@ func indexHandler(w http.ResponseWriter, req *http.Request) { FontSize: 18, }, }, - Legend: charts.NewLegendOption([]string{ - "Email", - }), - XAxis: charts.NewXAxisOption([]string{ - "Mon", - "Tue", - "Wed", - "Thu", - "Fri", - "Sat", - "Sun", - }), + Legend: charts.LegendOption{ + Data: []string{"Email"}, + }, + XAxis: charts.XAxisOption{ + Data: []string{ + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + "Sun", + }, + }, YAxis: []charts.YAxisOption{{ Min: charts.FloatPointer(0.0), // ensure y-axis starts at 0 }}, @@ -315,15 +324,17 @@ func indexHandler(w http.ResponseWriter, req *http.Request) { FontSize: 18, }, }, - XAxis: charts.NewXAxisOption([]string{ - "Mon", - "Tue", - "Wed", - "Thu", - "Fri", - "Sat", - "Sun", - }), + XAxis: charts.XAxisOption{ + Data: []string{ + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + "Sun", + }, + }, Legend: charts.LegendOption{ Data: []string{ "Rainfall", @@ -387,18 +398,24 @@ func indexHandler(w http.ResponseWriter, req *http.Request) { Bottom: 20, Left: 20, }, - Legend: charts.NewLegendOption([]string{ - "2011", - "2012", - }), - YAxis: charts.NewYAxisOptions([]string{ - "Brazil", - "Indonesia", - "USA", - "India", - "China", - "World", - }), + Legend: charts.LegendOption{ + Data: []string{ + "2011", + "2012", + }, + }, + YAxis: []charts.YAxisOption{ + { + Data: []string{ + "Brazil", + "Indonesia", + "USA", + "India", + "China", + "World", + }, + }, + }, SeriesList: []charts.Series{ { Type: charts.ChartTypeHorizontalBar, @@ -442,24 +459,28 @@ func indexHandler(w http.ResponseWriter, req *http.Request) { Bottom: 20, Left: 20, }, - XAxis: charts.NewXAxisOption([]string{ - "Jan", - "Feb", - "Mar", - "Apr", - "May", - "Jun", - "Jul", - "Aug", - "Sep", - "Oct", - "Nov", - "Dec", - }), - Legend: charts.NewLegendOption([]string{ - "Rainfall", - "Evaporation", - }), + XAxis: charts.XAxisOption{ + Data: []string{ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + }, + }, + Legend: charts.LegendOption{ + Data: []string{ + "Rainfall", + "Evaporation", + }, + }, SeriesList: []charts.Series{ { Type: charts.ChartTypeBar, @@ -519,20 +540,22 @@ func indexHandler(w http.ResponseWriter, req *http.Request) { FontSize: 16, }, }, - XAxis: charts.NewXAxisOption([]string{ - "Jan", - "Feb", - "Mar", - "Apr", - "May", - "Jun", - "Jul", - "Aug", - "Sep", - "Oct", - "Nov", - "Dec", - }), + XAxis: charts.XAxisOption{ + Data: []string{ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + }, + }, Legend: charts.LegendOption{ Data: []string{ "Evaporation", @@ -713,13 +736,15 @@ func indexHandler(w http.ResponseWriter, req *http.Request) { Title: charts.TitleOption{ Text: "Funnel", }, - Legend: charts.NewLegendOption([]string{ - "Show", - "Click", - "Visit", - "Inquiry", - "Order", - }), + Legend: charts.LegendOption{ + Data: []string{ + "Show", + "Click", + "Visit", + "Inquiry", + "Order", + }, + }, SeriesList: []charts.Series{ { Type: charts.ChartTypeFunnel, @@ -777,14 +802,16 @@ func indexHandler(w http.ResponseWriter, req *http.Request) { Bottom: 10, Left: 10, }, - XAxis: charts.NewXAxisOption([]string{ - "2012", - "2013", - "2014", - "2015", - "2016", - "2017", - }), + XAxis: charts.XAxisOption{ + Data: []string{ + "2012", + "2013", + "2014", + "2015", + "2016", + "2017", + }, + }, YAxis: []charts.YAxisOption{ { diff --git a/funnel_chart_test.go b/funnel_chart_test.go index 82249f7..886b397 100644 --- a/funnel_chart_test.go +++ b/funnel_chart_test.go @@ -16,13 +16,15 @@ func makeBasicFunnelChartOption() FunnelChartOption { 40, 20, }), - Legend: NewLegendOption([]string{ - "Show", - "Click", - "Visit", - "Inquiry", - "Order", - }), + Legend: LegendOption{ + Data: []string{ + "Show", + "Click", + "Visit", + "Inquiry", + "Order", + }, + }, Title: TitleOption{ Text: "Funnel", }, diff --git a/horizontal_bar_chart_test.go b/horizontal_bar_chart_test.go index 7b8c320..e923f00 100644 --- a/horizontal_bar_chart_test.go +++ b/horizontal_bar_chart_test.go @@ -38,18 +38,24 @@ func makeBasicHorizontalBarChartOption() HorizontalBarChartOption { Title: TitleOption{ Text: "World Population", }, - Legend: NewLegendOption([]string{ - "2011", - "2012", - }), - YAxis: NewYAxisOptions([]string{ - "Brazil", - "Indonesia", - "USA", - "India", - "China", - "World", - }), + Legend: LegendOption{ + Data: []string{ + "2011", + "2012", + }, + }, + YAxis: []YAxisOption{ + { + Data: []string{ + "Brazil", + "Indonesia", + "USA", + "India", + "China", + "World", + }, + }, + }, } } diff --git a/legend.go b/legend.go index c2cca63..26970c6 100644 --- a/legend.go +++ b/legend.go @@ -35,14 +35,6 @@ type LegendOption struct { Icon string } -// NewLegendOption returns a legend option -func NewLegendOption(labels []string) LegendOption { - opt := LegendOption{ - Data: labels, - } - return opt -} - // IsEmpty checks legend is empty func (opt *LegendOption) IsEmpty() bool { for _, v := range opt.Data { diff --git a/line_chart_test.go b/line_chart_test.go index 289bd00..f8d195d 100644 --- a/line_chart_test.go +++ b/line_chart_test.go @@ -67,22 +67,26 @@ func makeFullLineChartOption() LineChartOption { Bottom: 10, Left: 10, }, - XAxis: NewXAxisOption([]string{ - "Mon", - "Tue", - "Wed", - "Thu", - "Fri", - "Sat", - "Sun", - }), - Legend: NewLegendOption([]string{ - "Email", - "Union Ads", - "Video Ads", - "Direct", - "Search Engine", - }), + XAxis: XAxisOption{ + Data: []string{ + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + "Sun", + }, + }, + Legend: LegendOption{ + Data: []string{ + "Email", + "Union Ads", + "Video Ads", + "Direct", + "Search Engine", + }, + }, SeriesList: NewSeriesListDataFromValues(values), } } @@ -118,16 +122,20 @@ func makeBasicLineChartOption() LineChartOption { Bottom: 10, Left: 10, }, - XAxis: NewXAxisOption([]string{ - "A", - "B", - "C", - "D", - "E", - "F", - "G", - }), - Legend: NewLegendOption([]string{"1", "2"}), + XAxis: XAxisOption{ + Data: []string{ + "A", + "B", + "C", + "D", + "E", + "F", + "G", + }, + }, + Legend: LegendOption{ + Data: []string{"1", "2"}, + }, SeriesList: NewSeriesListDataFromValues(values), } } diff --git a/radar_chart_test.go b/radar_chart_test.go index 1156afd..b6c64da 100644 --- a/radar_chart_test.go +++ b/radar_chart_test.go @@ -31,10 +31,12 @@ func makeBasicRadarChartOption() RadarChartOption { Title: TitleOption{ Text: "Basic Radar Chart", }, - Legend: NewLegendOption([]string{ - "Allocated Budget", - "Actual Spending", - }), + Legend: LegendOption{ + Data: []string{ + "Allocated Budget", + "Actual Spending", + }, + }, RadarIndicators: NewRadarIndicators([]string{ "Sales", "Administration", diff --git a/xaxis.go b/xaxis.go index 4323a7b..ec4259c 100644 --- a/xaxis.go +++ b/xaxis.go @@ -32,17 +32,6 @@ type XAxisOption struct { const defaultXAxisHeight = 30 -// NewXAxisOption returns a x axis option -func NewXAxisOption(data []string, boundaryGap ...*bool) XAxisOption { - opt := XAxisOption{ - Data: data, - } - if len(boundaryGap) != 0 { - opt.BoundaryGap = boundaryGap[0] - } - return opt -} - func (opt *XAxisOption) ToAxisOption() AxisOption { position := PositionBottom if opt.Position == PositionTop { diff --git a/yaxis.go b/yaxis.go index 3d25e7c..18f5b20 100644 --- a/yaxis.go +++ b/yaxis.go @@ -35,21 +35,6 @@ type YAxisOption struct { SplitLineShow *bool } -// NewYAxisOptions returns a y-axis option -func NewYAxisOptions(data []string, others ...[]string) []YAxisOption { - arr := [][]string{ - data, - } - arr = append(arr, others...) - opts := make([]YAxisOption, 0) - for _, data := range arr { - opts = append(opts, YAxisOption{ - Data: data, - }) - } - return opts -} - func (opt *YAxisOption) ToAxisOption(p *Painter) AxisOption { position := PositionLeft if opt.Position == PositionRight { diff --git a/yaxis_test.go b/yaxis_test.go index 2c628eb..1729f4e 100644 --- a/yaxis_test.go +++ b/yaxis_test.go @@ -16,7 +16,9 @@ func TestRightYAxis(t *testing.T) { }{ { render: func(p *Painter) ([]byte, error) { - opt := NewYAxisOptions([]string{"a", "b", "c", "d"})[0] + opt := YAxisOption{ + Data: []string{"a", "b", "c", "d"}, + } _, err := NewRightYAxis(p, opt).Render() if err != nil { return nil, err