Skip to content

Commit

Permalink
(feat) add donut charts
Browse files Browse the repository at this point in the history
  • Loading branch information
kevincobain2000 committed Dec 25, 2023
1 parent 5618781 commit 5f6bfb7
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 5 deletions.
47 changes: 42 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
- [Continuous Series](#continuous-series)
- [`GET /bar`](#get-bar)
- [Bars](#bars)
- [`GET /donut`](#get-donut)
- [Donuts](#donuts)


## [`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})
Expand Down Expand Up @@ -127,10 +129,10 @@ https://instachart.coveritup.app/line?title=Continuous+Series&x_label=No+of+peop
| `width` | | int | |


| `data` | Required | Description | Example |
| :----- | :------- | :---------- | :----------------------------- |
| `x` || []Array | `"x": [["Mon","Tue", "Wed"], ` |
| `y` || []Array | `"y": [[1,2,3]]` |
| `data` | Required | Description | Example |
| :----- | :------- | :---------- | :--------------------------- |
| `x` || []Array | `"x": [["Mon","Tue", "Wed"]` |
| `y` || []Array | `"y": [[1,2,3]]` |


### Bars
Expand All @@ -149,4 +151,39 @@ https://instachart.coveritup.app/bar?title=Bar+Chart&y_label=Sleeping+hours&data

<br>

![Bar chart](https://instachart.coveritup.app/bar?title=Bar+Chart&y_label=Sleeping+hours&data={%20%22x%22:%20[%22Mon%22,%20%22Tue%22,%20%22Wed%22,%20%22Thu%22,%20%22Fri%22,%20%22Sat%22,%20%22Sunday%22],%20%22y%22:%20[2,%203,%206%20,14,%2020,%2021,%2040]%20})
![Bar chart](https://instachart.coveritup.app/bar?title=Bar+Chart&y_label=Sleeping+hours&data={%20%22x%22:%20[%22Mon%22,%20%22Tue%22,%20%22Wed%22,%20%22Thu%22,%20%22Fri%22,%20%22Sat%22,%20%22Sunday%22],%20%22y%22:%20[2,%203,%206%20,14,%2020,%2021,%2040]%20})

## [`GET /donut`](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})


| 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

<details>
<summary><b>REQUEST URL</b></summary>

```sh
https://instachart.coveritup.app/donut?title=Donut+Chart&data={
"names": ["Monday", "Friday", "Saturday", "Sunday"],
"values": [4, 6 ,7, 9]
}
```

</details>

<br>

![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})
23 changes: 23 additions & 0 deletions pkg/donut_chart.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package pkg

import "github.com/wcharczuk/go-chart/v2"

type DonutChart struct {
chart *Chart
}

func NewDonutChart() *DonutChart {
return &DonutChart{
chart: NewChart(),
}
}
func (c *DonutChart) 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
}
62 changes: 62 additions & 0 deletions pkg/donut_chart_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package pkg

import (
"bytes"
"encoding/json"
"net/http"

"github.com/labstack/echo/v4"
"github.com/wcharczuk/go-chart/v2"
)

type DonutChartHandler struct {
chart *DonutChart
}

func NewDonutChartHandler() *DonutChartHandler {
return &DonutChartHandler{
chart: NewDonutChart(),
}
}

type DonutChartRequest 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 DonutChartData struct {
Names []string `json:"names"`
Values []float64 `json:"values"`
}

func (h *DonutChartHandler) Get(c echo.Context) ([]byte, error) {
req := new(DonutChartRequest)
if err := BindRequest(c, req); err != nil {
return nil, echo.NewHTTPError(http.StatusUnprocessableEntity, err.Error())
}

var data DonutChartData
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.DonutChart{
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
}
53 changes: 53 additions & 0 deletions pkg/donut_chart_handler_test.go
Original file line number Diff line number Diff line change
@@ -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 TestGetDonutChart(t *testing.T) {
e := echo.New()

e.GET("/donut", func(c echo.Context) error {
img, err := NewDonutChartHandler().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 + "/donut?data=" + url.QueryEscape(tc.QueryParams)
resp, err := http.Get(url)
assert.NoError(t, err)
assert.Equal(t, tc.ExpectedStatus, resp.StatusCode)
}
}
8 changes: 8 additions & 0 deletions pkg/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ func SetupRoutes(e *echo.Echo) {
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 {
return err
}
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 {
Expand Down

0 comments on commit 5f6bfb7

Please sign in to comment.