From a6f8317880993e00d6069d89eea3ef982207c402 Mon Sep 17 00:00:00 2001 From: Pulkit Kathuria Date: Mon, 29 Jan 2024 10:09:57 +0900 Subject: [PATCH] mini chart utils --- pkg/bar_chart.go | 29 ++++++++----- pkg/chart.go | 106 +++++++++++++++++----------------------------- pkg/line_chart.go | 42 ++++-------------- pkg/util.go | 80 ++++++++++++++++++++++++++++++++++ 4 files changed, 146 insertions(+), 111 deletions(-) create mode 100644 pkg/util.go diff --git a/pkg/bar_chart.go b/pkg/bar_chart.go index 3573343..f1cfc19 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 { diff --git a/pkg/chart.go b/pkg/chart.go index e030ba8..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" ) @@ -19,6 +16,8 @@ const ( DEFAULT_PADDING_LEFT = 20 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" @@ -89,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 == "" { @@ -145,35 +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" - } - - 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) - } +func IsMiniChart(req *ChartRequest) bool { + return req.Width <= MINI_CHART_WIDTH && req.Height <= MINI_CHART_HEIGHT +} - if *num < 1000 { - return formatNumber(*num) +func GetPaddings(req *ChartRequest) charts.Box { + paddings := charts.Box{ + Top: 10, + Bottom: 10, + Left: 10, + Right: 10, } - - if *num < 1000000 { - return formatNumber(*num/1000) + "k" + if IsMiniChart(req) { + paddings = charts.Box{ + Top: 10, + Bottom: -20, + Left: -10, + Right: 10, + } } - - return formatNumber(*num/1000000) + "m" + return paddings } -func Truncate(s string, max int) string { - if len(s) > max { - return s[:max] + "..." +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 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 s + return titleSizes } diff --git a/pkg/line_chart.go b/pkg/line_chart.go index 7d9e1c4..1978417 100644 --- a/pkg/line_chart.go +++ b/pkg/line_chart.go @@ -17,43 +17,17 @@ func NewLineChart() *LineChart { } func (c *LineChart) Get(xData []string, yData [][]float64, names []string, req *ChartRequest) ([]byte, error) { - fill := true + fill := false if req.Line == "fill" { fill = true } - isMini := false + isMini := IsMiniChart(req) + showLegend := true - paddings := charts.Box{ - Top: 10, - Bottom: 10, - Left: 10, - Right: 10, - } - 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 req.Width <= 300 && req.Height <= 300 { + paddings := GetPaddings(req) + titleSizes := GetTitleSizes(req) + if isMini { showLegend = false - isMini = true - paddings = charts.Box{ - Top: 10, - Bottom: -20, - Left: -10, - Right: 10, - } - 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, - } } p, err := charts.LineRender( yData, @@ -72,8 +46,8 @@ func (c *LineChart) Get(xData []string, yData [][]float64, names []string, req * opt.Type = req.Output opt.Theme = req.Theme opt.Legend.Padding = charts.Box{ - Top: 0, - Bottom: 0, + Top: DEFAULT_PADDING_TOP * 2, + Bottom: DEFAULT_PADDING_BOTTOM, } opt.ValueFormatter = func(f float64) string { if isMini { 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 +}