diff --git a/pkg/bar_chart.go b/pkg/bar_chart.go index 3573343..554f88e 100644 --- a/pkg/bar_chart.go +++ b/pkg/bar_chart.go @@ -19,22 +19,25 @@ func NewBarChart() *BarChart { } func (c *BarChart) GetVertical(xData []string, yData [][]float64, names []string, req *ChartRequest) ([]byte, error) { + isMini := IsMiniChart(req) + showLegend := true + paddings := GetPaddings(req) + titleSizes := GetTitleSizes(req) + if isMini { + showLegend = false + } p, err := charts.BarRender( yData, - charts.TitleOptionFunc(charts.TitleOption{ - Text: req.ChartTitle, - Subtext: req.ChartSubtitle, - SubtextFontSize: DEFAULT_SUBTITLE_FONT_SIZE, - Left: charts.PositionCenter, - SubtextFontColor: DEFAULT_SUBTITLE_COLOR, - }), + charts.TitleOptionFunc(titleSizes), charts.HeightOptionFunc(req.Height), charts.WidthOptionFunc(req.Width), + charts.PaddingOptionFunc(paddings), charts.XAxisDataOptionFunc(xData), charts.LegendOptionFunc(charts.LegendOption{ Orient: charts.OrientHorizontal, Data: names, Left: charts.PositionLeft, + Show: &showLegend, }), charts.MarkLineOptionFunc(0, charts.SeriesMarkDataTypeAverage), charts.MarkPointOptionFunc(0, charts.SeriesMarkDataTypeMax, @@ -47,7 +50,11 @@ func (c *BarChart) GetVertical(xData []string, yData [][]float64, names []string Bottom: DEFAULT_PADDING_BOTTOM, } opt.ValueFormatter = func(f float64) string { + if isMini { + return "-" + } return fmt.Sprintf("%s %s", NumberToK(&f), req.Metric) + } idx := len(opt.SeriesList) - 1 if len(opt.SeriesList) > 1 { @@ -57,9 +64,11 @@ func (c *BarChart) GetVertical(xData []string, yData [][]float64, names []string charts.SeriesMarkDataTypeMax, charts.SeriesMarkDataTypeMin, ) - opt.SeriesList[idx].MarkLine = charts.NewMarkLine( - charts.SeriesMarkDataTypeAverage, - ) + if !isMini { + opt.SeriesList[idx].MarkLine = charts.NewMarkLine( + charts.SeriesMarkDataTypeAverage, + ) + } }, ) if err != nil { @@ -90,15 +99,9 @@ func (c *BarChart) GetStacked(xData []string, yData [][]float64, zData [][]float } series = append(series, s) } - + titleSizes := GetTitleSizes(req) opt := charts.ChartOption{ - Title: charts.TitleOption{ - Text: req.ChartTitle, - Subtext: req.ChartSubtitle, - SubtextFontSize: DEFAULT_SUBTITLE_FONT_SIZE, - SubtextFontColor: DEFAULT_SUBTITLE_COLOR, - Left: charts.PositionCenter, - }, + Title: titleSizes, XAxis: charts.NewXAxisOption(xData), Legend: charts.NewLegendOption(names), YAxisOptions: []charts.YAxisOption{ @@ -154,22 +157,33 @@ func (c *BarChart) GetStacked(xData []string, yData [][]float64, zData [][]float } func (c *BarChart) GetHorizontal(xData []string, yData [][]float64, names []string, req *ChartRequest) ([]byte, error) { + isMini := IsMiniChart(req) + showLegend := true + paddings := GetPaddings(req) + titleSizes := GetTitleSizes(req) + if isMini { + showLegend = false + } p, err := charts.HorizontalBarRender( yData, - charts.TitleOptionFunc(charts.TitleOption{ - Text: req.ChartTitle, - Subtext: req.ChartSubtitle, - SubtextFontSize: DEFAULT_SUBTITLE_FONT_SIZE, - SubtextFontColor: DEFAULT_SUBTITLE_COLOR, - Left: charts.PositionCenter, - }), + charts.TitleOptionFunc(titleSizes), charts.HeightOptionFunc(req.Height), charts.WidthOptionFunc(req.Width), + charts.PaddingOptionFunc(paddings), charts.YAxisDataOptionFunc(xData), + charts.LegendOptionFunc(charts.LegendOption{ + Orient: charts.OrientHorizontal, + Data: names, + Left: charts.PositionLeft, + Show: &showLegend, + }), func(opt *charts.ChartOption) { opt.Theme = req.Theme opt.Type = req.Output opt.ValueFormatter = func(f float64) string { + if isMini { + return "-" + } return fmt.Sprintf("%s %s", NumberToK(&f), req.Metric) } }, diff --git a/pkg/chart.go b/pkg/chart.go index 538fd79..2cb6789 100644 --- a/pkg/chart.go +++ b/pkg/chart.go @@ -3,12 +3,9 @@ package pkg import ( "errors" "net/http" - "net/url" "os" - "strconv" - "strings" - "github.com/imroc/req/v3" + charts "github.com/vicanso/go-charts/v2" "github.com/wcharczuk/go-chart/v2/drawing" ) @@ -17,7 +14,10 @@ const ( DEFAULT_PADDING_RIGHT = 20 DEFAULT_PADDING_BOTTOM = 20 DEFAULT_PADDING_LEFT = 20 - DEFAULT_SUBTITLE_FONT_SIZE = 11 + DEFAULT_TITLE_FONT_SIZE = 12 + DEFAULT_SUBTITLE_FONT_SIZE = 10 + MINI_CHART_WIDTH = 300 + MINI_CHART_HEIGHT = 300 BAR_STYLE_VERTICAL = "vertical" BAR_STYLE_HORIZONTAL = "horizontal" @@ -88,44 +88,6 @@ func SetHeadersResponseTxt(header http.Header) { header.Set("X-XSS-Protection", "1; mode=block") } -func IsURL(urlStr string) bool { - parsedURL, err := url.ParseRequestURI(urlStr) - return err == nil && parsedURL.Scheme != "" && parsedURL.Host != "" -} - -func IsAllowedDomain(urlStr string, allowedDomains string) bool { - if allowedDomains == "" { - return false // default do not allow any urls - } - - // Parse the URL to extract the domain - parsedURL, err := url.Parse(urlStr) - if err != nil { - return false // If the URL is invalid, do not allow - } - domain := parsedURL.Hostname() - - // Split the allowedDomains into a slice - domains := strings.Split(allowedDomains, ",") - - // Check if the domain is in the list of allowed domains - for _, d := range domains { - if domain == d { - return true - } - } - - return false -} - -func GetURL(urlStr string) (string, error) { - resp, err := req.Get(urlStr) - if err != nil { - return "", err - } - return resp.ToString() -} - func SetDataIfRemoteURL(req *ChartRequest) error { allowedRemoteDomains := os.Getenv("ALLOWED_REMOTE_DOMAINS") if allowedRemoteDomains == "" { @@ -144,28 +106,46 @@ func SetDataIfRemoteURL(req *ChartRequest) error { return nil } -// NumberToK converts a number to a string with 'k' for thousands and 'm' for millions. -func NumberToK(num *float64) string { - if num == nil { - return "0" - } +func IsMiniChart(req *ChartRequest) bool { + return req.Width <= MINI_CHART_WIDTH && req.Height <= MINI_CHART_HEIGHT +} - formatNumber := func(n float64) string { - if n == float64(int64(n)) { - // If n is an integer, format without decimal places. - return strconv.FormatFloat(n, 'f', 0, 64) +func GetPaddings(req *ChartRequest) charts.Box { + paddings := charts.Box{ + Top: 10, + Bottom: 10, + Left: 10, + Right: 10, + } + if IsMiniChart(req) { + paddings = charts.Box{ + Top: 10, + Bottom: -20, + Left: -10, + Right: 10, } - // Otherwise, format with one decimal place. - return strconv.FormatFloat(n, 'f', 1, 64) } + return paddings +} - if *num < 1000 { - return formatNumber(*num) +func GetTitleSizes(req *ChartRequest) charts.TitleOption { + titleSizes := charts.TitleOption{ + Text: req.ChartTitle, + Subtext: req.ChartSubtitle, + FontSize: DEFAULT_TITLE_FONT_SIZE, + SubtextFontSize: DEFAULT_SUBTITLE_FONT_SIZE, + Left: charts.PositionCenter, + SubtextFontColor: DEFAULT_SUBTITLE_COLOR, } - - if *num < 1000000 { - return formatNumber(*num/1000) + "k" + if IsMiniChart(req) { + titleSizes = charts.TitleOption{ + Text: Truncate(req.ChartTitle, 17), + Subtext: Truncate(req.ChartSubtitle, 17), + FontSize: DEFAULT_TITLE_FONT_SIZE, + SubtextFontSize: DEFAULT_SUBTITLE_FONT_SIZE, + Left: charts.PositionCenter, + SubtextFontColor: DEFAULT_SUBTITLE_COLOR, + } } - - return formatNumber(*num/1000000) + "m" + return titleSizes } diff --git a/pkg/line_chart.go b/pkg/line_chart.go index a190290..1978417 100644 --- a/pkg/line_chart.go +++ b/pkg/line_chart.go @@ -21,32 +21,40 @@ func (c *LineChart) Get(xData []string, yData [][]float64, names []string, req * if req.Line == "fill" { fill = true } + isMini := IsMiniChart(req) + + showLegend := true + paddings := GetPaddings(req) + titleSizes := GetTitleSizes(req) + if isMini { + showLegend = false + } p, err := charts.LineRender( yData, charts.HeightOptionFunc(req.Height), charts.WidthOptionFunc(req.Width), - charts.TitleOptionFunc(charts.TitleOption{ - Text: req.ChartTitle, - Subtext: req.ChartSubtitle, - SubtextFontSize: DEFAULT_SUBTITLE_FONT_SIZE, - Left: charts.PositionCenter, - SubtextFontColor: DEFAULT_SUBTITLE_COLOR, - }), + charts.PaddingOptionFunc(paddings), + charts.TitleOptionFunc(titleSizes), charts.XAxisDataOptionFunc(xData), charts.LegendOptionFunc(charts.LegendOption{ Orient: charts.OrientHorizontal, Data: names, Left: charts.PositionLeft, + Show: &showLegend, }), func(opt *charts.ChartOption) { opt.Type = req.Output opt.Theme = req.Theme opt.Legend.Padding = charts.Box{ - Top: DEFAULT_PADDING_TOP, - Bottom: DEFAULT_PADDING_BOTTOM * 2, + Top: DEFAULT_PADDING_TOP * 2, + Bottom: DEFAULT_PADDING_BOTTOM, } opt.ValueFormatter = func(f float64) string { + if isMini { + return "-" + } return fmt.Sprintf("%s %s", NumberToK(&f), req.Metric) + } opt.FillArea = fill @@ -58,9 +66,12 @@ func (c *LineChart) Get(xData []string, yData [][]float64, names []string, req * charts.SeriesMarkDataTypeMax, charts.SeriesMarkDataTypeMin, ) - opt.SeriesList[idx].MarkLine = charts.NewMarkLine( - charts.SeriesMarkDataTypeAverage, - ) + if !isMini { + opt.SeriesList[idx].MarkLine = charts.NewMarkLine( + charts.SeriesMarkDataTypeAverage, + ) + } + }, ) if err != nil { diff --git a/pkg/util.go b/pkg/util.go new file mode 100644 index 0000000..64542b8 --- /dev/null +++ b/pkg/util.go @@ -0,0 +1,80 @@ +package pkg + +import ( + "net/url" + "strconv" + "strings" + + "github.com/imroc/req/v3" +) + +func IsURL(urlStr string) bool { + parsedURL, err := url.ParseRequestURI(urlStr) + return err == nil && parsedURL.Scheme != "" && parsedURL.Host != "" +} + +func IsAllowedDomain(urlStr string, allowedDomains string) bool { + if allowedDomains == "" { + return false // default do not allow any urls + } + + // Parse the URL to extract the domain + parsedURL, err := url.Parse(urlStr) + if err != nil { + return false // If the URL is invalid, do not allow + } + domain := parsedURL.Hostname() + + // Split the allowedDomains into a slice + domains := strings.Split(allowedDomains, ",") + + // Check if the domain is in the list of allowed domains + for _, d := range domains { + if domain == d { + return true + } + } + + return false +} + +func GetURL(urlStr string) (string, error) { + resp, err := req.Get(urlStr) + if err != nil { + return "", err + } + return resp.ToString() +} + +// NumberToK converts a number to a string with 'k' for thousands and 'm' for millions. +func NumberToK(num *float64) string { + if num == nil { + return "0" + } + + formatNumber := func(n float64) string { + if n == float64(int64(n)) { + // If n is an integer, format without decimal places. + return strconv.FormatFloat(n, 'f', 0, 64) + } + // Otherwise, format with one decimal place. + return strconv.FormatFloat(n, 'f', 1, 64) + } + + if *num < 1000 { + return formatNumber(*num) + } + + if *num < 1000000 { + return formatNumber(*num/1000) + "k" + } + + return formatNumber(*num/1000000) + "m" +} + +func Truncate(s string, max int) string { + if len(s) > max { + return s[:max] + "..." + } + return s +}