feat: support funnel chart
This commit is contained in:
parent
b93d096633
commit
5519d2eca6
9 changed files with 432 additions and 18 deletions
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
[中文](./README_zh.md)
|
||||
|
||||
`go-charts` base on [go-chart](https://github.com/wcharczuk/go-chart),it is simpler way for generating charts, which supports `svg` and `png` format and three themes: `light`, `dark` and `grafana`.
|
||||
`go-charts` base on [go-chart](https://github.com/wcharczuk/go-chart),it is simpler way for generating charts, which supports `svg` and `png` format and themes: `light`, `dark`, `grafana` and `ant`.
|
||||
|
||||
`Apache ECharts` is popular among Front-end developers, so `go-charts` supports the option of `Apache ECharts`. Developers can generate charts almost the same as `Apache ECharts`.
|
||||
|
||||
|
|
@ -17,7 +17,7 @@ Screenshot of common charts, the left part is light theme, the right part is gra
|
|||
|
||||
## Chart Type
|
||||
|
||||
Support three chart types: `line`, `bar` and `pie`.
|
||||
These chart types are supported: `line`, `bar`, `pie`, `radar` or `funnel`.
|
||||
|
||||
## Example
|
||||
|
||||
|
|
@ -159,7 +159,7 @@ The name with `[]` is new parameter, others are the same as `echarts`.
|
|||
- `radar.indicator.min` The minimum value of indicator, default value is 0.
|
||||
- `series` The series for chart
|
||||
- `series.name` Series name used for displaying in legend.
|
||||
- `series.type` Series type: `line`, `bar` or`pie`
|
||||
- `series.type` Series type: `line`, `bar`, `pie`, `radar` or `funnel`
|
||||
- `series.radius` Radius of Pie chart:`50%`, default is `40%`
|
||||
- `series.yAxisIndex` Index of y axis to combine with, which is useful for multiple y axes in one chart
|
||||
- `series.label.show` Whether to show label
|
||||
|
|
|
|||
11
README_zh.md
11
README_zh.md
|
|
@ -3,7 +3,7 @@
|
|||
[](https://github.com/vicanso/go-charts/blob/master/LICENSE)
|
||||
[](https://github.com/vicanso/go-charts/actions)
|
||||
|
||||
`go-charts`基于[go-chart](https://github.com/wcharczuk/go-chart),更简单方便的形式生成数据图表,支持`svg`与`png`两种方式的输出,支持三种主题`light`, `dark`以及`grafana`。
|
||||
`go-charts`基于[go-chart](https://github.com/wcharczuk/go-chart),更简单方便的形式生成数据图表,支持`svg`与`png`两种方式的输出,支持主题`light`, `dark`, `grafana`以及`ant`。
|
||||
|
||||
`Apache ECharts`在前端开发中得到众多开发者的认可,因此`go-charts`提供了兼容`Apache ECharts`的配置参数,简单快捷的生成相似的图表(`svg`或`png`),方便插入至Email或分享使用。下面为常用的图表截图(主题为light与grafana):
|
||||
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
## 支持图表类型
|
||||
|
||||
暂仅支持三种的图表类型:`line`, `bar` 以及 `pie`
|
||||
支持以下的图表类型:`line`, `bar`, `pie`, `radar` 以及 `funnel`
|
||||
|
||||
|
||||
## 示例
|
||||
|
|
@ -155,9 +155,14 @@ func main() {
|
|||
- `legend.padding` legend的padding,配置方式与图表的`padding`一致
|
||||
- `legend.left` legend离容器左侧的距离,其值可以为具体的像素值(20)或百分比(20%)、`left`或者`right`
|
||||
- `legend.top` legend离容器顶部的距离,暂仅支持数值形式
|
||||
- `radar` 雷达图的坐标系
|
||||
- `radar.indicator` 雷达图的指示器,用来指定雷达图中的多个变量(维度)
|
||||
- `radar.indicator.name` 指示器名称
|
||||
- `radar.indicator.max` 指示器的最大值,可选,建议设置
|
||||
- `radar.indicator.min` 指示器的最小值,可选,默认为 0
|
||||
- `series` 图表的数据项列表
|
||||
- `series.name` 图表的名称,与`legend.data`对应,两者只只设置其一
|
||||
- `series.type` 图表的展示类型,暂支持`line`, `bar`以及`pie`,需要注意`pie`只能单独使用
|
||||
- `series.type` 图表的展示类型,暂支持`line`, `bar`, `pie`, `radar` 以及 `funnel`。需要注意只有`line`与`bar`可以混用
|
||||
- `series.radius` 饼图的半径值,如`50%`,默认为`40%`
|
||||
- `series.yAxisIndex` 该数据项使用的y轴,默认为0,对yAxis的配置对应
|
||||
- `series.label.show` 是否显示文本标签(默认为对应的值)
|
||||
|
|
|
|||
36
chart.go
36
chart.go
|
|
@ -25,6 +25,7 @@ package charts
|
|||
import (
|
||||
"errors"
|
||||
"math"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/freetype/truetype"
|
||||
|
|
@ -37,6 +38,7 @@ const (
|
|||
ChartTypeBar = "bar"
|
||||
ChartTypePie = "pie"
|
||||
ChartTypeRadar = "radar"
|
||||
ChartTypeFunnel = "funnel"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -161,10 +163,19 @@ func (o *ChartOption) FillDefault(theme string) {
|
|||
} else {
|
||||
seriesCount := len(o.SeriesList)
|
||||
for index, name := range o.Legend.Data {
|
||||
if index < seriesCount {
|
||||
if index < seriesCount &&
|
||||
len(o.SeriesList[index].Name) == 0 {
|
||||
o.SeriesList[index].Name = name
|
||||
}
|
||||
}
|
||||
nameIndexDict := map[string]int{}
|
||||
for index, name := range o.Legend.Data {
|
||||
nameIndexDict[name] = index
|
||||
}
|
||||
// 保证series的顺序与legend一致
|
||||
sort.Slice(o.SeriesList, func(i, j int) bool {
|
||||
return nameIndexDict[o.SeriesList[i].Name] < nameIndexDict[o.SeriesList[j].Name]
|
||||
})
|
||||
}
|
||||
// 如果无legend数据,则隐藏
|
||||
if len(strings.Join(o.Legend.Data, "")) == 0 {
|
||||
|
|
@ -289,13 +300,17 @@ func Render(opt ChartOption) (*Draw, error) {
|
|||
barSeries := make([]Series, 0)
|
||||
isPieChart := false
|
||||
isRadarChart := false
|
||||
for index, item := range opt.SeriesList {
|
||||
item.index = index
|
||||
isFunnelChart := false
|
||||
for index := range opt.SeriesList {
|
||||
opt.SeriesList[index].index = index
|
||||
item := opt.SeriesList[index]
|
||||
switch item.Type {
|
||||
case ChartTypePie:
|
||||
isPieChart = true
|
||||
case ChartTypeRadar:
|
||||
isRadarChart = true
|
||||
case ChartTypeFunnel:
|
||||
isFunnelChart = true
|
||||
case ChartTypeBar:
|
||||
barSeries = append(barSeries, item)
|
||||
default:
|
||||
|
|
@ -305,7 +320,9 @@ func Render(opt ChartOption) (*Draw, error) {
|
|||
// 如果指定了pie,则以pie的形式处理,pie不支持多类型图表
|
||||
// pie不需要axis
|
||||
// radar 同样处理
|
||||
if isPieChart || isRadarChart {
|
||||
if isPieChart ||
|
||||
isRadarChart ||
|
||||
isFunnelChart {
|
||||
opt.XAxis.Hidden = true
|
||||
for index := range opt.YAxisList {
|
||||
opt.YAxisList[index].Hidden = true
|
||||
|
|
@ -340,6 +357,17 @@ func Render(opt ChartOption) (*Draw, error) {
|
|||
Indicators: opt.RadarIndicators,
|
||||
}, result)
|
||||
},
|
||||
// funnel render
|
||||
func() error {
|
||||
if !isFunnelChart {
|
||||
return nil
|
||||
}
|
||||
return funnelChartRender(funnelChartOption{
|
||||
SeriesList: opt.SeriesList,
|
||||
Theme: opt.Theme,
|
||||
Font: opt.Font,
|
||||
}, result)
|
||||
},
|
||||
// bar render
|
||||
func() error {
|
||||
// 如果是pie或者无bar类型的series
|
||||
|
|
|
|||
|
|
@ -330,8 +330,9 @@ func (esList EChartsSeriesList) ToSeriesList() SeriesList {
|
|||
}
|
||||
continue
|
||||
}
|
||||
// 如果是radar
|
||||
if item.Type == ChartTypeRadar {
|
||||
// 如果是radar或funnel
|
||||
if item.Type == ChartTypeRadar ||
|
||||
item.Type == ChartTypeFunnel {
|
||||
for _, dataItem := range item.Data {
|
||||
seriesList = append(seriesList, Series{
|
||||
Name: dataItem.Name,
|
||||
|
|
|
|||
|
|
@ -556,6 +556,57 @@ func indexHandler(w http.ResponseWriter, req *http.Request) {
|
|||
},
|
||||
},
|
||||
},
|
||||
// 漏斗图
|
||||
{
|
||||
Title: charts.TitleOption{
|
||||
Text: "Funnel",
|
||||
},
|
||||
Legend: charts.NewLegendOption([]string{
|
||||
"Show",
|
||||
"Click",
|
||||
"Visit",
|
||||
"Inquiry",
|
||||
"Order",
|
||||
}),
|
||||
SeriesList: []charts.Series{
|
||||
{
|
||||
Type: charts.ChartTypeFunnel,
|
||||
Name: "Visit",
|
||||
Data: charts.NewSeriesDataFromValues([]float64{
|
||||
60,
|
||||
}),
|
||||
Max: charts.NewFloatPoint(120),
|
||||
},
|
||||
{
|
||||
Type: charts.ChartTypeFunnel,
|
||||
Name: "Inquiry",
|
||||
Data: charts.NewSeriesDataFromValues([]float64{
|
||||
40,
|
||||
}),
|
||||
},
|
||||
{
|
||||
Type: charts.ChartTypeFunnel,
|
||||
Name: "Order",
|
||||
Data: charts.NewSeriesDataFromValues([]float64{
|
||||
20,
|
||||
}),
|
||||
},
|
||||
{
|
||||
Type: charts.ChartTypeFunnel,
|
||||
Name: "Click",
|
||||
Data: charts.NewSeriesDataFromValues([]float64{
|
||||
80,
|
||||
}),
|
||||
},
|
||||
{
|
||||
Type: charts.ChartTypeFunnel,
|
||||
Name: "Show",
|
||||
Data: charts.NewSeriesDataFromValues([]float64{
|
||||
100,
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
// 多图展示
|
||||
{
|
||||
Legend: charts.LegendOption{
|
||||
|
|
@ -1552,6 +1603,91 @@ func echartsHandler(w http.ResponseWriter, req *http.Request) {
|
|||
}
|
||||
]
|
||||
}`,
|
||||
`{
|
||||
"title": {
|
||||
"text": "Funnel"
|
||||
},
|
||||
"tooltip": {
|
||||
"trigger": "item",
|
||||
"formatter": "{a} <br/>{b} : {c}%"
|
||||
},
|
||||
"toolbox": {
|
||||
"feature": {
|
||||
"dataView": {
|
||||
"readOnly": false
|
||||
},
|
||||
"restore": {},
|
||||
"saveAsImage": {}
|
||||
}
|
||||
},
|
||||
"legend": {
|
||||
"data": [
|
||||
"Show",
|
||||
"Click",
|
||||
"Visit",
|
||||
"Inquiry",
|
||||
"Order"
|
||||
]
|
||||
},
|
||||
"series": [
|
||||
{
|
||||
"name": "Funnel",
|
||||
"type": "funnel",
|
||||
"left": "10%",
|
||||
"top": 60,
|
||||
"bottom": 60,
|
||||
"width": "80%",
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"minSize": "0%",
|
||||
"maxSize": "100%",
|
||||
"sort": "descending",
|
||||
"gap": 2,
|
||||
"label": {
|
||||
"show": true,
|
||||
"position": "inside"
|
||||
},
|
||||
"labelLine": {
|
||||
"length": 10,
|
||||
"lineStyle": {
|
||||
"width": 1,
|
||||
"type": "solid"
|
||||
}
|
||||
},
|
||||
"itemStyle": {
|
||||
"borderColor": "#fff",
|
||||
"borderWidth": 1
|
||||
},
|
||||
"emphasis": {
|
||||
"label": {
|
||||
"fontSize": 20
|
||||
}
|
||||
},
|
||||
"data": [
|
||||
{
|
||||
"value": 60,
|
||||
"name": "Visit"
|
||||
},
|
||||
{
|
||||
"value": 40,
|
||||
"name": "Inquiry"
|
||||
},
|
||||
{
|
||||
"value": 20,
|
||||
"name": "Order"
|
||||
},
|
||||
{
|
||||
"value": 80,
|
||||
"name": "Click"
|
||||
},
|
||||
{
|
||||
"value": 100,
|
||||
"name": "Show"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}`,
|
||||
`{
|
||||
"legend": {
|
||||
"top": "-140",
|
||||
|
|
|
|||
141
funnel.go
Normal file
141
funnel.go
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
// MIT License
|
||||
|
||||
// Copyright (c) 2022 Tree Xie
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package charts
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/golang/freetype/truetype"
|
||||
"github.com/wcharczuk/go-chart/v2"
|
||||
)
|
||||
|
||||
type funnelChartOption struct {
|
||||
Theme string
|
||||
Font *truetype.Font
|
||||
SeriesList SeriesList
|
||||
}
|
||||
|
||||
func funnelChartRender(opt funnelChartOption, result *basicRenderResult) error {
|
||||
d, err := NewDraw(DrawOption{
|
||||
Parent: result.d,
|
||||
}, PaddingOption(chart.Box{
|
||||
Top: result.titleBox.Height(),
|
||||
}))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
seriesList := make([]Series, len(opt.SeriesList))
|
||||
copy(seriesList, opt.SeriesList)
|
||||
sort.Slice(seriesList, func(i, j int) bool {
|
||||
// 大的数据在前
|
||||
return seriesList[i].Data[0].Value > seriesList[j].Data[0].Value
|
||||
})
|
||||
max := float64(100)
|
||||
min := float64(0)
|
||||
for _, item := range seriesList {
|
||||
if item.Max != nil {
|
||||
max = *item.Max
|
||||
}
|
||||
if item.Min != nil {
|
||||
min = *item.Min
|
||||
}
|
||||
}
|
||||
|
||||
theme := NewTheme(opt.Theme)
|
||||
gap := 2
|
||||
height := d.Box.Height()
|
||||
width := d.Box.Width()
|
||||
count := len(seriesList)
|
||||
|
||||
h := (height - gap*(count-1)) / count
|
||||
|
||||
y := 0
|
||||
widthList := make([]int, len(seriesList))
|
||||
textList := make([]string, len(seriesList))
|
||||
for index, item := range seriesList {
|
||||
value := item.Data[0].Value
|
||||
percent := (item.Data[0].Value - min) / (max - min)
|
||||
w := int(percent * float64(width))
|
||||
widthList[index] = w
|
||||
p := humanize.CommafWithDigits(value, 2) + "%"
|
||||
textList[index] = fmt.Sprintf("%s(%s)", item.Name, p)
|
||||
}
|
||||
|
||||
for index, w := range widthList {
|
||||
series := seriesList[index]
|
||||
nextWidth := 0
|
||||
if index+1 < len(widthList) {
|
||||
nextWidth = widthList[index+1]
|
||||
}
|
||||
topStartX := (width - w) >> 1
|
||||
topEndX := topStartX + w
|
||||
bottomStartX := (width - nextWidth) >> 1
|
||||
bottomEndX := bottomStartX + nextWidth
|
||||
points := []Point{
|
||||
{
|
||||
X: topStartX,
|
||||
Y: y,
|
||||
},
|
||||
{
|
||||
X: topEndX,
|
||||
Y: y,
|
||||
},
|
||||
{
|
||||
X: bottomEndX,
|
||||
Y: y + h,
|
||||
},
|
||||
{
|
||||
X: bottomStartX,
|
||||
Y: y + h,
|
||||
},
|
||||
{
|
||||
X: topStartX,
|
||||
Y: y,
|
||||
},
|
||||
}
|
||||
color := theme.GetSeriesColor(series.index)
|
||||
d.fill(points, chart.Style{
|
||||
FillColor: color,
|
||||
})
|
||||
|
||||
// 文本
|
||||
text := textList[index]
|
||||
r := d.Render
|
||||
textStyle := chart.Style{
|
||||
FontColor: theme.GetTextColor(),
|
||||
FontSize: labelFontSize,
|
||||
Font: opt.Font,
|
||||
}
|
||||
textStyle.GetTextOptions().WriteToRenderer(r)
|
||||
textBox := r.MeasureText(text)
|
||||
textX := width>>1 - textBox.Width()>>1
|
||||
textY := y + h>>1
|
||||
d.text(text, textX, textY)
|
||||
|
||||
y += (h + gap)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
91
funnel_test.go
Normal file
91
funnel_test.go
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
// MIT License
|
||||
|
||||
// Copyright (c) 2022 Tree Xie
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package charts
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/wcharczuk/go-chart/v2"
|
||||
)
|
||||
|
||||
func TestFunnelChartRender(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
d, err := NewDraw(DrawOption{
|
||||
Width: 250,
|
||||
Height: 150,
|
||||
})
|
||||
assert.Nil(err)
|
||||
f, _ := chart.GetDefaultFont()
|
||||
err = funnelChartRender(funnelChartOption{
|
||||
Font: f,
|
||||
SeriesList: []Series{
|
||||
{
|
||||
Type: ChartTypeFunnel,
|
||||
Name: "Visit",
|
||||
Data: NewSeriesDataFromValues([]float64{
|
||||
60,
|
||||
}),
|
||||
},
|
||||
{
|
||||
Type: ChartTypeFunnel,
|
||||
Name: "Inquiry",
|
||||
Data: NewSeriesDataFromValues([]float64{
|
||||
40,
|
||||
}),
|
||||
index: 1,
|
||||
},
|
||||
{
|
||||
Type: ChartTypeFunnel,
|
||||
Name: "Order",
|
||||
Data: NewSeriesDataFromValues([]float64{
|
||||
20,
|
||||
}),
|
||||
index: 2,
|
||||
},
|
||||
{
|
||||
Type: ChartTypeFunnel,
|
||||
Name: "Click",
|
||||
Data: NewSeriesDataFromValues([]float64{
|
||||
80,
|
||||
}),
|
||||
index: 3,
|
||||
},
|
||||
{
|
||||
Type: ChartTypeFunnel,
|
||||
Name: "Show",
|
||||
Data: NewSeriesDataFromValues([]float64{
|
||||
100,
|
||||
}),
|
||||
index: 4,
|
||||
},
|
||||
},
|
||||
}, &basicRenderResult{
|
||||
d: d,
|
||||
})
|
||||
assert.Nil(err)
|
||||
data, err := d.Bytes()
|
||||
assert.Nil(err)
|
||||
assert.Equal("<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"250\" height=\"150\">\\n<path d=\"M 0 0\nL 250 0\nL 225 28\nL 25 28\nL 0 0\" style=\"stroke-width:0;stroke:none;fill:rgba(115,192,222,1.0)\"/><text x=\"89\" y=\"14\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:12.8px;font-family:'Roboto Medium',sans-serif\">Show(100%)</text><path d=\"M 25 30\nL 225 30\nL 200 58\nL 50 58\nL 25 30\" style=\"stroke-width:0;stroke:none;fill:rgba(238,102,102,1.0)\"/><text x=\"94\" y=\"44\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:12.8px;font-family:'Roboto Medium',sans-serif\">Click(80%)</text><path d=\"M 50 60\nL 200 60\nL 175 88\nL 75 88\nL 50 60\" style=\"stroke-width:0;stroke:none;fill:rgba(84,112,198,1.0)\"/><text x=\"96\" y=\"74\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:12.8px;font-family:'Roboto Medium',sans-serif\">Visit(60%)</text><path d=\"M 75 90\nL 175 90\nL 150 118\nL 100 118\nL 75 90\" style=\"stroke-width:0;stroke:none;fill:rgba(145,204,117,1.0)\"/><text x=\"89\" y=\"104\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:12.8px;font-family:'Roboto Medium',sans-serif\">Inquiry(40%)</text><path d=\"M 100 120\nL 150 120\nL 125 148\nL 125 148\nL 100 120\" style=\"stroke-width:0;stroke:none;fill:rgba(250,200,88,1.0)\"/><text x=\"93\" y=\"134\" style=\"stroke-width:0;stroke:none;fill:rgba(70,70,70,1.0);font-size:12.8px;font-family:'Roboto Medium',sans-serif\">Order(20%)</text></svg>", string(data))
|
||||
}
|
||||
|
|
@ -51,9 +51,17 @@ func radarChartRender(opt radarChartOption, result *basicRenderResult) error {
|
|||
if sides < 3 {
|
||||
return errors.New("The count of indicator should be >= 3")
|
||||
}
|
||||
for _, indicator := range opt.Indicators {
|
||||
maxValues := make([]float64, len(opt.Indicators))
|
||||
for _, series := range opt.SeriesList {
|
||||
for index, item := range series.Data {
|
||||
if index < len(maxValues) && item.Value > maxValues[index] {
|
||||
maxValues[index] = item.Value
|
||||
}
|
||||
}
|
||||
}
|
||||
for index, indicator := range opt.Indicators {
|
||||
if indicator.Max <= 0 {
|
||||
return errors.New("The max of indicator should be > 0")
|
||||
opt.Indicators[index].Max = maxValues[index]
|
||||
}
|
||||
}
|
||||
d, err := NewDraw(DrawOption{
|
||||
|
|
|
|||
|
|
@ -115,6 +115,10 @@ type Series struct {
|
|||
MarkPoint SeriesMarkPoint
|
||||
// Make line for series
|
||||
MarkLine SeriesMarkLine
|
||||
// Max value of series
|
||||
Min *float64
|
||||
// Min value of series
|
||||
Max *float64
|
||||
}
|
||||
type SeriesList []Series
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue