// charts provides functions to create charts // These are used to show statistics about users // See userStatistics.go to see how input statistics are created package createCharts import "seekia/internal/profiles/userStatistics" import goChart "github.com/wcharczuk/go-chart/v2" import "github.com/wcharczuk/go-chart/v2/drawing" import "image" import "errors" // Inputs: // -string: Chart title // -[]userStatistics.StatisticsItem: Statistics items list // -func(float64)(string, error): formatYAxisValuesFunction // -This will take values such as 1000000 and turn them to "1 million" // -bool: Y-axis units exist // -string: Y-axis units (example: " Users") // Outputs: // -image.Image // -error func CreateBarChart(chartTitle string, chartStatisticsItemsList []userStatistics.StatisticsItem, formatYAxisValuesFunction func(float64)(string, error), yAxisUnitsProvided bool, yAxisUnits string)(image.Image, error){ if (len(chartStatisticsItemsList) == 0) { return nil, errors.New("CreateBarChart called with empty chartStatisticsItemsList") } chartItemsList := make([]goChart.Value, 0, len(chartStatisticsItemsList)) for _, statisticsItem := range chartStatisticsItemsList{ itemLabel := statisticsItem.LabelFormatted itemValue := statisticsItem.Value // We make sure this function does not error _, err := formatYAxisValuesFunction(itemValue) if (err != nil){ return nil, errors.New("Invalid chartStatisticsItemsList: Item value is invalid. Reason: " + err.Error()) } newChartValue := goChart.Value{ Label: itemLabel, Value: itemValue, } chartItemsList = append(chartItemsList, newChartValue) } if (len(chartItemsList) == 1){ // This package cannot create bar charts with only 1 item // Thus, we must add an empty item newChartValue := goChart.Value{ Style: goChart.Style{ StrokeColor: drawing.ColorWhite, FillColor: drawing.ColorWhite, }, Label: "", Value: .001, } chartItemsList = append(chartItemsList, newChartValue) } chartStyleObject := goChart.Style{ Padding: goChart.Box{ Top: 40, Bottom: 0, Left: 0, Right: 0, }, } titleStyleObject := goChart.Style{ Padding: goChart.Box{ Top: 10, Bottom: 30, }, FontSize: 15, FontColor: drawing.ColorBlack, } yAxisObject := goChart.YAxis{ ValueFormatter: func(v interface{}) string { valueFloat64 := v.(float64) valueFormatted, err := formatYAxisValuesFunction(valueFloat64) if (err != nil){ return "ERROR" } if (yAxisUnitsProvided == false){ return valueFormatted } result := valueFormatted + yAxisUnits return result }, } barChartObject := goChart.BarChart{ Title: chartTitle, TitleStyle: titleStyleObject, Background: chartStyleObject, Height: 500, BarWidth: 60, Bars: chartItemsList, YAxis: yAxisObject, } collector := &goChart.ImageWriter{} barChartObject.Render(goChart.PNG, collector) goImage, err := collector.Image() if (err != nil) { return nil, err } return goImage, nil } func CreateDonutChart(chartTitle string, chartStatisticsItemsList []userStatistics.StatisticsItem)(image.Image, error){ if (len(chartStatisticsItemsList) == 0) { return nil, errors.New("CreateDonutChart called with empty chartStatisticsItemsList") } chartItemsList := make([]goChart.Value, 0, len(chartStatisticsItemsList)) for _, statisticsItem := range chartStatisticsItemsList{ itemLabel := statisticsItem.LabelFormatted // Value is always a number representing the percentage of the donut itemValue := statisticsItem.Value newChartValue := goChart.Value{ Label: itemLabel, Value: itemValue, } chartItemsList = append(chartItemsList, newChartValue) } chartStyleObject := goChart.Style{ Padding: goChart.Box{ Top: 20, Bottom: 0, Left: 0, Right: 0, }, } titleStyleObject := goChart.Style{ FontSize: 15, FontColor: drawing.ColorBlack, } donutChartObject := goChart.DonutChart{ Title: chartTitle, TitleStyle: titleStyleObject, Background: chartStyleObject, Height: 500, Values: chartItemsList, } if (len(chartItemsList) == 1){ // Default is transparent, we need to add color sliceStyleObject := goChart.Style{ FillColor: drawing.ColorRed, } donutChartObject.SliceStyle = sliceStyleObject } collector := &goChart.ImageWriter{} donutChartObject.Render(goChart.PNG, collector) goImage, err := collector.Image() if (err != nil) { return nil, err } return goImage, nil }