From 6b74fa021e66c2be6765556c4618c4895b697dec Mon Sep 17 00:00:00 2001 From: Mike Jensen Date: Sun, 11 Feb 2024 12:09:15 -0700 Subject: [PATCH] Config API updates and improved go docs These changes are designed to improve consistency and simplicity of chart configuration. General Changes * Public config struct fields have had their docs updated and improved. * `Type` has been renamed to `OutputFormat`, this better describes the format that you want the chart rendered to. * Heavier theme use - color specifications that were already possible to derive from the theme have been removed, instead the theme is now just always used. As a future change we will allow easier theme modifications to more easily allow one off customization. * `TrueFlag()` and `FlaseFlag()` have been renamed to just `True()` and `False()` * `NewFloatPoint(float64)` has been renamed to `FloatPointer` * Fields with `Option` in their name have had the `Option` removed. This makes names more concise, and for `YAxisOptions` renaming to `YAxis` made it more consistent when compared to `XAxis`. Axis-Specific Changes * YAxisOptions field renamed to `YAxis` as mentioned above. * `FirstAxis` has been renamed to the more descriptive `DataStartIndex` * `StrokeColor` and `SplitLineColor` has been removed (as mentioned above, theme is now used). You can use `WithAxisColor(Color)` to easily change an existing theme to a different axis color. We plan to improve this API further. * YAxis `Color` has been renamed to the more descriptive `AxisColor`. Depending on how easy we can make theme adjustments this field may be deprecated. It is preferred to modify the theme using `WithAxisColor(Color)`. Chart-Specific Changes * Line charts `Opacity` has been renamed to the more descriptive `FillOpacity` (avoid confusion with other shading opacity) * For Grid and Table charts `Column` and `Row` were made plural into `Columns` and `Rows` * For Table charts `Padding` was renamed to the more descriptive `CellPadding` (avoid confusion with chart padding). --- alias.go | 16 ++-- axis.go | 93 ++++++++------------- axis_test.go | 35 ++++---- bar_chart.go | 39 ++++----- bar_chart_test.go | 8 +- chart_option.go | 137 ++++++++++++++----------------- chart_option_test.go | 62 +++++--------- charts.go | 85 ++++++++++--------- charts_test.go | 19 ++--- echarts.go | 20 ++--- echarts_test.go | 22 ++--- examples/charts/main.go | 41 +++------ examples/line_chart/main.go | 2 +- examples/painter/main.go | 123 +++++++++------------------ examples/time_line_chart/main.go | 6 +- font_test.go | 8 +- funnel_chart.go | 24 +++--- funnel_chart_test.go | 6 +- grid.go | 30 +++---- grid_test.go | 18 ++-- horizontal_bar_chart.go | 29 +++---- horizontal_bar_chart_test.go | 8 +- legend.go | 78 ++++++++++-------- legend_test.go | 6 +- line_chart.go | 44 +++++----- line_chart_test.go | 33 ++++---- mark_line_test.go | 6 +- mark_point_test.go | 6 +- painter.go | 65 ++++++--------- painter_test.go | 18 ++-- pie_chart.go | 26 +++--- pie_chart_test.go | 6 +- radar_chart.go | 39 ++++----- radar_chart_test.go | 6 +- series.go | 35 ++++---- table.go | 123 +++++++++------------------ table_test.go | 6 +- theme.go | 12 +++ theme_test.go | 32 +++++++- title.go | 55 +++++++------ title_test.go | 6 +- util.go | 57 ++++++++----- util_test.go | 31 ++++++- xaxis.go | 44 +++++----- yaxis.go | 45 +++++----- yaxis_test.go | 6 +- 46 files changed, 749 insertions(+), 867 deletions(-) diff --git a/alias.go b/alias.go index 3c609cd..d6e076e 100644 --- a/alias.go +++ b/alias.go @@ -17,18 +17,18 @@ type Point struct { } const ( - ChartTypeLine = "line" - ChartTypeBar = "bar" - ChartTypePie = "pie" - ChartTypeRadar = "radar" - ChartTypeFunnel = "funnel" - // horizontal bar + ChartTypeLine = "line" + ChartTypeBar = "bar" + ChartTypePie = "pie" + ChartTypeRadar = "radar" + ChartTypeFunnel = "funnel" ChartTypeHorizontalBar = "horizontalBar" ) const ( - ChartOutputSVG = "svg" - ChartOutputPNG = "png" + ChartOutputSVG = "svg" + ChartOutputPNG = "png" + chartDefaultOutputFormat = ChartOutputPNG ) const ( diff --git a/axis.go b/axis.go index 0dd9f7b..db954a5 100644 --- a/axis.go +++ b/axis.go @@ -20,42 +20,38 @@ func NewAxisPainter(p *Painter, opt AxisOption) *axisPainter { } type AxisOption struct { - // The theme of chart + // Show specifies if the axis should be rendered, set this to *false (through False()) to hide the axis. + Show *bool + // Theme specifies the colors used for the axis. Theme ColorPalette - // Formatter for y axis text value - Formatter string - // The label of axis + // Data provides labels for the axis. Data []string - // The boundary gap on both sides of a coordinate axis. - // Nil or *true means the center part of two axis ticks - BoundaryGap *bool - // The flag for show axis, set this to *false will hide axis - Show *bool - // The position of axis, it can be 'left', 'top', 'right' or 'bottom' + // DataStartIndex specifies what index the Data values should start from. + DataStartIndex int + // Formatter for replacing axis text values. + Formatter string + // Position describes the position of axis, it can be 'left', 'top', 'right' or 'bottom'. Position string - // The line color of axis - StrokeColor Color - // The line width + // BoundaryGap specifies that the chart should have additional space on the left and right, with data points being + // centered between two axis ticks. Enabled by default, specify *false (through False()) to change the spacing. + BoundaryGap *bool + // StrokeWidth is the axis line width. StrokeWidth float64 - // The length of the axis tick + // TickLength is the length of each axis tick. TickLength int - // The first axis - FirstAxis int - // The margin value of label + // LabelMargin specifies the margin value of each label. LabelMargin int - // The font size of label + // FontSize specifies the font size of each label. FontSize float64 - // The font of label + // Font is the font used to render each label. Font *truetype.Font - // The color of label + // FontColor is the color used for text rendered. FontColor Color - // The flag for show axis split line, set this to true will show axis split line + // SplitLineShow, set this to true will show axis split line. SplitLineShow bool - // The color of split line - SplitLineColor Color - // The text rotation of label + // TextRotation are the radians for rotating the label. TextRotation float64 - // The offset of label + // LabelOffset is the offset of each label. LabelOffset Box // Unit is a suggestion for how large the axis step is, this is a recommendation only. Larger numbers result in fewer labels. Unit float64 @@ -91,10 +87,6 @@ func (a *axisPainter) Render() (Box, error) { if fontSize == 0 { fontSize = defaultFontSize } - strokeColor := opt.StrokeColor - if strokeColor.IsZero() { - strokeColor = theme.GetAxisStrokeColor() - } formatter := opt.Formatter if len(formatter) != 0 { @@ -112,7 +104,7 @@ func (a *axisPainter) Render() (Box, error) { labelMargin := getDefaultInt(opt.LabelMargin, 5) style := Style{ - StrokeColor: strokeColor, + StrokeColor: theme.GetAxisStrokeColor(), StrokeWidth: strokeWidth, Font: font, FontColor: fontColor, @@ -235,17 +227,11 @@ func (a *axisPainter) Render() (Box, error) { TickSpaces: tickSpaces, Length: tickLength, Orient: orient, - First: opt.FirstAxis, + First: opt.DataStartIndex, }) p.LineStroke([]Point{ - { - X: x0, - Y: y0, - }, - { - X: x1, - Y: y1, - }, + {X: x0, Y: y0}, + {X: x1, Y: y1}, }) } @@ -254,7 +240,7 @@ func (a *axisPainter) Render() (Box, error) { Top: labelPaddingTop, Right: labelPaddingRight, })).MultiText(MultiTextOption{ - First: opt.FirstAxis, + First: opt.DataStartIndex, Align: textAlign, TextList: opt.Data, Orient: orient, @@ -266,7 +252,7 @@ func (a *axisPainter) Render() (Box, error) { }) if opt.SplitLineShow { // show auxiliary lines - style.StrokeColor = opt.SplitLineColor + style.StrokeColor = theme.GetAxisSplitLineColor() style.StrokeWidth = 1 top.OverrideDrawingStyle(style) if isVertical { @@ -280,14 +266,8 @@ func (a *axisPainter) Render() (Box, error) { yValues = yValues[0 : len(yValues)-1] for _, y := range yValues { top.LineStroke([]Point{ - { - X: x0, - Y: y, - }, - { - X: x1, - Y: y, - }, + {X: x0, Y: y}, + {X: x1, Y: y}, }) } } else { @@ -299,21 +279,12 @@ func (a *axisPainter) Render() (Box, error) { continue } top.LineStroke([]Point{ - { - X: x, - Y: y0, - }, - { - X: x, - Y: y1, - }, + {X: x, Y: y0}, + {X: x, Y: y1}, }) } } } - return Box{ - Bottom: height, - Right: width, - }, nil + return Box{Bottom: height, Right: width}, nil } diff --git a/axis_test.go b/axis_test.go index 06841eb..c9e49fa 100644 --- a/axis_test.go +++ b/axis_test.go @@ -28,8 +28,7 @@ func TestAxis(t *testing.T) { "Sat", "Sun", }, - SplitLineShow: true, - SplitLineColor: drawing.ColorBlack, + SplitLineShow: true, }).Render() return p.Bytes() }, @@ -48,7 +47,7 @@ func TestAxis(t *testing.T) { "Sat", "Sun", }, - BoundaryGap: FalseFlag(), + BoundaryGap: False(), }).Render() return p.Bytes() }, @@ -86,10 +85,9 @@ func TestAxis(t *testing.T) { "Sat", "Sun", }, - Position: PositionLeft, - BoundaryGap: FalseFlag(), - SplitLineShow: true, - SplitLineColor: drawing.ColorBlack, + Position: PositionLeft, + BoundaryGap: False(), + SplitLineShow: true, }).Render() return p.Bytes() }, @@ -108,10 +106,9 @@ func TestAxis(t *testing.T) { "Sat", "Sun", }, - Position: PositionRight, - BoundaryGap: FalseFlag(), - SplitLineShow: true, - SplitLineColor: drawing.ColorBlack, + Position: PositionRight, + BoundaryGap: False(), + SplitLineShow: true, }).Render() return p.Bytes() }, @@ -139,13 +136,21 @@ func TestAxis(t *testing.T) { }, } + testThemeName := "axisTestTheme" + InstallTheme(testThemeName, ThemeOption{ + IsDarkMode: false, + AxisStrokeColor: Color{R: 110, G: 112, B: 121, A: 255}, + AxisSplitLineColor: drawing.ColorBlack, + BackgroundColor: drawing.ColorWhite, + TextColor: Color{R: 70, G: 70, B: 70, A: 255}, + }) for i, tt := range tests { t.Run(strconv.Itoa(i), func(t *testing.T) { p, err := NewPainter(PainterOptions{ - Type: ChartOutputSVG, - Width: 600, - Height: 400, - }, PainterThemeOption(GetTheme(ThemeLight))) + OutputFormat: ChartOutputSVG, + Width: 600, + Height: 400, + }, PainterThemeOption(GetTheme(testThemeName))) require.NoError(t, err) data, err := tt.render(p) require.NoError(t, err) diff --git a/bar_chart.go b/bar_chart.go index 2cec1c5..42c8c2e 100644 --- a/bar_chart.go +++ b/bar_chart.go @@ -21,22 +21,23 @@ func NewBarChart(p *Painter, opt BarChartOption) *barChart { } type BarChartOption struct { - // The theme + // Theme specifies the colors used for the bar chart. Theme ColorPalette - // The font to use + // Padding specifies the padding of bar chart. + Padding Box + // Font is the font used to render the chart. Font *truetype.Font - // The data series list + // SeriesList provides the data series. SeriesList SeriesList - // The x-axis options + // XAxis are options for the x-axis. XAxis XAxisOption - // The padding of line chart - Padding Box - // The y-axis options - YAxisOptions []YAxisOption - // The option of title + // YAxis are options for the y-axis (at most two). + YAxis []YAxisOption + // Title are options for rendering the title. Title TitleOption - // The legend option - Legend LegendOption + // Legend are options for the data legend. + Legend LegendOption + // BarWidth specifies the width of each bar. BarWidth int } @@ -78,7 +79,7 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B } for index := range seriesList { series := seriesList[index] - yRange := result.axisRanges[series.AxisIndex] + yRange := result.axisRanges[series.YAxisIndex] seriesColor := theme.GetSeriesColor(series.index) divideValues := xRange.AutoDivide() @@ -187,13 +188,13 @@ func (b *barChart) Render() (Box, error) { opt.Theme = getPreferredTheme(p.theme) } renderResult, err := defaultRender(p, defaultRenderOption{ - Theme: opt.Theme, - Padding: opt.Padding, - SeriesList: opt.SeriesList, - XAxis: opt.XAxis, - YAxisOptions: opt.YAxisOptions, - TitleOption: opt.Title, - LegendOption: opt.Legend, + Theme: opt.Theme, + Padding: opt.Padding, + SeriesList: opt.SeriesList, + XAxis: opt.XAxis, + YAxis: opt.YAxis, + Title: opt.Title, + Legend: opt.Legend, }) if err != nil { return BoxZero, err diff --git a/bar_chart_test.go b/bar_chart_test.go index 91e0505..433d267 100644 --- a/bar_chart_test.go +++ b/bar_chart_test.go @@ -62,7 +62,7 @@ func makeBasicBarChartOption() BarChartOption { "Nov", "Dec", }), - YAxisOptions: NewYAxisOptions([]string{ + YAxis: NewYAxisOptions([]string{ "Rainfall", "Evaporation", }), @@ -94,9 +94,9 @@ func TestBarChart(t *testing.T) { for _, tt := range tests { painterOptions := PainterOptions{ - Type: ChartOutputSVG, - Width: 600, - Height: 400, + OutputFormat: ChartOutputSVG, + Width: 600, + Height: 400, } if tt.defaultTheme { t.Run(tt.name, func(t *testing.T) { diff --git a/chart_option.go b/chart_option.go index b0692c2..c1c4d9e 100644 --- a/chart_option.go +++ b/chart_option.go @@ -7,71 +7,69 @@ import ( ) type ChartOption struct { - // The font to use for rendering the chart - Font *truetype.Font - // The output type of chart, "svg" or "png", default value is "svg" - Type string - // The theme of chart. Built in themes can be loaded using `GetTheme` with - //"light", "dark", "vivid-light", "vivid-dark", "ant" or "grafana". + // OutputFormat specifies the output type of chart, "svg" or "png", default value is "png" + OutputFormat string + // Width is the width of chart, default width is 600. + Width int + // Height is the height of chart, default height is 400 + Height int + // Theme specifies the colors used for the chart. Built in themes can be loaded using GetTheme with + // "light", "dark", "vivid-light", "vivid-dark", "ant" or "grafana". Theme ColorPalette - // The title option + // Padding specifies the padding for chart, default padding is [20, 10, 10, 10] + Padding Box + // XAxis are options for the x-axis. + XAxis XAxisOption + // YAxis are options for the y-axis (at most two). + YAxis []YAxisOption + // Title are options for rendering the title. Title TitleOption - // The legend option + // Legend are options for the data legend. Legend LegendOption - // The x-axis options - XAxis XAxisOption - // The y-axis option list - YAxisOptions []YAxisOption - // The width of chart, default width is 600 - Width int - // The height of chart, default height is 400 - Height int + // Font is the font to use for rendering the chart. + Font *truetype.Font + // Box specifies the canvas box for the chart. + Box Box Parent *Painter - // The padding for chart, default padding is [20, 10, 10, 10] - Padding Box - // The canvas box for chart - Box Box - // The series list + // SeriesList provides the data series. SeriesList SeriesList - // The radar indicator list + // RadarIndicators are radar indicator list for radar charts RadarIndicators []RadarIndicator - // The background color of chart - BackgroundColor Color - // The flag for show symbol of line, set this to *false will hide symbol + // SymbolShow set this to *false (using False()) to hide symbols. SymbolShow *bool - // The stroke width of line chart + // LineStrokeWidth is the stroke width for line charts. LineStrokeWidth float64 - // The bar with of bar chart + // FillArea set to true to fill the area under the line in line charts + FillArea bool + // FillOpacity is the opacity (alpha) of the area fill in line charts. + FillOpacity uint8 + // BarWidth is the width of the bars for bar charts. BarWidth int - // The bar height of horizontal bar chart + // BarHeight is the height of the bars for horizontal bar charts. BarHeight int - // Fill in the area under the line - FillArea bool - // background fill (alpha) opacity - Opacity uint8 - // The child charts + // Children are child charts to pull options from. Children []ChartOption - // The value formatter + // ValueFormatter to format numeric values into labels. ValueFormatter ValueFormatter } // OptionFunc option function type OptionFunc func(opt *ChartOption) -// SVGTypeOption set svg type of chart's output -func SVGTypeOption() OptionFunc { - return TypeOptionFunc(ChartOutputSVG) +// SVGOutputOption set svg type of chart's output. +func SVGOutputOption() OptionFunc { + return OutputFormatOptionFunc(ChartOutputSVG) } -// PNGTypeOption set png type of chart's output -func PNGTypeOption() OptionFunc { - return TypeOptionFunc(ChartOutputPNG) +// PNGOutputOption set png type of chart's output. +func PNGOutputOption() OptionFunc { + return OutputFormatOptionFunc(ChartOutputPNG) } -// TypeOptionFunc set type of chart's output -func TypeOptionFunc(t string) OptionFunc { +// OutputFormatOptionFunc set type of chart's output. +func OutputFormatOptionFunc(t string) OptionFunc { return func(opt *ChartOption) { - opt.Type = t + opt.OutputFormat = t } } @@ -144,14 +142,14 @@ func XAxisDataOptionFunc(data []string, boundaryGap ...*bool) OptionFunc { // YAxisOptionFunc set y-axis of chart, supports up to two y-axis. func YAxisOptionFunc(yAxisOption ...YAxisOption) OptionFunc { return func(opt *ChartOption) { - opt.YAxisOptions = yAxisOption + opt.YAxis = yAxisOption } } // YAxisDataOptionFunc set y-axis data of chart func YAxisDataOptionFunc(data []string) OptionFunc { return func(opt *ChartOption) { - opt.YAxisOptions = NewYAxisOptions(data) + opt.YAxis = NewYAxisOptions(data) } } @@ -209,13 +207,6 @@ func RadarIndicatorOptionFunc(names []string, values []float64) OptionFunc { } } -// BackgroundColorOptionFunc set background color of chart -func BackgroundColorOptionFunc(color Color) OptionFunc { - return func(opt *ChartOption) { - opt.BackgroundColor = color - } -} - // MarkLineOptionFunc set mark line for series of chart func MarkLineOptionFunc(seriesIndex int, markLineTypes ...string) OptionFunc { return func(opt *ChartOption) { @@ -237,18 +228,18 @@ func MarkPointOptionFunc(seriesIndex int, markPointTypes ...string) OptionFunc { } func (o *ChartOption) fillDefault() { - axisCount := 1 + yaxisCount := 1 for _, series := range o.SeriesList { - if series.AxisIndex >= axisCount { - axisCount++ + if series.YAxisIndex >= yaxisCount { + yaxisCount++ } } o.Width = getDefaultInt(o.Width, defaultChartWidth) o.Height = getDefaultInt(o.Height, defaultChartHeight) - yAxisOptions := make([]YAxisOption, axisCount) - copy(yAxisOptions, o.YAxisOptions) - o.YAxisOptions = yAxisOptions + yAxisOptions := make([]YAxisOption, yaxisCount) + copy(yAxisOptions, o.YAxis) + o.YAxis = yAxisOptions // TODO - this is a hack, we need to update the yaxis based on the markpoint state // TODO - but can't do this earlier due to needing the axis initialized // TODO - we should reconsider the API for configuration @@ -260,10 +251,10 @@ func (o *ChartOption) fillDefault() { } } if hasMarkpoint { - for i := range o.YAxisOptions { - if o.YAxisOptions[i].RangeValuePaddingScale == nil { + for i := range o.YAxis { + if o.YAxis[i].RangeValuePaddingScale == nil { defaultPadding := 2.5 // default a larger padding to give space for the mark point - o.YAxisOptions[i].RangeValuePaddingScale = &defaultPadding + o.YAxis[i].RangeValuePaddingScale = &defaultPadding } } } @@ -275,9 +266,6 @@ func (o *ChartOption) fillDefault() { o.Theme = GetDefaultTheme() } - if o.BackgroundColor.IsZero() { - o.BackgroundColor = o.Theme.GetBackgroundColor() - } if o.Padding.IsZero() { o.Padding = Box{ Top: 20, @@ -378,19 +366,18 @@ func TableRender(header []string, data [][]string, spanMaps ...map[int]int) (*Pa // TableOptionRender table render with option func TableOptionRender(opt TableChartOption) (*Painter, error) { - if opt.Type == "" { - opt.Type = ChartOutputPNG + if opt.OutputFormat == "" { + opt.OutputFormat = chartDefaultOutputFormat } if opt.Width <= 0 { opt.Width = defaultChartWidth } p, err := NewPainter(PainterOptions{ - Type: opt.Type, - Width: opt.Width, - // is only used to calculate the hight of the table - Height: 100, - Font: opt.Font, + OutputFormat: opt.OutputFormat, + Width: opt.Width, + Height: 100, // is only used to calculate the height of the table + Font: opt.Font, }) if err != nil { return nil, err @@ -401,10 +388,10 @@ func TableOptionRender(opt TableChartOption) (*Painter, error) { } p, err = NewPainter(PainterOptions{ - Type: opt.Type, - Width: info.Width, - Height: info.Height, - Font: opt.Font, + OutputFormat: opt.OutputFormat, + Width: info.Width, + Height: info.Height, + Font: opt.Font, }) if err != nil { return nil, err diff --git a/chart_option_test.go b/chart_option_test.go index cb30a0b..ed2e081 100644 --- a/chart_option_test.go +++ b/chart_option_test.go @@ -5,26 +5,19 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/wcharczuk/go-chart/v2/drawing" ) func TestChartOption(t *testing.T) { t.Parallel() fns := []OptionFunc{ - SVGTypeOption(), + SVGOutputOption(), FontOptionFunc(GetDefaultFont()), ThemeNameOptionFunc(ThemeVividDark), TitleTextOptionFunc("title"), - LegendLabelsOptionFunc([]string{ - "label", - }), - XAxisDataOptionFunc([]string{ - "xaxis", - }), - YAxisDataOptionFunc([]string{ - "yaxis", - }), + LegendLabelsOptionFunc([]string{"label"}), + XAxisDataOptionFunc([]string{"xaxis"}), + YAxisDataOptionFunc([]string{"yaxis"}), WidthOptionFunc(800), HeightOptionFunc(600), PaddingOptionFunc(Box{ @@ -33,34 +26,27 @@ func TestChartOption(t *testing.T) { Right: 10, Bottom: 10, }), - BackgroundColorOptionFunc(drawing.ColorBlack), } opt := ChartOption{} for _, fn := range fns { fn(&opt) } require.Equal(t, ChartOption{ - Type: ChartOutputSVG, - Font: GetDefaultFont(), - Theme: GetTheme(ThemeVividDark), + OutputFormat: ChartOutputSVG, + Font: GetDefaultFont(), + Theme: GetTheme(ThemeVividDark), Title: TitleOption{ Text: "title", }, Legend: LegendOption{ - Data: []string{ - "label", - }, + Data: []string{"label"}, }, XAxis: XAxisOption{ - Data: []string{ - "xaxis", - }, + Data: []string{"xaxis"}, }, - YAxisOptions: []YAxisOption{ + YAxis: []YAxisOption{ { - Data: []string{ - "yaxis", - }, + Data: []string{"yaxis"}, }, }, Width: 800, @@ -71,7 +57,6 @@ func TestChartOption(t *testing.T) { Right: 10, Bottom: 10, }, - BackgroundColor: drawing.ColorBlack, }, opt) } @@ -79,10 +64,7 @@ func TestChartOptionPieSeriesShowLabel(t *testing.T) { t.Parallel() opt := ChartOption{ - SeriesList: NewPieSeriesList([]float64{ - 1, - 2, - }), + SeriesList: NewPieSeriesList([]float64{1, 2}), } PieSeriesShowLabel()(&opt) assert.True(t, opt.SeriesList[0].Label.Show) @@ -92,9 +74,7 @@ func TestChartOptionMarkLine(t *testing.T) { t.Parallel() opt := ChartOption{ - SeriesList: NewSeriesListDataFromValues([][]float64{ - {1, 2}, - }), + SeriesList: NewSeriesListDataFromValues([][]float64{{1, 2}}), } MarkLineOptionFunc(0, "min", "max")(&opt) assert.Equal(t, NewMarkLine("min", "max"), opt.SeriesList[0].MarkLine) @@ -104,9 +84,7 @@ func TestChartOptionMarkPoint(t *testing.T) { t.Parallel() opt := ChartOption{ - SeriesList: NewSeriesListDataFromValues([][]float64{ - {1, 2}, - }), + SeriesList: NewSeriesListDataFromValues([][]float64{{1, 2}}), } MarkPointOptionFunc(0, "min", "max")(&opt) assert.Equal(t, NewMarkPoint("min", "max"), opt.SeriesList[0].MarkPoint) @@ -164,7 +142,7 @@ func TestLineRender(t *testing.T) { } p, err := LineRender( values, - SVGTypeOption(), + SVGOutputOption(), TitleTextOptionFunc("Line"), XAxisDataOptionFunc([]string{ "Mon", @@ -224,7 +202,7 @@ func TestBarRender(t *testing.T) { } p, err := BarRender( values, - SVGTypeOption(), + SVGOutputOption(), XAxisDataOptionFunc([]string{ "Jan", "Feb", @@ -285,7 +263,7 @@ func TestHorizontalBarRender(t *testing.T) { } p, err := HorizontalBarRender( values, - SVGTypeOption(), + SVGOutputOption(), TitleTextOptionFunc("World Population"), PaddingOptionFunc(Box{ Top: 20, @@ -324,7 +302,7 @@ func TestPieRender(t *testing.T) { } p, err := PieRender( values, - SVGTypeOption(), + SVGOutputOption(), TitleOptionFunc(TitleOption{ Text: "Rainfall vs Evaporation", Subtext: "Fake Data", @@ -378,7 +356,7 @@ func TestRadarRender(t *testing.T) { } p, err := RadarRender( values, - SVGTypeOption(), + SVGOutputOption(), TitleTextOptionFunc("Basic Radar Chart"), LegendLabelsOptionFunc([]string{ "Allocated Budget", @@ -418,7 +396,7 @@ func TestFunnelRender(t *testing.T) { } p, err := FunnelRender( values, - SVGTypeOption(), + SVGOutputOption(), TitleTextOptionFunc("Funnel"), LegendLabelsOptionFunc([]string{ "Show", diff --git a/charts.go b/charts.go index 071c238..6a41bd3 100644 --- a/charts.go +++ b/charts.go @@ -69,20 +69,23 @@ func (rh *renderHandler) Do() error { } type defaultRenderOption struct { - Theme ColorPalette - Padding Box + // Theme specifies the colors used for the chart. + Theme ColorPalette + // Padding specifies the padding of chart. + Padding Box + // SeriesList provides the data series. SeriesList SeriesList - // The y-axis options - YAxisOptions []YAxisOption - // The x-axis options + // XAxis are options for the x-axis. XAxis XAxisOption - // The title option - TitleOption TitleOption - // The legend option - LegendOption LegendOption - // background is filled + // YAxis are options for the y-axis (at most two). + YAxis []YAxisOption + // Title are options for rendering the title. + Title TitleOption + // Legend are options for the data legend. + Legend LegendOption + // backgroundIsFilled is true if the background is filled. backgroundIsFilled bool - // x y axis is reversed + // axisReversed is true if the x y axis is reversed. axisReversed bool } @@ -104,30 +107,30 @@ func defaultRender(p *Painter, opt defaultRenderOption) (*defaultRenderResult, e } legendHeight := 0 - if len(opt.LegendOption.Data) != 0 { - if opt.LegendOption.Theme == nil { - opt.LegendOption.Theme = opt.Theme + if len(opt.Legend.Data) != 0 { + if opt.Legend.Theme == nil { + opt.Legend.Theme = opt.Theme } - legendResult, err := NewLegendPainter(p, opt.LegendOption).Render() + legendResult, err := NewLegendPainter(p, opt.Legend).Render() if err != nil { return nil, err } legendHeight = legendResult.Height() } - if opt.TitleOption.Text != "" { - if opt.TitleOption.Theme == nil { - opt.TitleOption.Theme = opt.Theme + if opt.Title.Text != "" { + if opt.Title.Theme == nil { + opt.Title.Theme = opt.Theme } - titleBox, err := NewTitlePainter(p, opt.TitleOption).Render() + titleBox, err := NewTitlePainter(p, opt.Title).Render() if err != nil { return nil, err } top := chart.MaxInt(legendHeight, titleBox.Height()) // if in vertical mode, the legend height is not calculated - if opt.LegendOption.Orient == OrientVertical { + if opt.Legend.Orient == OrientVertical { top = titleBox.Height() } p = p.Child(PainterPaddingOption(Box{ @@ -142,10 +145,10 @@ func defaultRender(p *Painter, opt defaultRenderOption) (*defaultRenderResult, e axisIndexList := make([]int, 0) for _, series := range opt.SeriesList { - if containsInt(axisIndexList, series.AxisIndex) { + if containsInt(axisIndexList, series.YAxisIndex) { continue } - axisIndexList = append(axisIndexList, series.AxisIndex) + axisIndexList = append(axisIndexList, series.YAxisIndex) } // the height needs to be subtracted from the height of the x-axis rangeHeight := p.Height() - defaultXAxisHeight @@ -157,8 +160,8 @@ func defaultRender(p *Painter, opt defaultRenderOption) (*defaultRenderResult, e // calculate the axis range for _, index := range axisIndexList { yAxisOption := YAxisOption{} - if len(opt.YAxisOptions) > index { - yAxisOption = opt.YAxisOptions[index] + if len(opt.YAxis) > index { + yAxisOption = opt.YAxis[index] } minPadRange, maxPadRange := 1.0, 1.0 if yAxisOption.RangeValuePaddingScale != nil { @@ -290,10 +293,10 @@ func Render(opt ChartOption, opts ...OptionFunc) (*Painter, error) { if opt.Parent == nil { isChild = false p, err := NewPainter(PainterOptions{ - Type: opt.Type, - Width: opt.Width, - Height: opt.Height, - Font: opt.Font, + OutputFormat: opt.OutputFormat, + Width: opt.Width, + Height: opt.Height, + Font: opt.Font, }) if err != nil { return nil, err @@ -308,7 +311,7 @@ func Render(opt ChartOption, opts ...OptionFunc) (*Painter, error) { p = p.Child(PainterBoxOption(opt.Box)) } if !isChild { - p.SetBackground(p.Width(), p.Height(), opt.BackgroundColor) + p.SetBackground(p.Width(), p.Height(), opt.Theme.GetBackgroundColor()) } seriesList := opt.SeriesList seriesList.init() @@ -338,9 +341,9 @@ func Render(opt ChartOption, opts ...OptionFunc) (*Painter, error) { Padding: opt.Padding, SeriesList: opt.SeriesList, XAxis: opt.XAxis, - YAxisOptions: opt.YAxisOptions, - TitleOption: opt.Title, - LegendOption: opt.Legend, + YAxis: opt.YAxis, + Title: opt.Title, + Legend: opt.Legend, axisReversed: axisReversed, // the background color has been set backgroundIsFilled: true, @@ -348,15 +351,15 @@ func Render(opt ChartOption, opts ...OptionFunc) (*Painter, error) { if len(pieSeriesList) != 0 || len(radarSeriesList) != 0 || len(funnelSeriesList) != 0 { - renderOpt.XAxis.Show = FalseFlag() - renderOpt.YAxisOptions = []YAxisOption{ + renderOpt.XAxis.Show = False() + renderOpt.YAxis = []YAxisOption{ { - Show: FalseFlag(), + Show: False(), }, } } if len(horizontalBarSeriesList) != 0 { - renderOpt.YAxisOptions[0].Unit = 1 + renderOpt.YAxis[0].Unit = 1 } renderResult, err := defaultRender(p, renderOpt) @@ -383,10 +386,10 @@ func Render(opt ChartOption, opts ...OptionFunc) (*Painter, error) { if len(horizontalBarSeriesList) != 0 { handler.Add(func() error { _, err := NewHorizontalBarChart(p, HorizontalBarChartOption{ - Theme: opt.Theme, - Font: opt.Font, - BarHeight: opt.BarHeight, - YAxisOptions: opt.YAxisOptions, + Theme: opt.Theme, + Font: opt.Font, + BarHeight: opt.BarHeight, + YAxis: opt.YAxis, }).render(renderResult, horizontalBarSeriesList) return err }) @@ -413,7 +416,7 @@ func Render(opt ChartOption, opts ...OptionFunc) (*Painter, error) { SymbolShow: opt.SymbolShow, StrokeWidth: opt.LineStrokeWidth, FillArea: opt.FillArea, - Opacity: opt.Opacity, + FillOpacity: opt.FillOpacity, }).render(renderResult, lineSeriesList) return err }) diff --git a/charts_test.go b/charts_test.go index c8973ce..cb5ef1a 100644 --- a/charts_test.go +++ b/charts_test.go @@ -10,7 +10,7 @@ import ( func BenchmarkMultiChartPNGRender(b *testing.B) { for i := 0; i < b.N; i++ { opt := ChartOption{ - Type: ChartOutputPNG, + OutputFormat: ChartOutputPNG, Legend: LegendOption{ Top: "-90", Data: []string{ @@ -34,11 +34,11 @@ func BenchmarkMultiChartPNGRender(b *testing.B) { "2016", "2017", }), - YAxisOptions: []YAxisOption{ + YAxis: []YAxisOption{ { - Min: NewFloatPoint(0), - Max: NewFloatPoint(90), + Min: FloatPointer(0), + Max: FloatPointer(90), }, }, SeriesList: []Series{ @@ -78,7 +78,7 @@ func BenchmarkMultiChartPNGRender(b *testing.B) { Children: []ChartOption{ { Legend: LegendOption{ - Show: FalseFlag(), + Show: False(), Data: []string{ "Milk Tea", "Matcha Latte", @@ -143,11 +143,10 @@ func BenchmarkMultiChartSVGRender(b *testing.B) { "2016", "2017", }), - YAxisOptions: []YAxisOption{ + YAxis: []YAxisOption{ { - - Min: NewFloatPoint(0), - Max: NewFloatPoint(90), + Min: FloatPointer(0), + Max: FloatPointer(90), }, }, SeriesList: []Series{ @@ -187,7 +186,7 @@ func BenchmarkMultiChartSVGRender(b *testing.B) { Children: []ChartOption{ { Legend: LegendOption{ - Show: FalseFlag(), + Show: False(), Data: []string{ "Milk Tea", "Matcha Latte", diff --git a/echarts.go b/echarts.go index f8a6110..0354b21 100644 --- a/echarts.go +++ b/echarts.go @@ -336,10 +336,10 @@ func (esList EChartsSeriesList) ToSeriesList() SeriesList { } } seriesList = append(seriesList, Series{ - Type: item.Type, - Data: data, - AxisIndex: item.YAxisIndex, - Style: item.ItemStyle.ToStyle(), + Type: item.Type, + Data: data, + YAxisIndex: item.YAxisIndex, + Style: item.ItemStyle.ToStyle(), Label: SeriesLabel{ Color: parseColor(item.Label.Color), Show: item.Label.Show, @@ -405,9 +405,9 @@ func (eo *EChartsOption) ToOption() ChartOption { titleSubtextStyle := eo.Title.SubtextStyle.ToStyle() legendTextStyle := eo.Legend.TextStyle.ToStyle() o := ChartOption{ - Type: eo.Type, - Font: GetFont(fontFamily), - Theme: GetTheme(eo.Theme), + OutputFormat: eo.Type, + Font: GetFont(fontFamily), + Theme: GetTheme(eo.Theme), Title: TitleOption{ Text: eo.Title.Text, Subtext: eo.Title.Subtext, @@ -464,11 +464,11 @@ func (eo *EChartsOption) ToOption() ChartOption { Min: item.Min, Max: item.Max, Formatter: item.AxisLabel.Formatter, - Color: parseColor(item.AxisLine.LineStyle.Color), + AxisColor: parseColor(item.AxisLine.LineStyle.Color), Data: item.Data, } } - o.YAxisOptions = yAxisOptions + o.YAxis = yAxisOptions if len(eo.Children) != 0 { o.Children = make([]ChartOption, len(eo.Children)) @@ -485,7 +485,7 @@ func renderEcharts(options, outputType string) ([]byte, error) { return nil, err } opt := o.ToOption() - opt.Type = outputType + opt.OutputFormat = outputType if d, err := Render(opt); err != nil { return nil, err } else { diff --git a/echarts_test.go b/echarts_test.go index 63d992f..c6746ca 100644 --- a/echarts_test.go +++ b/echarts_test.go @@ -27,10 +27,7 @@ func TestEChartsSeriesDataValue(t *testing.T) { es := EChartsSeriesDataValue{} require.NoError(t, es.UnmarshalJSON([]byte(`[1, 2]`))) assert.Equal(t, EChartsSeriesDataValue{ - values: []float64{ - 1, - 2, - }, + values: []float64{1, 2}, }, es) assert.Equal(t, NewEChartsSeriesDataValue(1, 2), es) assert.Equal(t, 1.0, es.First()) @@ -40,17 +37,13 @@ func TestEChartsSeriesData(t *testing.T) { es := EChartsSeriesData{} require.NoError(t, es.UnmarshalJSON([]byte("1.1"))) assert.Equal(t, EChartsSeriesDataValue{ - values: []float64{ - 1.1, - }, + values: []float64{1.1}, }, es.Value) require.NoError(t, es.UnmarshalJSON([]byte(`{"value":200,"itemStyle":{"color":"#a90000"}}`))) assert.Equal(t, EChartsSeriesData{ Value: EChartsSeriesDataValue{ - values: []float64{ - 200.0, - }, + values: []float64{200.0}, }, ItemStyle: EChartStyle{ Color: "#a90000", @@ -65,13 +58,10 @@ func TestEChartsXAxis(t *testing.T) { assert.Equal(t, EChartsXAxis{ Data: []EChartsXAxisData{ { - BoundaryGap: TrueFlag(), + BoundaryGap: True(), SplitNumber: 5, - Data: []string{ - "a", - "b", - }, - Type: "value", + Data: []string{"a", "b"}, + Type: "value", }, }, }, ex) diff --git a/examples/charts/main.go b/examples/charts/main.go index 6149f5c..2baf968 100644 --- a/examples/charts/main.go +++ b/examples/charts/main.go @@ -74,7 +74,7 @@ func handler(w http.ResponseWriter, req *http.Request, chartOptions []charts.Cha bytesList := make([][]byte, 0) for _, opt := range chartOptions { opt.Theme = charts.GetTheme(theme) - opt.Type = charts.ChartOutputSVG + opt.OutputFormat = charts.ChartOutputSVG d, err := charts.Render(opt) if err != nil { panic(err) @@ -94,7 +94,7 @@ func handler(w http.ResponseWriter, req *http.Request, chartOptions []charts.Cha } p, err := charts.TableOptionRender(charts.TableChartOption{ - Type: charts.ChartOutputSVG, + OutputFormat: charts.ChartOutputSVG, Header: []string{ "Name", "Age", @@ -233,7 +233,7 @@ func indexHandler(w http.ResponseWriter, req *http.Request) { "Fri", "Sat", "Sun", - }, charts.FalseFlag()), + }, charts.False()), SeriesList: []charts.Series{ { Data: charts.NewSeriesDataFromValues([]float64{ @@ -331,12 +331,7 @@ func indexHandler(w http.ResponseWriter, req *http.Request) { { Value: 190, Style: charts.Style{ - FillColor: charts.Color{ - R: 169, - G: 0, - B: 0, - A: 255, - }, + FillColor: charts.Color{R: 169, G: 0, B: 0, A: 255}, }, }, { @@ -377,7 +372,7 @@ func indexHandler(w http.ResponseWriter, req *http.Request) { "2011", "2012", }), - YAxisOptions: charts.NewYAxisOptions([]string{ + YAxis: charts.NewYAxisOptions([]string{ "Brazil", "Indonesia", "USA", @@ -515,24 +510,14 @@ func indexHandler(w http.ResponseWriter, req *http.Request) { "Precipitation", "Temperature", }), - YAxisOptions: []charts.YAxisOption{ + YAxis: []charts.YAxisOption{ { Formatter: "{value}ml", - Color: charts.Color{ - R: 84, - G: 112, - B: 198, - A: 255, - }, + AxisColor: charts.Color{R: 84, G: 112, B: 198, A: 255}, }, { Formatter: "{value}°C", - Color: charts.Color{ - R: 250, - G: 200, - B: 88, - A: 255, - }, + AxisColor: charts.Color{R: 250, G: 200, B: 88, A: 255}, }, }, SeriesList: []charts.Series{ @@ -585,7 +570,7 @@ func indexHandler(w http.ResponseWriter, req *http.Request) { 12.0, 6.2, }), - AxisIndex: 1, + YAxisIndex: 1, }, }, }, @@ -755,11 +740,11 @@ func indexHandler(w http.ResponseWriter, req *http.Request) { "2016", "2017", }), - YAxisOptions: []charts.YAxisOption{ + YAxis: []charts.YAxisOption{ { - Min: charts.NewFloatPoint(0), - Max: charts.NewFloatPoint(90), + Min: charts.FloatPointer(0), + Max: charts.FloatPointer(90), }, }, SeriesList: []charts.Series{ @@ -799,7 +784,7 @@ func indexHandler(w http.ResponseWriter, req *http.Request) { Children: []charts.ChartOption{ { Legend: charts.LegendOption{ - Show: charts.FalseFlag(), + Show: charts.False(), Data: []string{ "Milk Tea", "Matcha Latte", diff --git a/examples/line_chart/main.go b/examples/line_chart/main.go index 5866e65..8cb67d4 100644 --- a/examples/line_chart/main.go +++ b/examples/line_chart/main.go @@ -91,7 +91,7 @@ func main() { Top: 5, Bottom: 10, } - opt.SymbolShow = charts.FalseFlag() + opt.SymbolShow = charts.False() opt.LineStrokeWidth = 1 opt.ValueFormatter = func(f float64) string { return fmt.Sprintf("%.0f", f) diff --git a/examples/painter/main.go b/examples/painter/main.go index 20ecb5d..b99f29d 100644 --- a/examples/painter/main.go +++ b/examples/painter/main.go @@ -1,7 +1,6 @@ package main import ( - "io/ioutil" "os" "path/filepath" @@ -12,24 +11,19 @@ import ( func writeFile(buf []byte) error { tmpPath := "./tmp" - err := os.MkdirAll(tmpPath, 0700) - if err != nil { + if err := os.MkdirAll(tmpPath, 0700); err != nil { return err } file := filepath.Join(tmpPath, "painter.png") - err = ioutil.WriteFile(file, buf, 0600) - if err != nil { - return err - } - return nil + return os.WriteFile(file, buf, 0600) } func main() { p, err := charts.NewPainter(charts.PainterOptions{ - Width: 600, - Height: 2000, - Type: charts.ChartOutputPNG, + OutputFormat: charts.ChartOutputPNG, + Width: 600, + Height: 2000, }) if err != nil { panic(err) @@ -45,22 +39,10 @@ func main() { StrokeWidth: 1, }) p.LineStroke([]charts.Point{ - { - X: 0, - Y: 0, - }, - { - X: 100, - Y: 10, - }, - { - X: 200, - Y: 0, - }, - { - X: 300, - Y: 10, - }, + {X: 0, Y: 0}, + {X: 100, Y: 10}, + {X: 200, Y: 0}, + {X: 300, Y: 10}, }) // smooth curve @@ -112,10 +94,7 @@ func main() { StrokeColor: drawing.ColorBlack, FillColor: drawing.ColorBlack, StrokeWidth: 1, - }).Polygon(charts.Point{ - X: 100, - Y: 0, - }, 50, 6) + }).Polygon(charts.Point{X: 100, Y: 0}, 50, 6) // FillArea top += 60 @@ -124,26 +103,11 @@ func main() { })).SetDrawingStyle(charts.Style{ FillColor: drawing.ColorBlack, }).FillArea([]charts.Point{ - { - X: 0, - Y: 0, - }, - { - X: 100, - Y: 0, - }, - { - X: 150, - Y: 40, - }, - { - X: 80, - Y: 30, - }, - { - X: 0, - Y: 0, - }, + {X: 0, Y: 0}, + {X: 100, Y: 0}, + {X: 150, Y: 40}, + {X: 80, Y: 30}, + {X: 0, Y: 0}, }) // point of coordinate axis @@ -340,9 +304,9 @@ func main() { FontColor: drawing.ColorBlack, FontSize: 10, }).Grid(charts.GridOption{ - Column: 8, + Columns: 8, IgnoreColumnLines: []int{0, 8}, - Row: 8, + Rows: 8, IgnoreRowLines: []int{0, 8}, }) @@ -360,18 +324,9 @@ func main() { StrokeColor: drawing.ColorBlack, StrokeWidth: 1, }).Dots([]charts.Point{ - { - X: 0, - Y: 0, - }, - { - X: 50, - Y: 0, - }, - { - X: 100, - Y: 10, - }, + {X: 0, Y: 0}, + {X: 50, Y: 0}, + {X: 100, Y: 10}, }) // rect @@ -419,7 +374,7 @@ func main() { Right: p.Width() - 1, Bottom: top + 100, })), charts.GridPainterOption{ - Row: 5, + Rows: 5, IgnoreFirstRow: true, IgnoreLastRow: true, StrokeColor: drawing.ColorBlue, @@ -502,9 +457,9 @@ func main() { "Sat", "Sun", }, - StrokeColor: drawing.ColorBlack, - FontSize: 12, - FontColor: drawing.ColorBlack, + //StrokeColor: drawing.ColorBlack, // TODO - apply to theme + FontSize: 12, + FontColor: drawing.ColorBlack, }).Render() // axis top @@ -516,7 +471,7 @@ func main() { Bottom: top + 50, })), charts.AxisOption{ Position: charts.PositionTop, - BoundaryGap: charts.FalseFlag(), + BoundaryGap: charts.False(), Data: []string{ "Mon", "Tue", @@ -526,9 +481,9 @@ func main() { "Sat", "Sun", }, - StrokeColor: drawing.ColorBlack, - FontSize: 12, - FontColor: drawing.ColorBlack, + //StrokeColor: drawing.ColorBlack, // TODO - apply to theme + FontSize: 12, + FontColor: drawing.ColorBlack, }).Render() // axis left @@ -549,9 +504,9 @@ func main() { "Sat", "Sun", }, - StrokeColor: drawing.ColorBlack, - FontSize: 12, - FontColor: drawing.ColorBlack, + //StrokeColor: drawing.ColorBlack, // TODO - apply to theme + FontSize: 12, + FontColor: drawing.ColorBlack, }).Render() // axis right @@ -571,9 +526,9 @@ func main() { "Sat", "Sun", }, - StrokeColor: drawing.ColorBlack, - FontSize: 12, - FontColor: drawing.ColorBlack, + //StrokeColor: drawing.ColorBlack, // TODO - apply to theme + FontSize: 12, + FontColor: drawing.ColorBlack, }).Render() // axis left no tick @@ -583,7 +538,7 @@ func main() { Right: 300, Bottom: top + 200, })), charts.AxisOption{ - BoundaryGap: charts.FalseFlag(), + BoundaryGap: charts.False(), Position: charts.PositionLeft, Data: []string{ "Mon", @@ -594,10 +549,10 @@ func main() { "Sat", "Sun", }, - FontSize: 12, - FontColor: drawing.ColorBlack, - SplitLineShow: true, - SplitLineColor: drawing.ColorBlack.WithAlpha(100), + FontSize: 12, + FontColor: drawing.ColorBlack, + SplitLineShow: true, + //SplitLineColor: drawing.ColorBlack.WithAlpha(100), // TODO - apply to theme }).Render() if buf, err := p.Bytes(); err != nil { diff --git a/examples/time_line_chart/main.go b/examples/time_line_chart/main.go index c594816..a8c2bc0 100644 --- a/examples/time_line_chart/main.go +++ b/examples/time_line_chart/main.go @@ -41,18 +41,18 @@ func main() { values, }, charts.TitleTextOptionFunc("Line"), - charts.XAxisDataOptionFunc(xAxisValue, charts.FalseFlag()), + charts.XAxisDataOptionFunc(xAxisValue, charts.False()), charts.LegendLabelsOptionFunc([]string{ "Demo", }, "50"), func(opt *charts.ChartOption) { - opt.XAxis.FirstAxis = firstAxis + opt.XAxis.DataStartIndex = firstAxis opt.XAxis.Unit = 10 opt.Legend.Padding = charts.Box{ Top: 5, Bottom: 10, } - opt.SymbolShow = charts.FalseFlag() + opt.SymbolShow = charts.False() opt.LineStrokeWidth = 1 opt.ValueFormatter = func(f float64) string { return fmt.Sprintf("%.0f", f) diff --git a/font_test.go b/font_test.go index f517b51..0a025ba 100644 --- a/font_test.go +++ b/font_test.go @@ -27,15 +27,15 @@ func TestCustomFontSizeRender(t *testing.T) { t.Parallel() p, err := NewPainter(PainterOptions{ - Type: ChartOutputSVG, - Width: 600, - Height: 400, + OutputFormat: ChartOutputSVG, + Width: 600, + Height: 400, }, PainterThemeOption(GetTheme(ThemeLight))) require.NoError(t, err) opt := makeBasicLineChartOption() opt.XAxis.FontSize = 4.0 - opt.YAxisOptions = []YAxisOption{ + opt.YAxis = []YAxisOption{ { FontSize: 4.0, }, diff --git a/funnel_chart.go b/funnel_chart.go index 3ed0bfd..48822be 100644 --- a/funnel_chart.go +++ b/funnel_chart.go @@ -29,17 +29,17 @@ func NewFunnelChart(p *Painter, opt FunnelChartOption) *funnelChart { } type FunnelChartOption struct { - // The theme + // Theme specifies the colors used for the chart. Theme ColorPalette - // The font size + // Padding specifies the padding of funnel chart. + Padding Box + // Font is the font used to render the chart. Font *truetype.Font - // The data series list + // SeriesList provides the data series. SeriesList SeriesList - // The padding of line chart - Padding Box - // The option of title + // Title are options for rendering the title. Title TitleOption - // The legend option + // Legend are options for the data legend. Legend LegendOption } @@ -153,15 +153,15 @@ func (f *funnelChart) Render() (Box, error) { Padding: opt.Padding, SeriesList: opt.SeriesList, XAxis: XAxisOption{ - Show: FalseFlag(), + Show: False(), }, - YAxisOptions: []YAxisOption{ + YAxis: []YAxisOption{ { - Show: FalseFlag(), + Show: False(), }, }, - TitleOption: opt.Title, - LegendOption: opt.Legend, + Title: opt.Title, + Legend: opt.Legend, }) if err != nil { return BoxZero, err diff --git a/funnel_chart_test.go b/funnel_chart_test.go index 6bada5b..549f87f 100644 --- a/funnel_chart_test.go +++ b/funnel_chart_test.go @@ -53,9 +53,9 @@ func TestFunnelChart(t *testing.T) { for _, tt := range tests { painterOptions := PainterOptions{ - Type: ChartOutputSVG, - Width: 600, - Height: 400, + OutputFormat: ChartOutputSVG, + Width: 600, + Height: 400, } if tt.defaultTheme { t.Run(tt.name, func(t *testing.T) { diff --git a/grid.go b/grid.go index 10bd5fd..9519785 100644 --- a/grid.go +++ b/grid.go @@ -6,23 +6,23 @@ type gridPainter struct { } type GridPainterOption struct { - // The stroke width + // StrokeWidth is the grid line width. StrokeWidth float64 - // The stroke color + // StrokeColor is the grid line color. StrokeColor Color - // The spans of column + // ColumnSpans specifies the span for each column. ColumnSpans []int - // The column of grid - Column int - // The row of grid - Row int - // Ignore first row + // Columns is the count of columns in the grid. + Columns int + // Rows are the count of rows in the grid. + Rows int + // IgnoreFirstRow can be set to true to ignore the first row. IgnoreFirstRow bool - // Ignore last row + // IgnoreLastRow can be set to true to ignore the last row. IgnoreLastRow bool - // Ignore first column + // IgnoreFirstColumn can be set to true to ignore the first colum. IgnoreFirstColumn bool - // Ignore last column + // IgnoreLastColumn can be set to true to ignore the last columns. IgnoreLastColumn bool } @@ -41,14 +41,14 @@ func (g *gridPainter) Render() (Box, error) { ignoreColumnLines = append(ignoreColumnLines, 0) } if opt.IgnoreLastColumn { - ignoreColumnLines = append(ignoreColumnLines, opt.Column) + ignoreColumnLines = append(ignoreColumnLines, opt.Columns) } ignoreRowLines := make([]int, 0) if opt.IgnoreFirstRow { ignoreRowLines = append(ignoreRowLines, 0) } if opt.IgnoreLastRow { - ignoreRowLines = append(ignoreRowLines, opt.Row) + ignoreRowLines = append(ignoreRowLines, opt.Rows) } strokeWidth := opt.StrokeWidth if strokeWidth <= 0 { @@ -60,9 +60,9 @@ func (g *gridPainter) Render() (Box, error) { StrokeColor: opt.StrokeColor, }) g.p.Grid(GridOption{ - Column: opt.Column, + Columns: opt.Columns, ColumnSpans: opt.ColumnSpans, - Row: opt.Row, + Rows: opt.Rows, IgnoreColumnLines: ignoreColumnLines, IgnoreRowLines: ignoreRowLines, }) diff --git a/grid_test.go b/grid_test.go index 4f63eeb..e1030c8 100644 --- a/grid_test.go +++ b/grid_test.go @@ -20,8 +20,8 @@ func TestGrid(t *testing.T) { render: func(p *Painter) ([]byte, error) { _, err := NewGridPainter(p, GridPainterOption{ StrokeColor: drawing.ColorBlack, - Column: 6, - Row: 6, + Columns: 6, + Rows: 6, IgnoreFirstRow: true, IgnoreLastRow: true, IgnoreFirstColumn: true, @@ -38,12 +38,8 @@ func TestGrid(t *testing.T) { render: func(p *Painter) ([]byte, error) { _, err := NewGridPainter(p, GridPainterOption{ StrokeColor: drawing.ColorBlack, - ColumnSpans: []int{ - 2, - 5, - 3, - }, - Row: 6, + ColumnSpans: []int{2, 5, 3}, + Rows: 6, }).Render() if err != nil { return nil, err @@ -56,9 +52,9 @@ func TestGrid(t *testing.T) { for i, tt := range tests { t.Run(strconv.Itoa(i), func(t *testing.T) { p, err := NewPainter(PainterOptions{ - Type: ChartOutputSVG, - Width: 600, - Height: 400, + OutputFormat: ChartOutputSVG, + Width: 600, + Height: 400, }, PainterThemeOption(GetTheme(ThemeLight))) require.NoError(t, err) data, err := tt.render(p) diff --git a/horizontal_bar_chart.go b/horizontal_bar_chart.go index 1ede691..a4f605b 100644 --- a/horizontal_bar_chart.go +++ b/horizontal_bar_chart.go @@ -11,22 +11,23 @@ type horizontalBarChart struct { } type HorizontalBarChartOption struct { - // The theme + // Theme specifies the colors used for the chart. Theme ColorPalette - // The font size + // Padding specifies the padding of bar chart. + Padding Box + // Font is the font used to render the chart. Font *truetype.Font - // The data series list + // SeriesList provides the data series. SeriesList SeriesList - // The x-axis options + // XAxis are options for the x-axis. XAxis XAxisOption - // The padding of line chart - Padding Box - // The y-axis options - YAxisOptions []YAxisOption - // The option of title + // YAxis are options for the y-axis (at most two). + YAxis []YAxisOption + // Title are options for rendering the title. Title TitleOption - // The legend option - Legend LegendOption + // Legend are options for the data legend. + Legend LegendOption + // BarHeight specifies the height of each horizontal bar. BarHeight int } @@ -157,9 +158,9 @@ func (h *horizontalBarChart) Render() (Box, error) { Padding: opt.Padding, SeriesList: opt.SeriesList, XAxis: opt.XAxis, - YAxisOptions: opt.YAxisOptions, - TitleOption: opt.Title, - LegendOption: opt.Legend, + YAxis: opt.YAxis, + Title: opt.Title, + Legend: opt.Legend, axisReversed: true, }) if err != nil { diff --git a/horizontal_bar_chart_test.go b/horizontal_bar_chart_test.go index 3add1a7..7d767ae 100644 --- a/horizontal_bar_chart_test.go +++ b/horizontal_bar_chart_test.go @@ -39,7 +39,7 @@ func makeBasicHorizontalBarChartOption() HorizontalBarChartOption { "2011", "2012", }), - YAxisOptions: NewYAxisOptions([]string{ + YAxis: NewYAxisOptions([]string{ "Brazil", "Indonesia", "USA", @@ -75,9 +75,9 @@ func TestHorizontalBarChart(t *testing.T) { for _, tt := range tests { painterOptions := PainterOptions{ - Type: ChartOutputSVG, - Width: 600, - Height: 400, + OutputFormat: ChartOutputSVG, + Width: 600, + Height: 400, } if tt.defaultTheme { t.Run(tt.name, func(t *testing.T) { diff --git a/legend.go b/legend.go index a1ffc5a..9dd0d58 100644 --- a/legend.go +++ b/legend.go @@ -2,8 +2,8 @@ package charts import ( "fmt" - "strconv" - "strings" + + "github.com/golang/freetype/truetype" ) type legendPainter struct { @@ -15,31 +15,32 @@ const IconRect = "rect" const IconLineDot = "lineDot" type LegendOption struct { - // The theme + // Show specifies if the legend should be rendered, set this to *false (through False()) to hide the legend. + Show *bool + // Theme specifies the colors used for the legend. Theme ColorPalette - // Text array of legend + // Padding specifies space padding around the legend. + Padding Box + // Data provides text for the legend. Data []string - // Distance between legend component and the left side of the container. - // It can be pixel value: 20, percentage value: 20%, - // or position value: right, center. + // FontSize specifies the font size of each label. + FontSize float64 + // Font is the font used to render each label. + Font *truetype.Font + // FontColor is the color used for text rendered. + FontColor Color + // 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 - // Distance between legend component and the top side of the container. - // It can be pixel value: 20. + // 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 - // Legend marker and text aligning, it can be left or right, default is left. + // Align is the legend marker and text alignment, it can be 'left' or 'right', default is 'left'. Align string - // The layout orientation of legend, it can be horizontal or vertical, default is horizontal. + // Orient is the layout orientation of legend, it can be 'horizontal' or 'vertical', default is 'horizontal'. Orient string - // Icon of the legend. + // Icon to show next to the labels. Can be 'rect' or 'dot'. Icon string - // Font size of legend text - FontSize float64 - // FontColor color of legend text - FontColor Color - // The flag for show legend, set this to *false will hide legend - Show *bool - // The padding of legend - Padding Box } // NewLegendOption returns a legend option @@ -73,13 +74,14 @@ func NewLegendPainter(p *Painter, opt LegendOption) *legendPainter { func (l *legendPainter) Render() (Box, error) { opt := l.opt - theme := opt.Theme if opt.IsEmpty() || isFalse(opt.Show) { return BoxZero, nil } + + theme := opt.Theme if theme == nil { - theme = l.p.theme + theme = getPreferredTheme(l.p.theme) } if opt.FontSize == 0 { opt.FontSize = defaultFontSize @@ -87,7 +89,7 @@ func (l *legendPainter) Render() (Box, error) { if opt.FontColor.IsZero() { opt.FontColor = theme.GetTextColor() } - if opt.Left == "" { + if opt.Left == "" { // default a center legend opt.Left = PositionCenter } padding := opt.Padding @@ -140,32 +142,36 @@ func (l *legendPainter) Render() (Box, error) { } // calculate starting position - left := 0 + var left int switch opt.Left { + case PositionLeft: + // no-op case PositionRight: left = p.Width() - width case PositionCenter: left = (p.Width() - width) >> 1 default: - if strings.HasSuffix(opt.Left, "%") { - value, _ := strconv.Atoi(strings.ReplaceAll(opt.Left, "%", "")) - left = p.Width() * value / 100 + if v, err := parseFlexibleValue(opt.Left, float64(p.Width())); err != nil { + return BoxZero, err } else { - value, _ := strconv.Atoi(opt.Left) - left = value + left = int(v) } } - top, err := strconv.Atoi(opt.Top) - if opt.Top != "" && err != nil { - return BoxZero, fmt.Errorf("unexpected parsing error: %v", err) - } - if left < 0 { left = 0 } - x := int(left) - y := int(top) + 10 + var top int + if opt.Top != "" { + if v, err := parseFlexibleValue(opt.Top, float64(p.Height())); err != nil { + return BoxZero, fmt.Errorf("unexpected parsing error: %v", err) + } else { + top = int(v) + } + } + + x := left + y := top + 10 startY := y x0 := x y0 := y diff --git a/legend_test.go b/legend_test.go index 4112ad1..c95d0af 100644 --- a/legend_test.go +++ b/legend_test.go @@ -59,9 +59,9 @@ func TestNewLegend(t *testing.T) { for i, tt := range tests { t.Run(strconv.Itoa(i), func(t *testing.T) { p, err := NewPainter(PainterOptions{ - Type: ChartOutputSVG, - Width: 600, - Height: 400, + OutputFormat: ChartOutputSVG, + Width: 600, + Height: 400, }, PainterThemeOption(GetTheme(ThemeLight))) require.NoError(t, err) data, err := tt.render(p) diff --git a/line_chart.go b/line_chart.go index a567f83..8037d55 100644 --- a/line_chart.go +++ b/line_chart.go @@ -21,32 +21,32 @@ func NewLineChart(p *Painter, opt LineChartOption) *lineChart { } type LineChartOption struct { - // The theme + // Theme specifies the colors used for the line chart. Theme ColorPalette - // The font size + // Padding specifies the padding of line chart. + Padding Box + // Font is the font used to render the chart. Font *truetype.Font - // The data series list + // SeriesList provides the data series. SeriesList SeriesList - // The x-axis option + // XAxis are options for the x-axis. XAxis XAxisOption - // The padding of line chart - Padding Box - // The y-axis option - YAxisOptions []YAxisOption - // The option of title + // YAxis are options for the y-axis (at most two). + YAxis []YAxisOption + // Title are options for rendering the title. Title TitleOption - // The legend option + // Legend are options for the data legend. Legend LegendOption - // The flag for show symbol of line, set this to *false will hide symbol + // SymbolShow set this to *false (through False()) to hide symbols. SymbolShow *bool - // The stroke width of line + // StrokeWidth is the width of the rendered line. StrokeWidth float64 - // Fill the area below the line + // FillArea set this to true to fill the area below the line. FillArea bool - // background is filled + // FillOpacity is the opacity (alpha) of the area fill. + FillOpacity uint8 + // backgroundIsFilled is set to true if the background is filled. backgroundIsFilled bool - // background fill (alpha) opacity - Opacity uint8 } func (l *lineChart) render(result *defaultRenderResult, seriesList SeriesList) (Box, error) { @@ -91,7 +91,7 @@ func (l *lineChart) render(result *defaultRenderResult, seriesList SeriesList) ( drawingStyle.StrokeDashArray = series.Style.StrokeDashArray } - yRange := result.axisRanges[series.AxisIndex] + yRange := result.axisRanges[series.YAxisIndex] points := make([]Point, 0) var labelPainter *SeriesLabelPainter if series.Label.Show { @@ -132,8 +132,8 @@ func (l *lineChart) render(result *defaultRenderResult, seriesList SeriesList) ( copy(areaPoints, points) bottomY := yRange.getRestHeight(yRange.min) var opacity uint8 = 200 - if opt.Opacity != 0 { - opacity = opt.Opacity + if opt.FillOpacity > 0 { + opacity = opt.FillOpacity } areaPoints = append(areaPoints, Point{ X: areaPoints[len(areaPoints)-1].X, @@ -198,9 +198,9 @@ func (l *lineChart) Render() (Box, error) { Padding: opt.Padding, SeriesList: opt.SeriesList, XAxis: opt.XAxis, - YAxisOptions: opt.YAxisOptions, - TitleOption: opt.Title, - LegendOption: opt.Legend, + YAxis: opt.YAxis, + Title: opt.Title, + Legend: opt.Legend, backgroundIsFilled: opt.backgroundIsFilled, }) if err != nil { diff --git a/line_chart_test.go b/line_chart_test.go index ed62c63..a23210f 100644 --- a/line_chart_test.go +++ b/line_chart_test.go @@ -124,10 +124,7 @@ func makeBasicLineChartOption() LineChartOption { "F", "G", }), - Legend: NewLegendOption([]string{ - "1", - "2", - }, PositionCenter), + Legend: NewLegendOption([]string{"1", "2"}, PositionCenter), SeriesList: NewSeriesListDataFromValues(values), } } @@ -170,7 +167,7 @@ func makeMinimalLineChartOption() LineChartOption { "6", "7", }, - Show: FalseFlag(), + Show: False(), }, SeriesList: NewSeriesListDataFromValues(values), } @@ -202,7 +199,7 @@ func TestLineChart(t *testing.T) { defaultTheme: false, makeOptions: func() LineChartOption { opt := makeFullLineChartOption() - opt.XAxis.BoundaryGap = FalseFlag() + opt.XAxis.BoundaryGap = False() return opt }, result: "\\nEmailUnion AdsVideo AdsDirectSearch EngineLine1.44k1.28k1.12k9608006404803201600MonTueWedThuFriSatSun", @@ -212,7 +209,7 @@ func TestLineChart(t *testing.T) { defaultTheme: true, makeOptions: func() LineChartOption { opt := makeMinimalLineChartOption() - opt.YAxisOptions = []YAxisOption{ + opt.YAxis = []YAxisOption{ { LabelCount: 8, LabelSkipCount: 1, @@ -227,7 +224,7 @@ func TestLineChart(t *testing.T) { defaultTheme: true, makeOptions: func() LineChartOption { opt := makeMinimalLineChartOption() - opt.YAxisOptions = []YAxisOption{ + opt.YAxis = []YAxisOption{ { LabelCount: 9, LabelSkipCount: 1, @@ -242,7 +239,7 @@ func TestLineChart(t *testing.T) { defaultTheme: true, makeOptions: func() LineChartOption { opt := makeMinimalLineChartOption() - opt.YAxisOptions = []YAxisOption{ + opt.YAxis = []YAxisOption{ { LabelCount: 8, LabelSkipCount: 2, @@ -257,7 +254,7 @@ func TestLineChart(t *testing.T) { defaultTheme: true, makeOptions: func() LineChartOption { opt := makeMinimalLineChartOption() - opt.YAxisOptions = []YAxisOption{ + opt.YAxis = []YAxisOption{ { LabelCount: 9, LabelSkipCount: 2, @@ -272,7 +269,7 @@ func TestLineChart(t *testing.T) { defaultTheme: true, makeOptions: func() LineChartOption { opt := makeMinimalLineChartOption() - opt.YAxisOptions = []YAxisOption{ + opt.YAxis = []YAxisOption{ { LabelCount: 10, LabelSkipCount: 2, @@ -287,7 +284,7 @@ func TestLineChart(t *testing.T) { defaultTheme: true, makeOptions: func() LineChartOption { opt := makeMinimalLineChartOption() - opt.YAxisOptions = []YAxisOption{ + opt.YAxis = []YAxisOption{ { LabelCount: 8, LabelSkipCount: 3, @@ -302,7 +299,7 @@ func TestLineChart(t *testing.T) { defaultTheme: true, makeOptions: func() LineChartOption { opt := makeMinimalLineChartOption() - opt.YAxisOptions = []YAxisOption{ + opt.YAxis = []YAxisOption{ { LabelCount: 9, LabelSkipCount: 3, @@ -317,7 +314,7 @@ func TestLineChart(t *testing.T) { defaultTheme: true, makeOptions: func() LineChartOption { opt := makeMinimalLineChartOption() - opt.YAxisOptions = []YAxisOption{ + opt.YAxis = []YAxisOption{ { LabelCount: 10, LabelSkipCount: 3, @@ -332,7 +329,7 @@ func TestLineChart(t *testing.T) { defaultTheme: true, makeOptions: func() LineChartOption { opt := makeMinimalLineChartOption() - opt.YAxisOptions = []YAxisOption{ + opt.YAxis = []YAxisOption{ { LabelCount: 11, LabelSkipCount: 3, @@ -346,9 +343,9 @@ func TestLineChart(t *testing.T) { for _, tt := range tests { painterOptions := PainterOptions{ - Type: ChartOutputSVG, - Width: 600, - Height: 400, + OutputFormat: ChartOutputSVG, + Width: 600, + Height: 400, } if tt.defaultTheme { t.Run(tt.name, func(t *testing.T) { diff --git a/mark_line_test.go b/mark_line_test.go index c08bae7..3ac4e5b 100644 --- a/mark_line_test.go +++ b/mark_line_test.go @@ -42,9 +42,9 @@ func TestMarkLine(t *testing.T) { for i, tt := range tests { t.Run(strconv.Itoa(i), func(t *testing.T) { p, err := NewPainter(PainterOptions{ - Type: ChartOutputSVG, - Width: 600, - Height: 400, + OutputFormat: ChartOutputSVG, + Width: 600, + Height: 400, }, PainterThemeOption(GetTheme(ThemeLight))) require.NoError(t, err) data, err := tt.render(p.Child(PainterPaddingOption(Box{ diff --git a/mark_point_test.go b/mark_point_test.go index f0cef9d..febc744 100644 --- a/mark_point_test.go +++ b/mark_point_test.go @@ -41,9 +41,9 @@ func TestMarkPoint(t *testing.T) { for i, tt := range tests { t.Run(strconv.Itoa(i), func(t *testing.T) { p, err := NewPainter(PainterOptions{ - Type: ChartOutputSVG, - Width: 600, - Height: 400, + OutputFormat: ChartOutputSVG, + Width: 600, + Height: 400, }, PainterThemeOption(GetTheme(ThemeLight))) require.NoError(t, err) data, err := tt.render(p.Child(PainterPaddingOption(Box{ diff --git a/painter.go b/painter.go index 711bb09..e1fc4d7 100644 --- a/painter.go +++ b/painter.go @@ -18,18 +18,18 @@ type Painter struct { style Style theme ColorPalette font *truetype.Font - outputType string + outputFormat string valueFormatter ValueFormatter } type PainterOptions struct { - // Draw type, "svg" or "png", default type is "png" - Type string - // The width of draw painter + // OutputFormat specifies the output type, "svg" or "png", default value is "png" + OutputFormat string + // Width is the width of the draw painter. Width int - // The height of draw painter + // Height is the height of the draw painter. Height int - // The font for painter + // Font is the font used for rendering text. Font *truetype.Font } @@ -58,12 +58,15 @@ type MultiTextOption struct { } type GridOption struct { - Column int - Row int + // Columns is the count of columns in the grid. + Columns int + // Rows are the count of rows in the grid. + Rows int + // ColumnSpans specifies the span for each column. ColumnSpans []int - // ignore columns that are not displayed + // IgnoreColumnLines specifies index for columns to not display. IgnoreColumnLines []int - // ignore rows that are not displayed + // IgnoreRowLines specifies index for rows to not display. IgnoreRowLines []int } @@ -136,7 +139,7 @@ func NewPainter(opts PainterOptions, opt ...PainterOption) (*Painter, error) { font = GetDefaultFont() } fn := chart.PNG - if opts.Type == ChartOutputSVG { + if opts.OutputFormat == ChartOutputSVG { fn = chart.SVG } width := opts.Width @@ -153,8 +156,8 @@ func NewPainter(opts PainterOptions, opt ...PainterOption) (*Painter, error) { Right: opts.Width, Bottom: opts.Height, }, - font: font, - outputType: opts.Type, + font: font, + outputFormat: opts.OutputFormat, } p.setOptions(opt...) if p.theme == nil { @@ -624,25 +627,13 @@ func (p *Painter) Ticks(opt TicksOption) *Painter { } if isVertical { p.LineStroke([]Point{ - { - X: 0, - Y: value, - }, - { - X: opt.Length, - Y: value, - }, + {X: 0, Y: value}, + {X: opt.Length, Y: value}, }) } else { p.LineStroke([]Point{ - { - X: value, - Y: opt.Length, - }, - { - X: value, - Y: 0, - }, + {X: value, Y: opt.Length}, + {X: value, Y: 0}, }) } } @@ -769,27 +760,21 @@ func (p *Painter) Grid(opt GridOption) *Painter { y1 = v } p.LineStroke([]Point{ - { - X: x0, - Y: y0, - }, - { - X: x1, - Y: y1, - }, + {X: x0, Y: y0}, + {X: x1, Y: y1}, }) } } columnCount := sumInt(opt.ColumnSpans) if columnCount == 0 { - columnCount = opt.Column + columnCount = opt.Columns } if columnCount > 0 { values := autoDivideSpans(width, columnCount, opt.ColumnSpans) drawLines(values, opt.IgnoreColumnLines, true) } - if opt.Row > 0 { - values := autoDivide(height, opt.Row) + if opt.Rows > 0 { + values := autoDivide(height, opt.Rows) drawLines(values, opt.IgnoreRowLines, false) } return p diff --git a/painter_test.go b/painter_test.go index 17ce585..b33b138 100644 --- a/painter_test.go +++ b/painter_test.go @@ -17,9 +17,9 @@ func TestPainterOption(t *testing.T) { font := &truetype.Font{} d, err := NewPainter(PainterOptions{ - Type: ChartOutputSVG, - Width: 800, - Height: 600, + OutputFormat: ChartOutputSVG, + Width: 800, + Height: 600, }, PainterBoxOption(Box{Right: 400, Bottom: 300}), PainterPaddingOption(Box{Left: 1, Top: 2, Right: 3, Bottom: 4}), @@ -205,9 +205,9 @@ func TestPainter(t *testing.T) { for i, tt := range tests { t.Run(strconv.Itoa(i), func(t *testing.T) { d, err := NewPainter(PainterOptions{ - Type: ChartOutputSVG, - Width: 400, - Height: 300, + OutputFormat: ChartOutputSVG, + Width: 400, + Height: 300, }, PainterPaddingOption(chart.Box{Left: 5, Top: 10})) require.NoError(t, err) tt.fn(d) @@ -222,9 +222,9 @@ func TestPainterTextFit(t *testing.T) { t.Parallel() p, err := NewPainter(PainterOptions{ - Type: ChartOutputSVG, - Width: 400, - Height: 300, + OutputFormat: ChartOutputSVG, + Width: 400, + Height: 300, }) require.NoError(t, err) style := Style{ diff --git a/pie_chart.go b/pie_chart.go index d90b1aa..306bcdd 100644 --- a/pie_chart.go +++ b/pie_chart.go @@ -14,19 +14,19 @@ type pieChart struct { } type PieChartOption struct { - // The theme + // Theme specifies the colors used for the pie chart. Theme ColorPalette - // The font size + // Padding specifies the padding of pie chart. + Padding Box + // Font is the font used to render the chart. Font *truetype.Font - // The data series list + // SeriesList provides the data series. SeriesList SeriesList - // The padding of line chart - Padding Box - // The option of title + // Title are options for rendering the title. Title TitleOption - // The legend option + // Legend are options for the data legend. Legend LegendOption - // background is filled + // backgroundIsFilled is set to true if the background is filled. backgroundIsFilled bool } @@ -182,15 +182,15 @@ func (p *pieChart) Render() (Box, error) { Padding: opt.Padding, SeriesList: opt.SeriesList, XAxis: XAxisOption{ - Show: FalseFlag(), + Show: False(), }, - YAxisOptions: []YAxisOption{ + YAxis: []YAxisOption{ { - Show: FalseFlag(), + Show: False(), }, }, - TitleOption: opt.Title, - LegendOption: opt.Legend, + Title: opt.Title, + Legend: opt.Legend, backgroundIsFilled: opt.backgroundIsFilled, }) if err != nil { diff --git a/pie_chart_test.go b/pie_chart_test.go index 2b1ff34..6913c12 100644 --- a/pie_chart_test.go +++ b/pie_chart_test.go @@ -68,9 +68,9 @@ func TestPieChart(t *testing.T) { for _, tt := range tests { painterOptions := PainterOptions{ - Type: ChartOutputSVG, - Width: 600, - Height: 400, + OutputFormat: ChartOutputSVG, + Width: 600, + Height: 400, } if tt.defaultTheme { t.Run(tt.name, func(t *testing.T) { diff --git a/radar_chart.go b/radar_chart.go index b81396f..4cef071 100644 --- a/radar_chart.go +++ b/radar_chart.go @@ -15,30 +15,30 @@ type radarChart struct { } type RadarIndicator struct { - // Indicator's name + // Name specifies a name for the iIndicator. Name string - // The maximum value of indicator + // Max is the maximum value of indicator. Max float64 - // The minimum value of indicator + // Min is the minimum value of indicator. Min float64 } type RadarChartOption struct { - // The theme + // Theme specifies the colors used for the pie chart. Theme ColorPalette - // The font size + // Padding specifies the padding of pie chart. + Padding Box + // Font is the font used to render the chart. Font *truetype.Font - // The data series list + // SeriesList provides the data series. SeriesList SeriesList - // The padding of line chart - Padding Box - // The option of title + // Title are options for rendering the title. Title TitleOption - // The legend option + // Legend are options for the data legend. Legend LegendOption - // The radar indicator list + // RadarIndicators provides the radar indicator list. RadarIndicators []RadarIndicator - // background is filled + // backgroundIsFilled is set to true if the background is filled. backgroundIsFilled bool } @@ -109,10 +109,7 @@ func (r *radarChart) render(result *defaultRenderResult, seriesList SeriesList) StrokeColor: theme.GetAxisSplitLineColor(), StrokeWidth: 1, }) - center := Point{ - X: cx, - Y: cy, - } + center := Point{X: cx, Y: cy} for i := 0; i < divideCount; i++ { seriesPainter.Polygon(center, divideRadius*float64(i+1), sides) } @@ -232,15 +229,15 @@ func (r *radarChart) Render() (Box, error) { Padding: opt.Padding, SeriesList: opt.SeriesList, XAxis: XAxisOption{ - Show: FalseFlag(), + Show: False(), }, - YAxisOptions: []YAxisOption{ + YAxis: []YAxisOption{ { - Show: FalseFlag(), + Show: False(), }, }, - TitleOption: opt.Title, - LegendOption: opt.Legend, + Title: opt.Title, + Legend: opt.Legend, backgroundIsFilled: opt.backgroundIsFilled, }) if err != nil { diff --git a/radar_chart_test.go b/radar_chart_test.go index 823a064..3beda6f 100644 --- a/radar_chart_test.go +++ b/radar_chart_test.go @@ -77,9 +77,9 @@ func TestRadarChart(t *testing.T) { for _, tt := range tests { painterOptions := PainterOptions{ - Type: ChartOutputSVG, - Width: 600, - Height: 400, + OutputFormat: ChartOutputSVG, + Width: 600, + Height: 400, } if tt.defaultTheme { t.Run(tt.name, func(t *testing.T) { diff --git a/series.go b/series.go index ce231fc..39fd140 100644 --- a/series.go +++ b/series.go @@ -9,9 +9,9 @@ import ( ) type SeriesData struct { - // The value of series data + // Value is the retained value for the data. Value float64 - // The style of series data + // Style provides a style for the series data. Style Style } @@ -73,41 +73,38 @@ const ( ) type SeriesMarkData struct { - // The mark data type, it can be "max", "min", "average". - // The "average" is only for mark line + // Type is the mark data type, it can be "max", "min", "average". "average" is only for mark line. Type string } type SeriesMarkPoint struct { - // The width of symbol, default value is 30 + // SymbolSize is the width of symbol, default value is 30. SymbolSize int - // The mark data of series mark point + // Data is the mark data for the series mark point. Data []SeriesMarkData } type SeriesMarkLine struct { - // The mark data of series mark line + // Data is the mark data for the series mark line. Data []SeriesMarkData } type Series struct { index int - // The type of series, it can be "line", "bar" or "pie". - // Default value is "line" + // Type is the type of series, it can be "line", "bar" or "pie". Default value is "line". Type string - // The data list of series + // Data provides the series data list. Data []SeriesData - // The Y axis index, it should be 0 or 1. - // Default value is 0 - AxisIndex int - // The style for series + // YAxisIndex is the index for the axis, it should be 0 or 1. + YAxisIndex int + // Style represents the style for the series. Style chart.Style - // The label for series + // Label provides the series labels. Label SeriesLabel - // The name of series + // Name specifies a name for the series. Name string // Radius for Pie chart, e.g.: 40%, default is "40%" Radius string - // Mark point for series + // MarkPoint provides a series for mark points. MarkPoint SeriesMarkPoint - // Make line for series + // MarkLine provides a series for mark lines. MarkLine SeriesMarkLine // Max value of series Min *float64 @@ -146,7 +143,7 @@ func (sl SeriesList) GetMinMax(axisIndex int) (float64, float64) { min := math.MaxFloat64 max := -math.MaxFloat64 for _, series := range sl { - if series.AxisIndex != axisIndex { + if series.YAxisIndex != axisIndex { continue } for _, item := range series.Data { diff --git a/table.go b/table.go index 2ff6044..d08f40d 100644 --- a/table.go +++ b/table.go @@ -33,35 +33,35 @@ type TableCell struct { } type TableChartOption struct { - // The output type - Type string - // The width of table - Width int - // The theme + // OutputFormat specifies the output type, "svg" or "png". + OutputFormat string + // Theme specifies the colors used for the table. Theme ColorPalette - // The padding of table cell + // Padding specifies the padding of table. Padding Box - // The header data of table + // Width specifies the width of the table. + Width int + // Header provides header data for the top of the table. Header []string - // The data of table + // Data provides the row and column data for the table. Data [][]string - // The span list of table column + // Spans provide the span for each column on the table. Spans []int - // The text align list of table cell + // TextAligns specifies the text alignment for each cell on the table. TextAligns []string - // The font size of table + // The font size of table contents. FontSize float64 - // The font to render the table with + // Font is the font used to render the table. Font *truetype.Font - // The font color of table + // FontColor is the color used for text on the table. FontColor Color - // The background color of header + // HeaderBackgroundColor provides a background color of header row. HeaderBackgroundColor Color - // The header font color + // HeaderFontColor specifies a text color for the header text. HeaderFontColor Color - // The background color of row + // RowBackgroundColors specifies an array of colors for each row. RowBackgroundColors []Color - // The background color + // BackgroundColor specifies a general background color. BackgroundColor Color // CellTextStyle customize text style of table cell CellTextStyle func(TableCell) *Style @@ -70,47 +70,27 @@ type TableChartOption struct { } type TableSetting struct { - // The color of header + // HeaderColor specifies the color of the header. HeaderColor Color - // The color of header text + // HeaderFontColor specifies the color of header text. HeaderFontColor Color - // The color of table text + // FontColor specifies the color of table text. FontColor Color - // The color list of row + // RowColors specifies an array of colors for each row. RowColors []Color - // The padding of cell - Padding Box + // Padding specifies the padding of each cell. + CellPadding Box } var TableLightThemeSetting = TableSetting{ - HeaderColor: Color{ - R: 240, - G: 240, - B: 240, - A: 255, - }, - HeaderFontColor: Color{ - R: 98, - G: 105, - B: 118, - A: 255, - }, - FontColor: Color{ - R: 70, - G: 70, - B: 70, - A: 255, - }, + HeaderColor: Color{R: 240, G: 240, B: 240, A: 255}, + HeaderFontColor: Color{R: 98, G: 105, B: 118, A: 255}, + FontColor: Color{R: 70, G: 70, B: 70, A: 255}, RowColors: []Color{ drawing.ColorWhite, - { - R: 247, - G: 247, - B: 247, - A: 255, - }, + {R: 247, G: 247, B: 247, A: 255}, }, - Padding: Box{ + CellPadding: Box{ Left: 10, Top: 10, Right: 10, @@ -119,39 +99,14 @@ var TableLightThemeSetting = TableSetting{ } var TableDarkThemeSetting = TableSetting{ - HeaderColor: Color{ - R: 38, - G: 38, - B: 42, - A: 255, - }, - HeaderFontColor: Color{ - R: 216, - G: 217, - B: 218, - A: 255, - }, - FontColor: Color{ - R: 216, - G: 217, - B: 218, - A: 255, - }, + HeaderColor: Color{R: 38, G: 38, B: 42, A: 255}, + HeaderFontColor: Color{R: 216, G: 217, B: 218, A: 255}, + FontColor: Color{R: 216, G: 217, B: 218, A: 255}, RowColors: []Color{ - { - R: 24, - G: 24, - B: 28, - A: 255, - }, - { - R: 38, - G: 38, - B: 42, - A: 255, - }, + {R: 24, G: 24, B: 28, A: 255}, + {R: 38, G: 38, B: 42, A: 255}, }, - Padding: Box{ + CellPadding: Box{ Left: 10, Top: 10, Right: 10, @@ -232,9 +187,9 @@ func (t *tableChart) render() (*renderInfo, error) { headerHeight := 0 if opt.Padding.IsZero() { if opt.Theme.IsDark() { - opt.Padding = TableDarkThemeSetting.Padding + opt.Padding = TableDarkThemeSetting.CellPadding } else { - opt.Padding = TableLightThemeSetting.Padding + opt.Padding = TableLightThemeSetting.CellPadding } } getCellTextStyle := opt.CellTextStyle @@ -346,9 +301,7 @@ func (t *tableChart) renderWithInfo(info *renderInfo) (Box, error) { } arr = append(arr, opt.Data...) top := 0 - heights := []int{ - info.HeaderHeight, - } + heights := []int{info.HeaderHeight} heights = append(heights, info.RowHeights...) // loop through all table cells to generate background color for i, textList := range arr { @@ -397,7 +350,7 @@ func (t *tableChart) Render() (Box, error) { r := p.render fn := chart.PNG - if p.outputType == ChartOutputSVG { + if p.outputFormat == ChartOutputSVG { fn = chart.SVG } newRender, err := fn(p.Width(), 100) diff --git a/table_test.go b/table_test.go index 3547974..5b6fa94 100644 --- a/table_test.go +++ b/table_test.go @@ -103,9 +103,9 @@ func TestTableChart(t *testing.T) { for i, tt := range tests { painterOptions := PainterOptions{ - Type: ChartOutputSVG, - Width: 600, - Height: 400, + OutputFormat: ChartOutputSVG, + Width: 600, + Height: 400, } runName := strconv.Itoa(i) if tt.theme != nil { diff --git a/theme.go b/theme.go index a11be8e..ead6ffb 100644 --- a/theme.go +++ b/theme.go @@ -33,6 +33,9 @@ type ColorPalette interface { GetSeriesColor(int) Color GetBackgroundColor() Color GetTextColor() Color + // WithAxisColor will provide a new ColorPalette that uses the specified color for axis values. + // This includes the Axis Stroke, Split Line, and Text Color. + WithAxisColor(Color) ColorPalette } type themeColorPalette struct { @@ -266,3 +269,12 @@ func (t *themeColorPalette) GetBackgroundColor() Color { func (t *themeColorPalette) GetTextColor() Color { return t.textColor } + +func (t *themeColorPalette) WithAxisColor(c Color) ColorPalette { + copy := *t + copy.name += "-modified" + copy.axisStrokeColor = c + copy.axisSplitLineColor = c + copy.textColor = c + return © +} diff --git a/theme_test.go b/theme_test.go index c9cfce7..92d08a7 100644 --- a/theme_test.go +++ b/theme_test.go @@ -3,7 +3,9 @@ package charts import ( "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/wcharczuk/go-chart/v2/drawing" ) func TestInstallGetTheme(t *testing.T) { @@ -31,9 +33,9 @@ func renderTestLineChartWithThemeName(t *testing.T, themeName string) string { t.Helper() p, err := NewPainter(PainterOptions{ - Type: ChartOutputSVG, - Width: 600, - Height: 400, + OutputFormat: ChartOutputSVG, + Width: 600, + Height: 400, }) require.NoError(t, err) opt := makeFullLineChartOption() @@ -87,3 +89,27 @@ func TestThemeGrafana(t *testing.T) { svg := renderTestLineChartWithThemeName(t, ThemeGrafana) assertEqualSVG(t, "\\nEmailUnion AdsVideo AdsDirectSearch EngineLine1.44k1.28k1.12k9608006404803201600MonTueWedThuFriSatSun", svg) } + +func TestWithAxisColor(t *testing.T) { + whiteCP := &themeColorPalette{ + name: "test", + isDarkMode: false, + axisSplitLineColor: drawing.ColorWhite, + axisStrokeColor: drawing.ColorWhite, + backgroundColor: drawing.ColorWhite, + textColor: drawing.ColorWhite, + seriesColors: []Color{drawing.ColorWhite}, + } + + blackCP := whiteCP.WithAxisColor(drawing.ColorBlack) + + assert.Equal(t, drawing.ColorWhite, whiteCP.axisSplitLineColor) + assert.Equal(t, drawing.ColorWhite, whiteCP.axisStrokeColor) + assert.Equal(t, drawing.ColorWhite, whiteCP.backgroundColor) + assert.Equal(t, drawing.ColorWhite, whiteCP.textColor) + assert.Equal(t, drawing.ColorWhite, whiteCP.seriesColors[0]) + + assert.Equal(t, drawing.ColorBlack, blackCP.GetAxisSplitLineColor()) + assert.Equal(t, drawing.ColorBlack, blackCP.GetAxisStrokeColor()) + assert.Equal(t, drawing.ColorBlack, blackCP.GetTextColor()) +} diff --git a/title.go b/title.go index 6675dc0..579f9bf 100644 --- a/title.go +++ b/title.go @@ -1,35 +1,35 @@ package charts import ( - "strconv" "strings" "github.com/golang/freetype/truetype" ) type TitleOption struct { - // The theme of chart + // Show specifies if the title should be rendered, set this to *false (through False()) to hide the title. + Show *bool + // Theme specifies the colors used for the title. Theme ColorPalette - // Title text, support \n for new line + // Text specifies the title text, supporting \n for new lines. Text string - // Subtitle text, support \n for new line + // Subtext to the title, supporting \n for new lines. Subtext string - // Distance between title component and the left side of the container. - // It can be pixel value: 20, percentage value: 20%, - // or position value: right, center. + // 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 - // Distance between title component and the top side of the container. - // It can be pixel value: 20. + // 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 - // The font of label - Font *truetype.Font - // The font size of label + // The font size of title. FontSize float64 - // The color of label + // Font is the font used to render the title. + Font *truetype.Font + // FontColor is the color used for text on the title. FontColor Color - // The subtext font size of label + // SubtextFontSize specifies the size of the subtext. SubtextFontSize float64 - // The subtext font color of label + // SubtextFontColor specifies a unique color for the subtext. SubtextFontColor Color } @@ -69,10 +69,13 @@ func NewTitlePainter(p *Painter, opt TitleOption) *titlePainter { func (t *titlePainter) Render() (Box, error) { opt := t.opt p := t.p - theme := opt.Theme + if isFalse(opt.Show) { + return BoxZero, nil + } + theme := opt.Theme if theme == nil { - theme = p.theme + theme = getPreferredTheme(p.theme) } if opt.Text == "" && opt.Subtext == "" { return BoxZero, nil @@ -141,24 +144,26 @@ func (t *titlePainter) Render() (Box, error) { titleX := 0 switch opt.Left { + case "", PositionLeft: + // no-op case PositionRight: titleX = p.Width() - textMaxWidth case PositionCenter: titleX = p.Width()>>1 - (textMaxWidth >> 1) default: - if strings.HasSuffix(opt.Left, "%") { - value, _ := strconv.Atoi(strings.ReplaceAll(opt.Left, "%", "")) - titleX = p.Width() * value / 100 + if v, err := parseFlexibleValue(opt.Left, float64(p.Width())); err != nil { + return BoxZero, err } else { - value, _ := strconv.Atoi(opt.Left) - titleX = value + titleX = int(v) } } titleY := 0 - // TODO - top only supports numerical values if opt.Top != "" { - value, _ := strconv.Atoi(opt.Top) - titleY += value + if v, err := parseFlexibleValue(opt.Top, float64(p.Height())); err != nil { + return BoxZero, err + } else { + titleY = int(v) + } } for _, item := range measureOptions { p.OverrideTextStyle(item.style) diff --git a/title_test.go b/title_test.go index 2a6764f..2446f20 100644 --- a/title_test.go +++ b/title_test.go @@ -62,9 +62,9 @@ func TestTitleRenderer(t *testing.T) { for i, tt := range tests { t.Run(strconv.Itoa(i), func(t *testing.T) { p, err := NewPainter(PainterOptions{ - Type: ChartOutputSVG, - Width: 600, - Height: 400, + OutputFormat: ChartOutputSVG, + Width: 600, + Height: 400, }, PainterThemeOption(GetTheme(ThemeLight))) require.NoError(t, err) data, err := tt.render(p) diff --git a/util.go b/util.go index 15b7fb2..6b0c5f1 100644 --- a/util.go +++ b/util.go @@ -1,6 +1,7 @@ package charts import ( + "fmt" "math" "strconv" "strings" @@ -9,19 +10,24 @@ import ( "github.com/wcharczuk/go-chart/v2" ) -func TrueFlag() *bool { - t := true - return &t +// True returns a pointer to a true bool, useful for configuration. +func True() *bool { + return BoolPointer(true) } -func FalseFlag() *bool { - f := false - return &f +// False returns a pointer to a false bool, useful for configuration. +func False() *bool { + return BoolPointer(false) } -func ZeroFloat() *float64 { - v := 0.0 - return &v +// BoolPointer returns a pointer to the given bool value, useful for configuration. +func BoolPointer(b bool) *bool { + return &b +} + +// FloatPointer returns a pointer to the given float64 value, useful for configuration. +func FloatPointer(f float64) *float64 { + return &f } func isFalse(flag *bool) bool { @@ -120,20 +126,27 @@ func reverseIntSlice(intList []int) { } } -func convertPercent(value string) float64 { +func parseFlexibleValue(value string, percentTotal float64) (float64, error) { + if strings.HasSuffix(value, "%") { + percent, err := convertPercent(value) + if err != nil { + return 0, err + } + return percent * percentTotal, nil + } else { + return strconv.ParseFloat(value, 64) + } +} + +func convertPercent(value string) (float64, error) { if !strings.HasSuffix(value, "%") { - return -1 + return -1, fmt.Errorf("not a percent input: %s", value) } - v, err := strconv.Atoi(strings.ReplaceAll(value, "%", "")) + v, err := strconv.ParseFloat(strings.ReplaceAll(value, "%", ""), 64) if err != nil { - return -1 + return -1, err } - return float64(v) / 100 -} - -func NewFloatPoint(f float64) *float64 { - v := f - return &v + return v / 100.0, nil } const K_VALUE = float64(1000) @@ -163,15 +176,15 @@ const defaultRadiusPercent = 0.4 func getRadius(diameter float64, radiusValue string) float64 { var radius float64 if len(radiusValue) != 0 { - v := convertPercent(radiusValue) + v, _ := convertPercent(radiusValue) if v != -1 { - radius = float64(diameter) * v + radius = diameter * v } else { radius, _ = strconv.ParseFloat(radiusValue, 64) } } if radius <= 0 { - radius = float64(diameter) * defaultRadiusPercent + radius = diameter * defaultRadiusPercent } return radius } diff --git a/util_test.go b/util_test.go index 1695cad..274ea43 100644 --- a/util_test.go +++ b/util_test.go @@ -96,8 +96,33 @@ func TestReverseSlice(t *testing.T) { assert.Equal(t, []int{9, 7, 5, 3, 1}, numbers) } +func TestParseFlexibleValue(t *testing.T) { + t.Run("percent", func(t *testing.T) { + result, err := parseFlexibleValue("10%", 200) + assert.NoError(t, err) + assert.Equal(t, 20.0, result) + }) + t.Run("value", func(t *testing.T) { + result, err := parseFlexibleValue("10", 200) + assert.NoError(t, err) + assert.Equal(t, 10.0, result) + }) +} + func TestConvertPercent(t *testing.T) { - assert.Equal(t, -1.0, convertPercent("1")) - assert.Equal(t, -1.0, convertPercent("a%")) - assert.Equal(t, 0.1, convertPercent("10%")) + verifyConvertPercent(t, -1.0, "1") + verifyConvertPercent(t, -1.0, "a%") + verifyConvertPercent(t, 0.1, "10%") +} + +func verifyConvertPercent(t *testing.T, expected float64, input string) { + t.Helper() + + v, err := convertPercent(input) + if expected == -1 { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + assert.Equal(t, expected, v) } diff --git a/xaxis.go b/xaxis.go index 5849fcc..0988cdb 100644 --- a/xaxis.go +++ b/xaxis.go @@ -5,30 +5,28 @@ import ( ) type XAxisOption struct { - // The font of x-axis - Font *truetype.Font - // The boundary gap on both sides of a coordinate axis. - // Nil or *true means the center part of two axis ticks - BoundaryGap *bool - // The data value of x-axis - Data []string - // The theme of chart - Theme ColorPalette - // The font size of x-axis label - FontSize float64 - // The flag for show axis, set this to *false will hide axis + // Show specifies if the x-axis should be rendered, set this to *false (through False()) to hide the axis. Show *bool - // The position of axis, it can be 'top' or 'bottom' + // Theme specifies the colors used for the x-axis. + Theme ColorPalette + // Data provides labels for the x-axis. + Data []string + // DataStartIndex specifies what index the Data values should start from. + DataStartIndex int + // Position describes the position of x-axis, it can be 'top' or 'bottom'. Position string - // The line color of axis - StrokeColor Color - // The color of label + // BoundaryGap specifies that the chart should have additional space on the left and right, with data points being + // centered between two axis ticks. Enabled by default, specify *false (through False()) to change the spacing. + BoundaryGap *bool + // FontSize specifies the font size of each label. + FontSize float64 + // Font is the font used to render each label. + Font *truetype.Font + // FontColor is the color used for text rendered. FontColor Color - // The text rotation of label + // TextRotation are the radians for rotating the label. TextRotation float64 - // The first axis - FirstAxis int - // The offset of label + // LabelOffset is the offset of each label. LabelOffset Box // Unit is a suggestion for how large the axis step is, this is a recommendation only. Larger numbers result in fewer labels. Unit float64 @@ -58,24 +56,22 @@ func (opt *XAxisOption) ToAxisOption() AxisOption { axisOpt := AxisOption{ Theme: opt.Theme, Data: opt.Data, + DataStartIndex: opt.DataStartIndex, BoundaryGap: opt.BoundaryGap, Position: position, - StrokeColor: opt.StrokeColor, FontSize: opt.FontSize, Font: opt.Font, FontColor: opt.FontColor, Show: opt.Show, Unit: opt.Unit, LabelCount: opt.LabelCount, - SplitLineColor: opt.Theme.GetAxisSplitLineColor(), TextRotation: opt.TextRotation, LabelOffset: opt.LabelOffset, - FirstAxis: opt.FirstAxis, } if opt.isValueAxis { axisOpt.SplitLineShow = true axisOpt.StrokeWidth = -1 - axisOpt.BoundaryGap = FalseFlag() + axisOpt.BoundaryGap = False() } return axisOpt } diff --git a/yaxis.go b/yaxis.go index 56f42bb..1b07067 100644 --- a/yaxis.go +++ b/yaxis.go @@ -5,30 +5,30 @@ import ( ) type YAxisOption struct { - // The minimun value of axis. + // Show specifies if the y-axis should be rendered, set this to *false (through False()) to hide the axis. + Show *bool + // Theme specifies the colors used for the x-axis. + Theme ColorPalette + // Color for y-axis. + AxisColor Color + // Min, if set this will force the minimum value of y-axis. Min *float64 - // The maximum value of axis. + // Max, if set this will force the maximum value of y-axis. Max *float64 - // RangeValuePaddingScale suggest a scale of padding added to the max and min values + // RangeValuePaddingScale suggest a scale of padding added to the max and min values. RangeValuePaddingScale *float64 - // The font of y-axis - Font *truetype.Font - // The data value of y-axis + // Data provides labels for the y-axis. Data []string - // The theme of chart - Theme ColorPalette - // The font size of y-axis label - FontSize float64 - // The position of axis, it can be 'left' or 'right' + // Position describes the position of y-axis, it can be 'left' or 'right'. Position string - // The color of label + // FontSize specifies the font size of each label. + FontSize float64 + // Font is the font used to render each label. + Font *truetype.Font + // FontColor is the color used for text rendered. FontColor Color - // Formatter for y-axis text value + // Formatter for replacing y-axis text values. Formatter string - // Color for y-axis - Color Color - // The flag for show axis, set this to *false will hide axis - Show *bool // 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. @@ -71,20 +71,19 @@ func (opt *YAxisOption) ToAxisOption(p *Painter) AxisOption { StrokeWidth: -1, Font: opt.Font, FontColor: opt.FontColor, - BoundaryGap: FalseFlag(), + BoundaryGap: False(), Unit: opt.Unit, LabelCount: opt.LabelCount, LabelSkipCount: opt.LabelSkipCount, SplitLineShow: true, - SplitLineColor: theme.GetAxisSplitLineColor(), Show: opt.Show, } - if !opt.Color.IsZero() { - axisOpt.FontColor = opt.Color - axisOpt.StrokeColor = opt.Color + if !opt.AxisColor.IsZero() { + axisOpt.FontColor = opt.AxisColor + axisOpt.Theme = theme.WithAxisColor(opt.AxisColor) } if opt.isCategoryAxis { - axisOpt.BoundaryGap = TrueFlag() + axisOpt.BoundaryGap = True() axisOpt.StrokeWidth = 1 axisOpt.SplitLineShow = false } diff --git a/yaxis_test.go b/yaxis_test.go index da524f1..c26f656 100644 --- a/yaxis_test.go +++ b/yaxis_test.go @@ -29,9 +29,9 @@ func TestRightYAxis(t *testing.T) { for i, tt := range tests { t.Run(strconv.Itoa(i), func(t *testing.T) { p, err := NewPainter(PainterOptions{ - Type: ChartOutputSVG, - Width: 600, - Height: 400, + OutputFormat: ChartOutputSVG, + Width: 600, + Height: 400, }, PainterThemeOption(GetTheme(ThemeLight)), PainterPaddingOption(Box{ Top: 10, Right: 10,