diff --git a/README.md b/README.md index dce5863..b939d07 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,8 @@ - [Bars](#bars) - [`GET /donut`](#get-donut) - [Donuts](#donuts) + - [`GET /pie`](#get-pie) + - [Donuts](#donuts-1) ## [`GET /line`](https://instachart.coveritup.app/line?title=Single+Line+series&x_label=dates&y_label=amount&data={%20%22x%22:%20[[%222022-12-23%22,%222022-12-24%22,%222023-12-25%22]],%20%22y%22:%20[[1,2,3]]%20}) @@ -187,3 +189,40 @@ https://instachart.coveritup.app/donut?title=Donut+Chart&data={
![Donut chart](https://instachart.coveritup.app/donut?title=Donut+Chart&data={%20"names":%20["Monday",%20"Friday",%20"Saturday",%20"Sunday"],%20"values":%20[4,%206%20,7,%209]%20}) + + +## [`GET /pie`](https://instachart.coveritup.app/pie?title=Pie+Chart&data={%20"names":%20["Monday",%20"Friday",%20"Saturday",%20"Sunday"],%20"values":%20[4,%206%20,7,%209]%20}) + + +| Query | Required | Description | Example | +| :------- | :------- | :---------- | :-------------------------------------------------------- | +| `data` | ◯ | JSON | `?data={ "names": ["Mon","Tue","Wed"],"values": [1,2,3]}` | +| `title` | | string | | +| `height` | | int | | +| `width` | | int | | + + +| `data` | Required | Description | Example | +| :------- | :------- | :------------- | :-------------------------- | +| `names` | ◯ | Array (string) | `"x": ["Mon","Tue", "Wed"]` | +| `values` | ◯ | Array (int) | `"y": [1,2,3]` | + + +### Donuts + +
+ REQUEST URL + +```sh +https://instachart.coveritup.app/pie?title=Pie+Chart&data={ + "names": ["Monday", "Friday", "Saturday", "Sunday"], + "values": [4, 6 ,7, 9] +} +``` + +
+ +
+ +![Donut chart](https://instachart.coveritup.app/pie?title=Pie+Chart&data={%20"names":%20["Monday",%20"Friday",%20"Saturday",%20"Sunday"],%20"values":%20[4,%206%20,7,%209]%20}) + diff --git a/pkg/pie_chart.go b/pkg/pie_chart.go new file mode 100644 index 0000000..743e56f --- /dev/null +++ b/pkg/pie_chart.go @@ -0,0 +1,23 @@ +package pkg + +import "github.com/wcharczuk/go-chart/v2" + +type PieChart struct { + chart *Chart +} + +func NewPieChart() *PieChart { + return &PieChart{ + chart: NewChart(), + } +} +func (c *PieChart) GetValues(names []string, values []float64) []chart.Value { + var chartValues []chart.Value + for i := 0; i < len(names); i++ { + chartValues = append(chartValues, chart.Value{ + Value: values[i], + Label: names[i], + }) + } + return chartValues +} diff --git a/pkg/pie_chart_handler.go b/pkg/pie_chart_handler.go new file mode 100644 index 0000000..7a85eda --- /dev/null +++ b/pkg/pie_chart_handler.go @@ -0,0 +1,62 @@ +package pkg + +import ( + "bytes" + "encoding/json" + "net/http" + + "github.com/labstack/echo/v4" + "github.com/wcharczuk/go-chart/v2" +) + +type PieChartHandler struct { + chart *PieChart +} + +func NewPieChartHandler() *PieChartHandler { + return &PieChartHandler{ + chart: NewPieChart(), + } +} + +type PieChartRequest struct { + ChartData string `json:"data" query:"data" form:"data" validate:"required" message:"data is required"` + ChartTitle string `json:"title" query:"title" form:"title"` + Height int `json:"height" query:"height" form:"height"` + Width int `json:"width" query:"width" form:"width"` +} + +type PieChartData struct { + Names []string `json:"names"` + Values []float64 `json:"values"` +} + +func (h *PieChartHandler) Get(c echo.Context) ([]byte, error) { + req := new(PieChartRequest) + if err := BindRequest(c, req); err != nil { + return nil, echo.NewHTTPError(http.StatusUnprocessableEntity, err.Error()) + } + + var data PieChartData + if err := json.Unmarshal([]byte(req.ChartData), &data); err != nil { + return nil, echo.NewHTTPError(http.StatusUnprocessableEntity, err.Error()) + } + + if len(data.Values) == 0 || len(data.Values) != len(data.Names) { + return nil, echo.NewHTTPError(http.StatusUnprocessableEntity, "data provided is invalid") + } + + graph := chart.PieChart{ + Title: req.ChartTitle, + Height: req.Height, + Width: req.Width, + Values: h.chart.GetValues(data.Names, data.Values), + } + + buffer := bytes.NewBuffer([]byte{}) + err := graph.Render(chart.PNG, buffer) + if err != nil { + return nil, echo.NewHTTPError(http.StatusUnprocessableEntity, err.Error()) + } + return buffer.Bytes(), err +} diff --git a/pkg/pie_chart_handler_test.go b/pkg/pie_chart_handler_test.go new file mode 100644 index 0000000..f2c8369 --- /dev/null +++ b/pkg/pie_chart_handler_test.go @@ -0,0 +1,53 @@ +package pkg + +import ( + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/labstack/echo/v4" + "github.com/stretchr/testify/assert" +) + +func TestGetPieChart(t *testing.T) { + e := echo.New() + + e.GET("/pie", func(c echo.Context) error { + img, err := NewPieChartHandler().Get(c) + if err != nil { + return err + } + return c.Blob(http.StatusOK, "image/png", img) + }) + + // Start a test HTTP server + server := httptest.NewServer(e) + defer server.Close() + + type TestCase struct { + QueryParams string + ExpectedStatus int + } + testCases := []TestCase{ + { + QueryParams: `{"names": ["Monday","Tuesday","Wednesday"], "values": [1,2,3]}`, + ExpectedStatus: http.StatusOK, + }, + { + QueryParams: `{"names": ["Monday,"Tuesday","Wednesday"], "values": [1,2,3]}`, + ExpectedStatus: http.StatusUnprocessableEntity, + }, + { + QueryParams: `{"names": [["Monday","Tuesday","Wednesday"]], "values": [1,2,3]}`, + ExpectedStatus: http.StatusUnprocessableEntity, + }, + } + + for _, tc := range testCases { + url := server.URL + "/pie?data=" + url.QueryEscape(tc.QueryParams) + resp, err := http.Get(url) + assert.NoError(t, err) + assert.Equal(t, tc.ExpectedStatus, resp.StatusCode) + } +} diff --git a/pkg/routes.go b/pkg/routes.go index bb02688..d50e827 100644 --- a/pkg/routes.go +++ b/pkg/routes.go @@ -24,6 +24,14 @@ func SetupRoutes(e *echo.Echo) { setHeaders(c) return c.Blob(http.StatusOK, "image/png", img) }) + e.GET("/bar", func(c echo.Context) error { + img, err := NewBarChartHandler().Get(c) + if err != nil { + return err + } + setHeaders(c) + return c.Blob(http.StatusOK, "image/png", img) + }) e.GET("/donut", func(c echo.Context) error { img, err := NewDonutChartHandler().Get(c) if err != nil { @@ -32,8 +40,8 @@ func SetupRoutes(e *echo.Echo) { setHeaders(c) return c.Blob(http.StatusOK, "image/png", img) }) - e.GET("/bar", func(c echo.Context) error { - img, err := NewBarChartHandler().Get(c) + e.GET("/pie", func(c echo.Context) error { + img, err := NewPieChartHandler().Get(c) if err != nil { return err }