diff --git a/bar_chart.go b/bar_chart.go
index 7582294..6db1714 100644
--- a/bar_chart.go
+++ b/bar_chart.go
@@ -28,7 +28,7 @@ func NewBarChartOptionWithData(data [][]float64) BarChartOption {
Padding: defaultPadding,
Theme: GetDefaultTheme(),
Font: GetDefaultFont(),
- YAxis: make([]YAxisOption, sl.getYAxisCount()),
+ YAxis: make([]YAxisOption, getSeriesYAxisCount(sl)),
ValueFormatter: defaultValueFormatter,
}
}
@@ -40,8 +40,8 @@ type BarChartOption struct {
Padding Box
// Font is the font used to render the chart.
Font *truetype.Font
- // SeriesList provides the data series.
- SeriesList SeriesList
+ // SeriesList provides the data population for the chart, typically constructed using NewSeriesListBar.
+ SeriesList BarSeriesList
// StackSeries if set to *true a single bar with the colored series stacked together will be rendered.
// This feature will result in some options being ignored, including BarMargin and SeriesLabelPosition.
// MarkLine is also interpreted differently, only the first Series will have the MarkLine rendered (as it's the
@@ -101,7 +101,7 @@ func calculateBarMarginsAndSize(seriesCount, space int, configuredBarSize int, c
return margin, barMargin, barSize
}
-func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (Box, error) {
+func (b *barChart) render(result *defaultRenderResult, seriesList BarSeriesList) (Box, error) {
p := b.p
opt := b.opt
seriesCount := len(seriesList)
@@ -115,13 +115,13 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B
x0, x1 := xRange.GetRange(0)
width := int(x1 - x0)
barMaxHeight := seriesPainter.Height() // total vertical space for bars
- seriesNames := seriesList.Names()
+ seriesNames := seriesList.names()
divideValues := xRange.AutoDivide()
stackedSeries := flagIs(true, opt.StackSeries)
var margin, barMargin, barWidth int
var accumulatedHeights []int // prior heights for stacking to avoid recalculating the heights
if stackedSeries {
- barCount := seriesList.getYAxisCount() // only two bars if two y-axis
+ barCount := getSeriesYAxisCount(seriesList) // only two bars if two y-axis
configuredMargin := opt.BarMargin
if barCount == 1 {
configuredMargin = nil // no margin needed with a single bar
@@ -148,8 +148,8 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B
rendererList = append(rendererList, labelPainter)
}
- points := make([]Point, len(series.Data)) // used for mark points
- for j, item := range series.Data {
+ points := make([]Point, len(series.Values)) // used for mark points
+ for j, item := range series.Values {
if j >= xRange.divideCount {
continue
}
@@ -230,7 +230,7 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B
var globalSeriesData []float64 // lazily initialized
if series.MarkLine.GlobalLine && stackSeries && index == seriesCount-1 {
if globalSeriesData == nil {
- globalSeriesData = seriesList.makeSumSeries(ChartTypeBar, series.YAxisIndex).Data
+ globalSeriesData = sumSeriesData(seriesList, series.YAxisIndex)
}
markLinePainter.add(markLineRenderOption{
fillColor: defaultGlobalMarkFillColor,
@@ -238,7 +238,7 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B
strokeColor: defaultGlobalMarkFillColor,
font: opt.Font,
markline: series.MarkLine,
- seriesData: globalSeriesData,
+ seriesValues: globalSeriesData,
axisRange: yRange,
valueFormatterDefault: markLineValueFormatter,
})
@@ -250,20 +250,20 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B
strokeColor: seriesColor,
font: opt.Font,
markline: series.MarkLine,
- seriesData: series.Data,
+ seriesValues: series.Values,
axisRange: yRange,
valueFormatterDefault: markLineValueFormatter,
})
}
if series.MarkPoint.GlobalPoint && stackSeries && index == seriesCount-1 {
if globalSeriesData == nil {
- globalSeriesData = seriesList.makeSumSeries(ChartTypeBar, series.YAxisIndex).Data
+ globalSeriesData = sumSeriesData(seriesList, series.YAxisIndex)
}
markPointPainter.add(markPointRenderOption{
fillColor: defaultGlobalMarkFillColor,
font: opt.Font,
markpoint: series.MarkPoint,
- seriesData: globalSeriesData,
+ seriesValues: globalSeriesData,
points: points,
valueFormatterDefault: markPointValueFormatter,
seriesLabelPainter: labelPainter,
@@ -273,7 +273,7 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B
fillColor: seriesColor,
font: opt.Font,
markpoint: series.MarkPoint,
- seriesData: series.Data,
+ seriesValues: series.Values,
points: points,
valueFormatterDefault: markPointValueFormatter,
seriesLabelPainter: labelPainter,
@@ -307,6 +307,5 @@ func (b *barChart) Render() (Box, error) {
if err != nil {
return BoxZero, err
}
- seriesList := opt.SeriesList.Filter(ChartTypeBar)
- return b.render(renderResult, seriesList)
+ return b.render(renderResult, opt.SeriesList)
}
diff --git a/bar_chart_test.go b/bar_chart_test.go
index d0b27f9..cc3842b 100644
--- a/bar_chart_test.go
+++ b/bar_chart_test.go
@@ -84,7 +84,7 @@ func TestNewBarChartOptionWithData(t *testing.T) {
})
assert.Len(t, opt.SeriesList, 2)
- assert.Equal(t, ChartTypeBar, opt.SeriesList[0].Type)
+ assert.Equal(t, ChartTypeBar, opt.SeriesList[0].getType())
assert.Len(t, opt.YAxis, 1)
assert.Equal(t, defaultPadding, opt.Padding)
diff --git a/benchmark_test.go b/benchmark_test.go
index 1dbac3f..7763ddd 100644
--- a/benchmark_test.go
+++ b/benchmark_test.go
@@ -20,7 +20,6 @@ func makeDefaultMultiChartOptions() ChartOption {
},
YAxis: []YAxisOption{
{
-
Min: Ptr(0.0),
Max: Ptr(90.0),
},
@@ -29,11 +28,11 @@ func makeDefaultMultiChartOptions() ChartOption {
NewSeriesListLine([][]float64{
{56.5, 82.1, 88.7, 70.1, 53.4, 85.1},
{51.1, 51.4, 55.1, 53.3, 73.8, 68.7},
- }),
+ }).ToGenericSeriesList(),
NewSeriesListBar([][]float64{
{40.1, 62.2, 69.5, 36.4, 45.2, 32.5},
{25.2, 37.1, 41.2, 18, 33.9, 49.1},
- })...),
+ }).ToGenericSeriesList()...),
Children: []ChartOption{
{
Legend: LegendOption{
@@ -50,7 +49,7 @@ func makeDefaultMultiChartOptions() ChartOption {
435.9, 354.3, 285.9, 204.5,
}, PieSeriesOption{
Radius: "35%",
- }),
+ }).ToGenericSeriesList(),
},
},
}
diff --git a/chart_option.go b/chart_option.go
index ea69194..92a2b1a 100644
--- a/chart_option.go
+++ b/chart_option.go
@@ -30,8 +30,8 @@ type ChartOption struct {
Font *truetype.Font
// Box specifies the canvas box for the chart.
Box Box
- // SeriesList provides the data series.
- SeriesList SeriesList
+ // SeriesList provides the population data for the charts, constructed through NewSeriesListGeneric.
+ SeriesList GenericSeriesList
// StackSeries if set to *true the lines will be layered or stacked. This option significantly changes the chart
// visualization, please see the specific chart docs for full details.
StackSeries *bool
@@ -228,7 +228,7 @@ func (o *ChartOption) fillDefault() error {
o.Width = getDefaultInt(o.Width, defaultChartWidth)
o.Height = getDefaultInt(o.Height, defaultChartHeight)
- yaxisCount := o.SeriesList.getYAxisCount()
+ yaxisCount := getSeriesYAxisCount(o.SeriesList)
if yaxisCount < 0 {
return errors.New("series specified invalid y-axis index")
}
@@ -267,41 +267,41 @@ func fillThemeDefaults(defaultTheme ColorPalette, title *TitleOption, legend *Le
// LineRender line chart render.
func LineRender(values [][]float64, opts ...OptionFunc) (*Painter, error) {
return Render(ChartOption{
- SeriesList: NewSeriesListLine(values),
+ SeriesList: NewSeriesListGeneric(values, ChartTypeLine),
}, opts...)
}
// BarRender bar chart render.
func BarRender(values [][]float64, opts ...OptionFunc) (*Painter, error) {
return Render(ChartOption{
- SeriesList: NewSeriesListBar(values),
+ SeriesList: NewSeriesListGeneric(values, ChartTypeBar),
}, opts...)
}
// HorizontalBarRender horizontal bar chart render.
func HorizontalBarRender(values [][]float64, opts ...OptionFunc) (*Painter, error) {
return Render(ChartOption{
- SeriesList: NewSeriesListHorizontalBar(values),
+ SeriesList: NewSeriesListGeneric(values, ChartTypeHorizontalBar),
}, opts...)
}
// PieRender pie chart render.
func PieRender(values []float64, opts ...OptionFunc) (*Painter, error) {
return Render(ChartOption{
- SeriesList: NewSeriesListPie(values),
+ SeriesList: NewSeriesListPie(values).ToGenericSeriesList(),
}, opts...)
}
// RadarRender radar chart render.
func RadarRender(values [][]float64, opts ...OptionFunc) (*Painter, error) {
return Render(ChartOption{
- SeriesList: NewSeriesListRadar(values),
+ SeriesList: NewSeriesListGeneric(values, ChartTypeRadar),
}, opts...)
}
// FunnelRender funnel chart render.
func FunnelRender(values []float64, opts ...OptionFunc) (*Painter, error) {
return Render(ChartOption{
- SeriesList: NewSeriesListFunnel(values),
+ SeriesList: NewSeriesListFunnel(values).ToGenericSeriesList(),
}, opts...)
}
diff --git a/chart_option_test.go b/chart_option_test.go
index 709e7f0..8118f17 100644
--- a/chart_option_test.go
+++ b/chart_option_test.go
@@ -54,7 +54,7 @@ func TestChartOptionSeriesShowLabel(t *testing.T) {
t.Parallel()
opt := ChartOption{
- SeriesList: NewSeriesListPie([]float64{1, 2}),
+ SeriesList: NewSeriesListPie([]float64{1, 2}).ToGenericSeriesList(),
}
SeriesShowLabel(true)(&opt)
assert.True(t, flagIs(true, opt.SeriesList[0].Label.Show))
@@ -63,9 +63,8 @@ func TestChartOptionSeriesShowLabel(t *testing.T) {
assert.True(t, flagIs(false, opt.SeriesList[0].Label.Show))
}
-func newNoTypeSeriesListFromValues(values [][]float64) SeriesList {
- return newSeriesListFromValues(values, "",
- SeriesLabel{}, nil, "", SeriesMarkPoint{}, SeriesMarkLine{})
+func newNoTypeSeriesListFromValues(values [][]float64) GenericSeriesList {
+ return NewSeriesListGeneric(values, "")
}
func TestChartOptionMarkLine(t *testing.T) {
@@ -273,7 +272,7 @@ func TestChildRender(t *testing.T) {
SeriesList: NewSeriesListHorizontalBar([][]float64{
{70, 90, 110, 130},
{80, 100, 120, 140},
- }),
+ }).ToGenericSeriesList(),
Legend: LegendOption{
SeriesNames: []string{"2011", "2012"},
},
diff --git a/charts.go b/charts.go
index 418ad9e..20ee154 100644
--- a/charts.go
+++ b/charts.go
@@ -3,7 +3,6 @@ package charts
import (
"errors"
"math"
- "sort"
"github.com/go-analyze/charts/chartdraw"
)
@@ -70,7 +69,7 @@ type defaultRenderOption struct {
// padding specifies the padding of chart.
padding Box
// seriesList provides the data series.
- seriesList SeriesList
+ seriesList seriesList
// stackSeries can be set to true if the series data will be stacked (summed).
stackSeries bool
// xAxis are options for the x-axis.
@@ -99,15 +98,12 @@ func defaultRender(p *Painter, opt defaultRenderOption) (*defaultRenderResult, e
fillThemeDefaults(getPreferredTheme(opt.theme, p.theme), &opt.title, opt.legend, opt.xAxis)
// TODO - this is a hack, we need to update the yaxis based on the markpoint state
- for _, sl := range opt.seriesList {
- if len(sl.MarkPoint.Data) > 0 { // if graph has markpoint
- // adjust padding scale to give space for mark point (if not specified by user)
- for i := range opt.yAxis {
- if opt.yAxis[i].RangeValuePaddingScale == nil {
- opt.yAxis[i].RangeValuePaddingScale = Ptr(2.5)
- }
+ if opt.seriesList.hasMarkPoint() {
+ // adjust padding scale to give space for mark point (if not specified by user)
+ for i := range opt.yAxis {
+ if opt.yAxis[i].RangeValuePaddingScale == nil {
+ opt.yAxis[i].RangeValuePaddingScale = Ptr(2.5)
}
- break
}
}
@@ -120,12 +116,14 @@ func defaultRender(p *Painter, opt defaultRenderOption) (*defaultRenderResult, e
// association between legend and series name
if len(opt.legend.SeriesNames) == 0 {
- opt.legend.SeriesNames = opt.seriesList.Names()
+ opt.legend.SeriesNames = opt.seriesList.names()
} else {
- seriesCount := len(opt.seriesList)
+ seriesCount := opt.seriesList.len()
for index, name := range opt.legend.SeriesNames {
- if index < seriesCount && len(opt.seriesList[index].Name) == 0 {
- opt.seriesList[index].Name = name
+ if index >= seriesCount {
+ break
+ } else if opt.seriesList.getSeriesName(index) == "" {
+ opt.seriesList.setSeriesName(index, name)
}
}
nameIndexDict := map[string]int{}
@@ -133,9 +131,7 @@ func defaultRender(p *Painter, opt defaultRenderOption) (*defaultRenderResult, e
nameIndexDict[name] = index
}
// ensure order of series is consistent with legend
- sort.Slice(opt.seriesList, func(i, j int) bool {
- return nameIndexDict[opt.seriesList[i].Name] < nameIndexDict[opt.seriesList[j].Name]
- })
+ opt.seriesList.sortByNameIndex(nameIndexDict)
}
const legendTitlePadding = 15
@@ -188,7 +184,7 @@ func defaultRender(p *Painter, opt defaultRenderOption) (*defaultRenderResult, e
axisRanges: make(map[int]axisRange),
}
- axisIndexList := make([]int, opt.seriesList.getYAxisCount())
+ axisIndexList := make([]int, getSeriesYAxisCount(opt.seriesList))
for i := range axisIndexList {
axisIndexList[i] = i
}
@@ -208,7 +204,7 @@ func defaultRender(p *Painter, opt defaultRenderOption) (*defaultRenderResult, e
minPadRange = *yAxisOption.RangeValuePaddingScale
maxPadRange = *yAxisOption.RangeValuePaddingScale
}
- min, max, sumMax := opt.seriesList.getMinMaxSumMax(index, opt.stackSeries)
+ min, max, sumMax := getSeriesMinMaxSumMax(opt.seriesList, index, opt.stackSeries)
decimalData := min != math.Floor(min) || (max-min) != math.Floor(max-min)
if yAxisOption.Min != nil && *yAxisOption.Min < min {
min = *yAxisOption.Min
@@ -270,7 +266,7 @@ func defaultRender(p *Painter, opt defaultRenderOption) (*defaultRenderResult, e
} else {
yAxisOption.isCategoryAxis = true
// we need to update the range labels or the bars won't be aligned to the Y axis
- r.divideCount = opt.seriesList.getMaxDataCount("")
+ r.divideCount = getSeriesMaxDataCount(opt.seriesList)
result.axisRanges[index] = r
// since the x-axis is the value part, it's label is calculated and processed separately
opt.xAxis.Labels = r.Values()
@@ -350,12 +346,12 @@ func Render(opt ChartOption, opts ...OptionFunc) (*Painter, error) {
}
seriesList := opt.SeriesList
- lineSeriesList := seriesList.Filter(ChartTypeLine)
- barSeriesList := seriesList.Filter(ChartTypeBar)
- horizontalBarSeriesList := seriesList.Filter(ChartTypeHorizontalBar)
- pieSeriesList := seriesList.Filter(ChartTypePie)
- radarSeriesList := seriesList.Filter(ChartTypeRadar)
- funnelSeriesList := seriesList.Filter(ChartTypeFunnel)
+ lineSeriesList := filterSeriesList[LineSeriesList](opt.SeriesList, ChartTypeLine)
+ barSeriesList := filterSeriesList[BarSeriesList](opt.SeriesList, ChartTypeBar)
+ horizontalBarSeriesList := filterSeriesList[HorizontalBarSeriesList](opt.SeriesList, ChartTypeHorizontalBar)
+ pieSeriesList := filterSeriesList[PieSeriesList](opt.SeriesList, ChartTypePie)
+ radarSeriesList := filterSeriesList[RadarSeriesList](opt.SeriesList, ChartTypeRadar)
+ funnelSeriesList := filterSeriesList[FunnelSeriesList](opt.SeriesList, ChartTypeFunnel)
seriesCount := len(seriesList)
if len(horizontalBarSeriesList) != 0 && len(horizontalBarSeriesList) != seriesCount {
diff --git a/echarts.go b/echarts.go
index 9f47876..42ab296 100644
--- a/echarts.go
+++ b/echarts.go
@@ -253,20 +253,20 @@ type EChartsSeries struct {
}
type EChartsSeriesList []EChartsSeries
-func (esList EChartsSeriesList) ToSeriesList() SeriesList {
- seriesList := make(SeriesList, 0, len(esList))
+func (esList EChartsSeriesList) ToSeriesList() GenericSeriesList {
+ seriesList := make([]GenericSeries, 0, len(esList))
for _, item := range esList {
// if pie, each sub-recommendation generates a series
if item.Type == ChartTypePie {
for _, dataItem := range item.Data {
- seriesList = append(seriesList, Series{
+ seriesList = append(seriesList, GenericSeries{
Type: item.Type,
Name: dataItem.Name,
Label: SeriesLabel{
Show: Ptr(true),
},
Radius: item.Radius,
- Data: []float64{dataItem.Value.First()},
+ Values: []float64{dataItem.Value.First()},
})
}
continue
@@ -274,10 +274,10 @@ func (esList EChartsSeriesList) ToSeriesList() SeriesList {
if item.Type == ChartTypeRadar ||
item.Type == ChartTypeFunnel {
for _, dataItem := range item.Data {
- seriesList = append(seriesList, Series{
- Name: dataItem.Name,
- Type: item.Type,
- Data: dataItem.Value.values,
+ seriesList = append(seriesList, GenericSeries{
+ Name: dataItem.Name,
+ Type: item.Type,
+ Values: dataItem.Value.values,
Label: SeriesLabel{
FontStyle: FontStyle{
FontColor: ParseColor(item.Label.Color),
@@ -289,9 +289,9 @@ func (esList EChartsSeriesList) ToSeriesList() SeriesList {
}
continue
}
- seriesList = append(seriesList, Series{
+ seriesList = append(seriesList, GenericSeries{
Type: item.Type,
- Data: sliceConversion(item.Data, func(dataItem EChartsSeriesData) float64 {
+ Values: sliceConversion(item.Data, func(dataItem EChartsSeriesData) float64 {
return dataItem.Value.First()
}),
YAxisIndex: item.YAxisIndex,
diff --git a/examples/multiple_charts-3/main.go b/examples/multiple_charts-3/main.go
index 56aac98..a3a6c09 100644
--- a/examples/multiple_charts-3/main.go
+++ b/examples/multiple_charts-3/main.go
@@ -60,7 +60,7 @@ func main() {
SeriesList: charts.NewSeriesListHorizontalBar([][]float64{
{70, 90, 110, 130},
{80, 100, 120, 140},
- }),
+ }).ToGenericSeriesList(),
Legend: charts.LegendOption{
SeriesNames: []string{
"2011", "2012",
diff --git a/examples/web-1/main.go b/examples/web-1/main.go
index 1d6359d..ae014b3 100644
--- a/examples/web-1/main.go
+++ b/examples/web-1/main.go
@@ -157,13 +157,13 @@ func indexHandler(w http.ResponseWriter, req *http.Request) {
"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun",
},
},
- SeriesList: charts.NewSeriesListLine([][]float64{
+ SeriesList: charts.NewSeriesListGeneric([][]float64{
{120, 132, 101, 134, 90, 230, 210},
{220, 182, 191, 234, 290, 330, 310},
{150, 232, 201, 154, 190, 330, 410},
{320, 332, 301, 334, 390, 330, 320},
{820, 932, 901, 934, 1290, 1330, 1320},
- }),
+ }, charts.ChartTypeLine),
},
// temperature line chart
{
@@ -187,9 +187,9 @@ func indexHandler(w http.ResponseWriter, req *http.Request) {
},
BoundaryGap: charts.Ptr(false),
},
- SeriesList: []charts.Series{
+ SeriesList: []charts.GenericSeries{
{
- Data: []float64{
+ Values: []float64{
14, 11, 13, 11, 12, 12, 7,
},
Type: charts.ChartTypeLine,
@@ -197,7 +197,7 @@ func indexHandler(w http.ResponseWriter, req *http.Request) {
MarkLine: charts.NewMarkLine(charts.SeriesMarkDataTypeAverage),
},
{
- Data: []float64{
+ Values: []float64{
1, -2, 2, 5, 3, 2, 0,
},
Type: charts.ChartTypeLine,
@@ -221,9 +221,9 @@ func indexHandler(w http.ResponseWriter, req *http.Request) {
YAxis: []charts.YAxisOption{{
Min: charts.Ptr(0.0), // ensure y-axis starts at 0
}},
- SeriesList: charts.NewSeriesListLine([][]float64{
+ SeriesList: charts.NewSeriesListGeneric([][]float64{
{120, 132, 101, 134, 90, 230, 210},
- }),
+ }, charts.ChartTypeLine),
FillArea: charts.Ptr(true),
},
// histogram
@@ -243,15 +243,15 @@ func indexHandler(w http.ResponseWriter, req *http.Request) {
},
Icon: charts.IconRect,
},
- SeriesList: []charts.Series{
+ SeriesList: []charts.GenericSeries{
{
- Data: []float64{
+ Values: []float64{
120, 200, 150, 80, 70, 110, 130,
},
Type: charts.ChartTypeBar,
},
{
- Data: []float64{
+ Values: []float64{
100, 190, 230, 140, 100, 200, 180,
},
Type: charts.ChartTypeBar,
@@ -285,10 +285,10 @@ func indexHandler(w http.ResponseWriter, req *http.Request) {
},
},
},
- SeriesList: charts.NewSeriesListHorizontalBar([][]float64{
+ SeriesList: charts.NewSeriesListGeneric([][]float64{
{18203, 23489, 29034, 104970, 131744, 630230},
{19325, 23438, 31000, 121594, 134141, 681807},
- }),
+ }, charts.ChartTypeHorizontalBar),
},
// histogram+marker
{
@@ -309,10 +309,10 @@ func indexHandler(w http.ResponseWriter, req *http.Request) {
"Rainfall", "Evaporation",
},
},
- SeriesList: []charts.Series{
+ SeriesList: []charts.GenericSeries{
{
Type: charts.ChartTypeBar,
- Data: []float64{
+ Values: []float64{
2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 135.6, 162.2, 32.6, 20.0, 6.4, 3.3,
},
MarkPoint: charts.NewMarkPoint(
@@ -325,7 +325,7 @@ func indexHandler(w http.ResponseWriter, req *http.Request) {
},
{
Type: charts.ChartTypeBar,
- Data: []float64{
+ Values: []float64{
2.6, 5.9, 9.0, 26.4, 28.7, 70.7, 175.6, 182.2, 48.7, 18.8, 6.0, 2.3,
},
MarkPoint: charts.NewMarkPoint(
@@ -365,12 +365,12 @@ func indexHandler(w http.ResponseWriter, req *http.Request) {
AxisColor: charts.ColorRGB(250, 200, 88),
},
},
- SeriesList: append(charts.NewSeriesListBar([][]float64{
+ SeriesList: append(charts.NewSeriesListGeneric([][]float64{
{2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 135.6, 162.2, 32.6, 20.0, 6.4, 3.3},
{2.6, 5.9, 9.0, 26.4, 28.7, 70.7, 175.6, 182.2, 48.7, 18.8, 6.0, 2.3},
- }), charts.Series{
+ }, charts.ChartTypeBar), charts.GenericSeries{
Type: charts.ChartTypeLine,
- Data: []float64{
+ Values: []float64{
2.0, 2.2, 3.3, 4.5, 6.3, 10.2, 20.3, 23.4, 23.0, 16.5, 12.0, 6.2,
},
YAxisIndex: 1,
@@ -396,7 +396,7 @@ func indexHandler(w http.ResponseWriter, req *http.Request) {
1048, 735, 580, 484, 300,
}, charts.PieSeriesOption{
Radius: "35%",
- }),
+ }).ToGenericSeriesList(),
},
// radar chart
{
@@ -436,10 +436,10 @@ func indexHandler(w http.ResponseWriter, req *http.Request) {
Max: 25000,
},
},
- SeriesList: charts.NewSeriesListRadar([][]float64{
+ SeriesList: charts.NewSeriesListGeneric([][]float64{
{4200, 3000, 20000, 35000, 50000, 18000},
{5000, 14000, 28000, 26000, 42000, 21000},
- }),
+ }, charts.ChartTypeRadar),
},
// funnel chart
{
@@ -451,31 +451,31 @@ func indexHandler(w http.ResponseWriter, req *http.Request) {
"Show", "Click", "Visit", "Inquiry", "Order",
},
},
- SeriesList: []charts.Series{
+ SeriesList: []charts.GenericSeries{
{
- Type: charts.ChartTypeFunnel,
- Name: "Show",
- Data: []float64{100},
+ Type: charts.ChartTypeFunnel,
+ Name: "Show",
+ Values: []float64{100},
},
{
- Type: charts.ChartTypeFunnel,
- Name: "Click",
- Data: []float64{80},
+ Type: charts.ChartTypeFunnel,
+ Name: "Click",
+ Values: []float64{80},
},
{
- Type: charts.ChartTypeFunnel,
- Name: "Visit",
- Data: []float64{60},
+ Type: charts.ChartTypeFunnel,
+ Name: "Visit",
+ Values: []float64{60},
},
{
- Type: charts.ChartTypeFunnel,
- Name: "Inquiry",
- Data: []float64{40},
+ Type: charts.ChartTypeFunnel,
+ Name: "Inquiry",
+ Values: []float64{40},
},
{
- Type: charts.ChartTypeFunnel,
- Name: "Order",
- Data: []float64{20},
+ Type: charts.ChartTypeFunnel,
+ Name: "Order",
+ Values: []float64{20},
},
},
},
@@ -507,14 +507,14 @@ func indexHandler(w http.ResponseWriter, req *http.Request) {
},
},
SeriesList: append(
- charts.NewSeriesListLine([][]float64{
+ charts.NewSeriesListGeneric([][]float64{
{56.5, 82.1, 88.7, 70.1, 53.4, 85.1},
{51.1, 51.4, 55.1, 53.3, 73.8, 68.7},
- }),
- charts.NewSeriesListBar([][]float64{
+ }, charts.ChartTypeLine),
+ charts.NewSeriesListGeneric([][]float64{
{40.1, 62.2, 69.5, 36.4, 45.2, 32.5},
{25.2, 37.1, 41.2, 18, 33.9, 49.1},
- })...),
+ }, charts.ChartTypeBar)...),
Children: []charts.ChartOption{
{
Legend: charts.LegendOption{
@@ -533,7 +533,7 @@ func indexHandler(w http.ResponseWriter, req *http.Request) {
435.9, 354.3, 285.9, 204.5,
}, charts.PieSeriesOption{
Radius: "35%",
- }),
+ }).ToGenericSeriesList(),
},
},
},
diff --git a/funnel_chart.go b/funnel_chart.go
index 4e42e42..ee61313 100644
--- a/funnel_chart.go
+++ b/funnel_chart.go
@@ -36,8 +36,8 @@ type FunnelChartOption struct {
Padding Box
// Font is the font used to render the chart.
Font *truetype.Font
- // SeriesList provides the data series.
- SeriesList SeriesList
+ // SeriesList provides the data population for the chart, typically constructed using NewSeriesListFunnel.
+ SeriesList FunnelSeriesList
// Title are options for rendering the title.
Title TitleOption
// Legend are options for the data legend.
@@ -46,14 +46,14 @@ type FunnelChartOption struct {
ValueFormatter ValueFormatter
}
-func (f *funnelChart) render(result *defaultRenderResult, seriesList SeriesList) (Box, error) {
+func (f *funnelChart) render(result *defaultRenderResult, seriesList FunnelSeriesList) (Box, error) {
opt := f.opt
count := len(seriesList)
if count == 0 {
return BoxZero, errors.New("empty series list")
}
seriesPainter := result.seriesPainter
- max := seriesList[0].Data[0]
+ max := seriesList[0].Value
var min float64
theme := opt.Theme
gap := 2
@@ -65,27 +65,26 @@ func (f *funnelChart) render(result *defaultRenderResult, seriesList SeriesList)
var y int
widthList := make([]int, len(seriesList))
textList := make([]string, len(seriesList))
- seriesNames := seriesList.Names()
+ seriesNames := seriesList.names()
offset := max - min
for index, item := range seriesList {
- value := item.Data[0]
// if the maximum and minimum are consistent it's 100%
widthPercent := 100.0
if offset != 0 {
- widthPercent = (value - min) / offset
+ widthPercent = (item.Value - min) / offset
}
w := int(widthPercent * float64(width))
widthList[index] = w
// if the maximum value is 0, the proportion is 100%
percent := 1.0
if max != 0 {
- percent = value / max
+ percent = item.Value / max
}
if !flagIs(false, item.Label.Show) {
if item.Label.ValueFormatter != nil || opt.ValueFormatter != nil {
- textList[index] = getPreferredValueFormatter(item.Label.ValueFormatter, opt.ValueFormatter)(value)
+ textList[index] = getPreferredValueFormatter(item.Label.ValueFormatter, opt.ValueFormatter)(item.Value)
} else {
- textList[index] = labelFormatFunnel(seriesNames, item.Label.FormatTemplate, index, value, percent)
+ textList[index] = labelFormatFunnel(seriesNames, item.Label.FormatTemplate, index, item.Value, percent)
}
}
}
@@ -165,6 +164,5 @@ func (f *funnelChart) Render() (Box, error) {
if err != nil {
return BoxZero, err
}
- seriesList := opt.SeriesList.Filter(ChartTypeFunnel)
- return f.render(renderResult, seriesList)
+ return f.render(renderResult, opt.SeriesList)
}
diff --git a/funnel_chart_test.go b/funnel_chart_test.go
index 673ab1f..40e54fa 100644
--- a/funnel_chart_test.go
+++ b/funnel_chart_test.go
@@ -28,7 +28,7 @@ func TestNewFunnelChartOptionWithData(t *testing.T) {
opt := NewFunnelChartOptionWithData([]float64{12, 24, 48})
assert.Len(t, opt.SeriesList, 3)
- assert.Equal(t, ChartTypeFunnel, opt.SeriesList[0].Type)
+ assert.Equal(t, ChartTypeFunnel, opt.SeriesList[0].getType())
assert.Equal(t, defaultPadding, opt.Padding)
p := NewPainter(PainterOptions{})
diff --git a/horizontal_bar_chart.go b/horizontal_bar_chart.go
index de03e66..b5d85fd 100644
--- a/horizontal_bar_chart.go
+++ b/horizontal_bar_chart.go
@@ -30,8 +30,8 @@ type HorizontalBarChartOption struct {
Padding Box
// Font is the font used to render the chart.
Font *truetype.Font
- // SeriesList provides the data series.
- SeriesList SeriesList
+ // SeriesList provides the data population for the chart, typically constructed using NewSeriesListHorizontalBar.
+ SeriesList HorizontalBarSeriesList
// StackSeries if set to *true a single bar with the colored series stacked together will be rendered.
// This feature will result in some options being ignored, including BarMargin and SeriesLabelPosition.
// MarkLine is also interpreted differently, only the first Series will have the MarkLine rendered (as it's the
@@ -64,7 +64,7 @@ func newHorizontalBarChart(p *Painter, opt HorizontalBarChartOption) *horizontal
}
}
-func (h *horizontalBarChart) render(result *defaultRenderResult, seriesList SeriesList) (Box, error) {
+func (h *horizontalBarChart) render(result *defaultRenderResult, seriesList HorizontalBarSeriesList) (Box, error) {
p := h.p
opt := h.opt
seriesCount := len(seriesList)
@@ -76,7 +76,7 @@ func (h *horizontalBarChart) render(result *defaultRenderResult, seriesList Seri
y0, y1 := yRange.GetRange(0)
height := int(y1 - y0)
stackedSeries := flagIs(true, opt.StackSeries)
- min, max, sumMax := seriesList.getMinMaxSumMax(0, stackedSeries)
+ min, max, sumMax := getSeriesMinMaxSumMax(seriesList, 0, stackedSeries)
// If stacking, keep track of accumulated widths for each data index (after the “reverse” logic).
var accumulatedWidths []int
var margin, barMargin, barHeight int
@@ -89,10 +89,10 @@ func (h *horizontalBarChart) render(result *defaultRenderResult, seriesList Seri
margin, barMargin, barHeight = calculateBarMarginsAndSize(seriesCount, height, opt.BarHeight, opt.BarMargin)
}
- seriesNames := seriesList.Names()
+ seriesNames := seriesList.names()
// xRange is used to convert data values into horizontal bar widths
xRange := newRange(p, getPreferredValueFormatter(opt.XAxis.ValueFormatter, opt.ValueFormatter),
- seriesPainter.Width(), len(seriesList[0].Data), min, max, 1.0, 1.0)
+ seriesPainter.Width(), len(seriesList[0].Values), min, max, 1.0, 1.0)
divideValues := yRange.AutoDivide()
var rendererList []renderer
@@ -106,7 +106,7 @@ func (h *horizontalBarChart) render(result *defaultRenderResult, seriesList Seri
rendererList = append(rendererList, labelPainter)
}
- for j, item := range series.Data {
+ for j, item := range series.Values {
if j >= yRange.divideCount {
continue
}
@@ -201,6 +201,5 @@ func (h *horizontalBarChart) Render() (Box, error) {
if err != nil {
return BoxZero, err
}
- seriesList := opt.SeriesList.Filter(ChartTypeHorizontalBar)
- return h.render(renderResult, seriesList)
+ return h.render(renderResult, opt.SeriesList)
}
diff --git a/horizontal_bar_chart_test.go b/horizontal_bar_chart_test.go
index f624184..35beacd 100644
--- a/horizontal_bar_chart_test.go
+++ b/horizontal_bar_chart_test.go
@@ -76,7 +76,7 @@ func TestNewHorizontalBarChartOptionWithData(t *testing.T) {
})
assert.Len(t, opt.SeriesList, 2)
- assert.Equal(t, ChartTypeHorizontalBar, opt.SeriesList[0].Type)
+ assert.Equal(t, ChartTypeHorizontalBar, opt.SeriesList[0].getType())
assert.Equal(t, defaultPadding, opt.Padding)
p := NewPainter(PainterOptions{})
diff --git a/line_chart.go b/line_chart.go
index 8d39c55..b4cecbe 100644
--- a/line_chart.go
+++ b/line_chart.go
@@ -29,9 +29,9 @@ func NewLineChartOptionWithData(data [][]float64) LineChartOption {
Theme: GetDefaultTheme(),
Font: GetDefaultFont(),
XAxis: XAxisOption{
- Labels: make([]string, sl.getMaxDataCount(ChartTypeLine)),
+ Labels: make([]string, getSeriesMaxDataCount(sl)),
},
- YAxis: make([]YAxisOption, sl.getYAxisCount()),
+ YAxis: make([]YAxisOption, getSeriesYAxisCount(sl)),
ValueFormatter: defaultValueFormatter,
}
}
@@ -43,8 +43,8 @@ type LineChartOption struct {
Padding Box
// Font is the font used to render the chart.
Font *truetype.Font
- // SeriesList provides the data series.
- SeriesList SeriesList
+ // SeriesList provides the data population for the chart, typically constructed using NewSeriesListLine.
+ SeriesList LineSeriesList
// StackSeries if set to *true the lines will be layered over each other, with the last series value representing
// the sum of all the values. Enabling this will also enable FillArea (which until v0.5 can't be disabled).
// Some options will be ignored when StackedSeries is enabled, this includes StrokeSmoothingTension.
@@ -80,7 +80,7 @@ type LineChartOption struct {
const showSymbolDefaultThreshold = 100
-func (l *lineChart) render(result *defaultRenderResult, seriesList SeriesList) (Box, error) {
+func (l *lineChart) render(result *defaultRenderResult, seriesList LineSeriesList) (Box, error) {
p := l.p
opt := l.opt
seriesCount := len(seriesList)
@@ -111,7 +111,7 @@ func (l *lineChart) render(result *defaultRenderResult, seriesList SeriesList) (
}
xDivideValues := autoDivide(seriesPainter.Width(), xDivideCount)
xValues := make([]int, len(xDivideValues)-1)
- dataCount := seriesList.getMaxDataCount(ChartTypeLine)
+ dataCount := getSeriesMaxDataCount(seriesList)
// accumulatedValues is used for stacking: it holds the summed data values at each X index
var accumulatedValues []float64
if stackedSeries {
@@ -141,21 +141,21 @@ func (l *lineChart) render(result *defaultRenderResult, seriesList SeriesList) (
markLinePainter := newMarkLinePainter(seriesPainter)
rendererList := []renderer{markPointPainter, markLinePainter}
- seriesNames := seriesList.Names()
+ seriesNames := seriesList.names()
var priorSeriesPoints []Point
for index := range seriesList {
series := seriesList[index]
stackSeries := stackedSeries && series.YAxisIndex == 0
seriesColor := opt.Theme.GetSeriesColor(index)
yRange := result.axisRanges[series.YAxisIndex]
- points := make([]Point, len(series.Data))
+ points := make([]Point, len(series.Values))
var labelPainter *seriesLabelPainter
if flagIs(true, series.Label.Show) {
labelPainter = newSeriesLabelPainter(seriesPainter, seriesNames, series.Label, opt.Theme, opt.Font)
rendererList = append(rendererList, labelPainter)
}
- for i, item := range series.Data {
+ for i, item := range series.Values {
if item == GetNullValue() {
points[i] = Point{X: xValues[i], Y: math.MaxInt32}
} else if stackSeries {
@@ -251,7 +251,7 @@ func (l *lineChart) render(result *defaultRenderResult, seriesList SeriesList) (
var globalSeriesData []float64 // lazily initialized
if series.MarkLine.GlobalLine && stackSeries && index == seriesCount-1 {
if globalSeriesData == nil {
- globalSeriesData = seriesList.makeSumSeries(ChartTypeLine, series.YAxisIndex).Data
+ globalSeriesData = sumSeriesData(seriesList, series.YAxisIndex)
}
markLinePainter.add(markLineRenderOption{
fillColor: defaultGlobalMarkFillColor,
@@ -259,7 +259,7 @@ func (l *lineChart) render(result *defaultRenderResult, seriesList SeriesList) (
strokeColor: defaultGlobalMarkFillColor,
font: opt.Font,
markline: series.MarkLine,
- seriesData: globalSeriesData,
+ seriesValues: globalSeriesData,
axisRange: yRange,
valueFormatterDefault: markLineValueFormatter,
})
@@ -271,21 +271,21 @@ func (l *lineChart) render(result *defaultRenderResult, seriesList SeriesList) (
strokeColor: seriesColor,
font: opt.Font,
markline: series.MarkLine,
- seriesData: series.Data,
+ seriesValues: series.Values,
axisRange: yRange,
valueFormatterDefault: markLineValueFormatter,
})
}
if series.MarkPoint.GlobalPoint && stackSeries && index == seriesCount-1 {
if globalSeriesData == nil {
- globalSeriesData = seriesList.makeSumSeries(ChartTypeLine, series.YAxisIndex).Data
+ globalSeriesData = sumSeriesData(seriesList, series.YAxisIndex)
}
markPointPainter.add(markPointRenderOption{
fillColor: defaultGlobalMarkFillColor,
font: opt.Font,
points: points,
markpoint: series.MarkPoint,
- seriesData: globalSeriesData,
+ seriesValues: globalSeriesData,
valueFormatterDefault: markPointValueFormatter,
seriesLabelPainter: labelPainter,
})
@@ -295,7 +295,7 @@ func (l *lineChart) render(result *defaultRenderResult, seriesList SeriesList) (
font: opt.Font,
points: points,
markpoint: series.MarkPoint,
- seriesData: series.Data,
+ seriesValues: series.Values,
valueFormatterDefault: markPointValueFormatter,
seriesLabelPainter: labelPainter,
})
@@ -342,6 +342,5 @@ func (l *lineChart) Render() (Box, error) {
if err != nil {
return BoxZero, err
}
- seriesList := opt.SeriesList.Filter(ChartTypeLine)
- return l.render(renderResult, seriesList)
+ return l.render(renderResult, opt.SeriesList)
}
diff --git a/line_chart_test.go b/line_chart_test.go
index c8c12d1..e936c9b 100644
--- a/line_chart_test.go
+++ b/line_chart_test.go
@@ -120,7 +120,7 @@ func TestNewLineChartOptionWithData(t *testing.T) {
})
assert.Len(t, opt.SeriesList, 2)
- assert.Equal(t, ChartTypeLine, opt.SeriesList[0].Type)
+ assert.Equal(t, ChartTypeLine, opt.SeriesList[0].getType())
assert.Len(t, opt.YAxis, 1)
assert.Equal(t, defaultPadding, opt.Padding)
@@ -502,7 +502,7 @@ func TestLineChart(t *testing.T) {
name: "line_gap",
makeOptions: func() LineChartOption {
opt := makeMinimalLineChartOption()
- opt.SeriesList[0].Data[3] = GetNullValue()
+ opt.SeriesList[0].Values[3] = GetNullValue()
return opt
},
result: "",
@@ -511,8 +511,8 @@ func TestLineChart(t *testing.T) {
name: "line_gap_dot",
makeOptions: func() LineChartOption {
opt := makeMinimalLineChartOption()
- opt.SeriesList[0].Data[3] = GetNullValue()
- opt.SeriesList[0].Data[5] = GetNullValue()
+ opt.SeriesList[0].Values[3] = GetNullValue()
+ opt.SeriesList[0].Values[5] = GetNullValue()
return opt
},
result: "",
@@ -521,7 +521,7 @@ func TestLineChart(t *testing.T) {
name: "line_gap_fill_area",
makeOptions: func() LineChartOption {
opt := makeMinimalLineChartOption()
- opt.SeriesList[0].Data[3] = GetNullValue()
+ opt.SeriesList[0].Values[3] = GetNullValue()
opt.FillArea = Ptr(true)
return opt
},
@@ -531,9 +531,9 @@ func TestLineChart(t *testing.T) {
name: "line_gap_start_fill_area",
makeOptions: func() LineChartOption {
opt := makeMinimalLineChartOption()
- opt.SeriesList[0].Data[0] = GetNullValue()
- opt.SeriesList[0].Data[1] = GetNullValue()
- opt.SeriesList[1].Data[0] = GetNullValue()
+ opt.SeriesList[0].Values[0] = GetNullValue()
+ opt.SeriesList[0].Values[1] = GetNullValue()
+ opt.SeriesList[1].Values[0] = GetNullValue()
opt.FillArea = Ptr(true)
return opt
},
@@ -544,7 +544,7 @@ func TestLineChart(t *testing.T) {
makeOptions: func() LineChartOption {
opt := makeMinimalLineChartOption()
opt.StrokeSmoothingTension = 0.8
- opt.SeriesList[0].Data[3] = GetNullValue()
+ opt.SeriesList[0].Values[3] = GetNullValue()
return opt
},
result: "",
@@ -554,7 +554,7 @@ func TestLineChart(t *testing.T) {
makeOptions: func() LineChartOption {
opt := makeMinimalLineChartOption()
opt.StrokeSmoothingTension = 0.8
- opt.SeriesList[0].Data[3] = GetNullValue()
+ opt.SeriesList[0].Values[3] = GetNullValue()
opt.FillArea = Ptr(true)
return opt
},
diff --git a/mark_line.go b/mark_line.go
index 706bc63..7525497 100644
--- a/mark_line.go
+++ b/mark_line.go
@@ -40,7 +40,7 @@ type markLineRenderOption struct {
fontColor Color
strokeColor Color
font *truetype.Font
- seriesData []float64
+ seriesValues []float64
markline SeriesMarkLine
axisRange axisRange
valueFormatterDefault ValueFormatter
@@ -52,7 +52,7 @@ func (m *markLinePainter) Render() (Box, error) {
if len(opt.markline.Data) == 0 {
continue
}
- summary := summarizePopulationData(opt.seriesData)
+ summary := summarizePopulationData(opt.seriesValues)
fontStyle := FontStyle{
Font: getPreferredFont(opt.font),
FontColor: opt.fontColor,
diff --git a/mark_line_test.go b/mark_line_test.go
index 615076f..5814d2b 100644
--- a/mark_line_test.go
+++ b/mark_line_test.go
@@ -18,10 +18,10 @@ func TestMarkLine(t *testing.T) {
render: func(p *Painter) ([]byte, error) {
markLine := newMarkLinePainter(p)
markLine.add(markLineRenderOption{
- fillColor: ColorBlack,
- fontColor: ColorBlack,
- strokeColor: ColorBlack,
- seriesData: []float64{1, 2, 3},
+ fillColor: ColorBlack,
+ fontColor: ColorBlack,
+ strokeColor: ColorBlack,
+ seriesValues: []float64{1, 2, 3},
markline: NewMarkLine(
SeriesMarkDataTypeMax,
SeriesMarkDataTypeAverage,
diff --git a/mark_point.go b/mark_point.go
index a408fd8..7002f94 100644
--- a/mark_point.go
+++ b/mark_point.go
@@ -33,7 +33,7 @@ func (m *markPointPainter) add(opt markPointRenderOption) {
type markPointRenderOption struct {
fillColor Color
font *truetype.Font
- seriesData []float64
+ seriesValues []float64
markpoint SeriesMarkPoint
seriesLabelPainter *seriesLabelPainter
points []Point
@@ -54,7 +54,7 @@ func (m *markPointPainter) Render() (Box, error) {
continue
}
points := opt.points
- summary := summarizePopulationData(opt.seriesData)
+ summary := summarizePopulationData(opt.seriesValues)
symbolSize := opt.markpoint.SymbolSize
if symbolSize == 0 {
symbolSize = 28
diff --git a/mark_point_test.go b/mark_point_test.go
index 565b917..3f71fd2 100644
--- a/mark_point_test.go
+++ b/mark_point_test.go
@@ -18,9 +18,9 @@ func TestMarkPoint(t *testing.T) {
render: func(p *Painter) ([]byte, error) {
markPoint := newMarkPointPainter(p)
markPoint.add(markPointRenderOption{
- fillColor: ColorBlack,
- seriesData: []float64{1, 2, 3},
- markpoint: NewMarkPoint(SeriesMarkDataTypeMax),
+ fillColor: ColorBlack,
+ seriesValues: []float64{1, 2, 3},
+ markpoint: NewMarkPoint(SeriesMarkDataTypeMax),
points: []Point{
{X: 10, Y: 10},
{X: 30, Y: 30},
diff --git a/pie_chart.go b/pie_chart.go
index 6f2e270..b21555c 100644
--- a/pie_chart.go
+++ b/pie_chart.go
@@ -32,8 +32,8 @@ type PieChartOption struct {
Padding Box
// Font is the font used to render the chart.
Font *truetype.Font
- // SeriesList provides the data series.
- SeriesList SeriesList
+ // SeriesList provides the data population for the chart, typically constructed using NewSeriesListPie.
+ SeriesList PieSeriesList
// Title are options for rendering the title.
Title TitleOption
// Legend are options for the data legend.
@@ -149,26 +149,23 @@ func (s *sector) calculateTextXY(textBox Box) (x int, y int) {
return
}
-func (p *pieChart) render(result *defaultRenderResult, seriesList SeriesList) (Box, error) {
+func (p *pieChart) render(result *defaultRenderResult, seriesList PieSeriesList) (Box, error) {
opt := p.opt
seriesCount := len(seriesList)
if seriesCount == 0 {
return BoxZero, errors.New("empty series list")
}
- values := make([]float64, seriesCount)
var total float64
var radiusValue string
for index, series := range seriesList {
if series.Radius != "" {
radiusValue = series.Radius
}
- value := chartdraw.SumFloat64(series.Data...)
- values[index] = value
- total += value
- if value < 0 {
+ if series.Value < 0 {
return BoxZero, fmt.Errorf("unsupported negative value for series index %d", index)
}
+ total += series.Value
}
if total <= 0 {
return BoxZero, errors.New("the sum value of pie chart should greater than 0")
@@ -187,25 +184,25 @@ func (p *pieChart) render(result *defaultRenderResult, seriesList SeriesList) (B
labelRadius := radius + float64(labelLineWidth)
seriesNames := opt.Legend.SeriesNames
if len(seriesNames) == 0 {
- seriesNames = seriesList.Names()
+ seriesNames = seriesList.names()
}
theme := opt.Theme
var currentValue float64
var quadrant1, quadrant2, quadrant3, quadrant4 []sector
- for index, v := range values {
- series := seriesList[index]
+ seriesLen := len(seriesList)
+ for index, series := range seriesList {
seriesRadius := radius
if series.Radius != "" {
seriesRadius = getRadius(float64(diameter), series.Radius)
}
color := theme.GetSeriesColor(index)
- if index == len(values)-1 {
+ if index == seriesLen-1 {
if color == theme.GetSeriesColor(0) {
color = theme.GetSeriesColor(1)
}
}
- s := newSector(cx, cy, seriesRadius, labelRadius, v, currentValue, total, labelLineWidth,
+ s := newSector(cx, cy, seriesRadius, labelRadius, series.Value, currentValue, total, labelLineWidth,
seriesNames[index], series.Label, opt.ValueFormatter, color)
switch quadrant := s.quadrant; quadrant {
case 1:
@@ -217,7 +214,7 @@ func (p *pieChart) render(result *defaultRenderResult, seriesList SeriesList) (B
case 4:
quadrant4 = append(quadrant4, s)
}
- currentValue += v
+ currentValue += series.Value
}
sectors := append(quadrant1, quadrant4...)
sectors = append(sectors, quadrant3...)
@@ -304,6 +301,5 @@ func (p *pieChart) Render() (Box, error) {
if err != nil {
return BoxZero, err
}
- seriesList := opt.SeriesList.Filter(ChartTypePie)
- return p.render(renderResult, seriesList)
+ return p.render(renderResult, opt.SeriesList)
}
diff --git a/pie_chart_test.go b/pie_chart_test.go
index 31a4e3c..9fc644a 100644
--- a/pie_chart_test.go
+++ b/pie_chart_test.go
@@ -34,7 +34,7 @@ func TestNewPieChartOptionWithData(t *testing.T) {
opt := NewPieChartOptionWithData([]float64{12, 24, 48})
assert.Len(t, opt.SeriesList, 3)
- assert.Equal(t, ChartTypePie, opt.SeriesList[0].Type)
+ assert.Equal(t, ChartTypePie, opt.SeriesList[0].getType())
assert.Equal(t, defaultPadding, opt.Padding)
p := NewPainter(PainterOptions{})
diff --git a/radar_chart.go b/radar_chart.go
index 1c678c2..abb6f29 100644
--- a/radar_chart.go
+++ b/radar_chart.go
@@ -45,14 +45,16 @@ type RadarChartOption struct {
Padding Box
// Font is the font used to render the chart.
Font *truetype.Font
- // SeriesList provides the data series.
- SeriesList SeriesList
+ // SeriesList provides the data population for the chart, typically constructed using NewSeriesListRadar.
+ SeriesList RadarSeriesList
// Title are options for rendering the title.
Title TitleOption
// Legend are options for the data legend.
Legend LegendOption
// RadarIndicators provides the radar indicator list.
RadarIndicators []RadarIndicator
+ // Radius for radar e.g.: 40%, default is "40%"
+ Radius string
// ValueFormatter defines how float values should be rendered to strings, notably for series labels.
ValueFormatter ValueFormatter
// backgroundIsFilled is set to true if the background is filled.
@@ -82,7 +84,7 @@ func newRadarChart(p *Painter, opt RadarChartOption) *radarChart {
}
}
-func (r *radarChart) render(result *defaultRenderResult, seriesList SeriesList) (Box, error) {
+func (r *radarChart) render(result *defaultRenderResult, seriesList RadarSeriesList) (Box, error) {
opt := r.opt
indicators := opt.RadarIndicators
sides := len(indicators)
@@ -93,7 +95,7 @@ func (r *radarChart) render(result *defaultRenderResult, seriesList SeriesList)
}
maxValues := make([]float64, len(indicators))
for _, series := range seriesList {
- for index, item := range series.Data {
+ for index, item := range series.Values {
if index < len(maxValues) && item > maxValues[index] {
maxValues[index] = item
}
@@ -105,20 +107,13 @@ func (r *radarChart) render(result *defaultRenderResult, seriesList SeriesList)
}
}
- var radiusValue string
- for _, series := range seriesList {
- if series.Radius != "" {
- radiusValue = series.Radius
- }
- }
-
seriesPainter := result.seriesPainter
theme := opt.Theme
cx := seriesPainter.Width() >> 1
cy := seriesPainter.Height() >> 1
diameter := chartdraw.MinInt(seriesPainter.Width(), seriesPainter.Height())
- radius := getRadius(float64(diameter), radiusValue)
+ radius := getRadius(float64(diameter), opt.Radius)
divideCount := 5
divideRadius := float64(int(radius / float64(divideCount)))
@@ -185,7 +180,7 @@ func (r *radarChart) render(result *defaultRenderResult, seriesList SeriesList)
valueFormatter := getPreferredValueFormatter(series.Label.ValueFormatter, opt.ValueFormatter,
radarDefaultValueFormatter)
linePoints := make([]Point, 0, maxCount)
- for j, item := range series.Data {
+ for j, item := range series.Values {
if j >= maxCount {
continue
}
@@ -210,8 +205,8 @@ func (r *radarChart) render(result *defaultRenderResult, seriesList SeriesList)
dotWith := defaultDotWidth
for index, point := range linePoints {
seriesPainter.Circle(dotWith, point.X, point.Y, dotFillColor, color, defaultStrokeWidth)
- if flagIs(true, series.Label.Show) && index < len(series.Data) {
- valueStr := valueFormatter(series.Data[index])
+ if flagIs(true, series.Label.Show) && index < len(series.Values) {
+ valueStr := valueFormatter(series.Values[index])
b := seriesPainter.MeasureText(valueStr, 0, fontStyle)
seriesPainter.Text(valueStr, point.X-b.Width()/2, point.Y, 0, fontStyle)
}
@@ -247,6 +242,5 @@ func (r *radarChart) Render() (Box, error) {
if err != nil {
return BoxZero, err
}
- seriesList := opt.SeriesList.Filter(ChartTypeRadar)
- return r.render(renderResult, seriesList)
+ return r.render(renderResult, opt.SeriesList)
}
diff --git a/radar_chart_test.go b/radar_chart_test.go
index 721813b..7a23f79 100644
--- a/radar_chart_test.go
+++ b/radar_chart_test.go
@@ -52,7 +52,7 @@ func TestNewRadarChartOptionWithData(t *testing.T) {
})
assert.Len(t, opt.SeriesList, 2)
- assert.Equal(t, ChartTypeRadar, opt.SeriesList[0].Type)
+ assert.Equal(t, ChartTypeRadar, opt.SeriesList[0].getType())
assert.Equal(t, defaultPadding, opt.Padding)
p := NewPainter(PainterOptions{})
diff --git a/series.go b/series.go
index fdfe557..65e3581 100644
--- a/series.go
+++ b/series.go
@@ -6,28 +6,9 @@ import (
"strings"
"github.com/dustin/go-humanize"
-)
-// newSeriesListFromValues returns a series list for the given values and chart type.
-func newSeriesListFromValues(values [][]float64, chartType string, label SeriesLabel, names []string,
- radius string, markPoint SeriesMarkPoint, markLine SeriesMarkLine) SeriesList {
- seriesList := make(SeriesList, len(values))
- for index, value := range values {
- s := Series{
- Data: value,
- Type: chartType,
- Label: label,
- Radius: radius,
- MarkPoint: markPoint,
- MarkLine: markLine,
- }
- if index < len(names) {
- s.Name = names[index]
- }
- seriesList[index] = s
- }
- return seriesList
-}
+ "github.com/go-analyze/charts/chartdraw"
+)
type SeriesLabel struct {
// FormatTemplate is a string template for formatting the data label.
@@ -80,12 +61,13 @@ type SeriesMarkLine struct {
Data []SeriesMarkData
}
-// Series references a population of data.
-type Series struct {
+// GenericSeries references a population of data for any type of charts. The chart specific fields will only be active
+// for chart types which support them.
+type GenericSeries struct {
// Type is the type of series, it can be "line", "bar" or "pie". Default value is "line".
Type string
- // Data provides the series data list.
- Data []float64
+ // Values provides the series data values.
+ Values []float64
// YAxisIndex is the index for the axis, it must be 0 or 1.
YAxisIndex int
// Label provides the series labels.
@@ -102,53 +84,778 @@ type Series struct {
MarkLine SeriesMarkLine
}
-// SeriesList is a list of series to be rendered on the chart, typically constructed using NewSeriesListLine,
-// NewSeriesListBar, NewSeriesListHorizontalBar, NewSeriesListPie, NewSeriesListRadar, or NewSeriesListFunnel.
-// These Series can be appended to each other if multiple chart types should be rendered to the same axis.
-type SeriesList []Series
+func (g *GenericSeries) getYAxisIndex() int {
+ return g.YAxisIndex
+}
+
+func (g *GenericSeries) getValues() []float64 {
+ return g.Values
+}
+
+func (g *GenericSeries) getType() string {
+ return g.Type
+}
+
+// GenericSeriesList provides the data populations for any chart type configured through ChartOption.
+type GenericSeriesList []GenericSeries
+
+func (g GenericSeriesList) names() []string {
+ return seriesNames(g)
+}
+
+func (g GenericSeriesList) len() int {
+ return len(g)
+}
+
+func (g GenericSeriesList) getSeries(index int) series {
+ return &g[index]
+}
+
+func (g GenericSeriesList) getSeriesName(index int) string {
+ return g[index].Name
+}
+
+func (g GenericSeriesList) getSeriesValues(index int) []float64 {
+ return g[index].Values
+}
+
+func (g GenericSeriesList) hasMarkPoint() bool {
+ for _, s := range g {
+ if len(s.MarkPoint.Data) > 0 {
+ return true
+ }
+ }
+ return false
+}
+
+func (g GenericSeriesList) setSeriesName(index int, name string) {
+ g[index].Name = name
+}
+
+func (g GenericSeriesList) sortByNameIndex(dict map[string]int) {
+ sort.Slice(g, func(i, j int) bool {
+ return dict[g[i].Name] < dict[g[j].Name]
+ })
+}
+
+// LineSeries references a population of data for line charts.
+type LineSeries struct {
+ // Values provides the series data values.
+ Values []float64
+ // YAxisIndex is the index for the axis, it must be 0 or 1.
+ YAxisIndex int
+ // Label provides the series labels.
+ Label SeriesLabel
+ // Name specifies a name for the series.
+ Name string
+ // MarkPoint provides a series for mark points. If Label is also enabled, the MarkPoint will replace the label
+ // where rendered.
+ MarkPoint SeriesMarkPoint
+ // MarkLine provides a series for mark lines. When using a MarkLine, you will want to configure padding to the
+ // chart on the right for the values.
+ MarkLine SeriesMarkLine
+}
+
+func (l *LineSeries) getYAxisIndex() int {
+ return l.YAxisIndex
+}
+
+func (l *LineSeries) getValues() []float64 {
+ return l.Values
+}
+
+func (l *LineSeries) getType() string {
+ return ChartTypeLine
+}
+
+func (l *LineSeries) Summary() populationSummary {
+ return summarizePopulationData(l.Values)
+}
+
+// LineSeriesList provides the data populations for line charts (LineChartOption).
+type LineSeriesList []LineSeries
+
+func (l LineSeriesList) names() []string {
+ return seriesNames(l)
+}
+
+func (l LineSeriesList) SumSeries() []float64 {
+ return sumSeriesData(l, -1)
+}
+
+func (l LineSeriesList) len() int {
+ return len(l)
+}
+
+func (l LineSeriesList) getSeries(index int) series {
+ return &l[index]
+}
+
+func (l LineSeriesList) getSeriesName(index int) string {
+ return l[index].Name
+}
+
+func (l LineSeriesList) getSeriesValues(index int) []float64 {
+ return l[index].Values
+}
+
+func (l LineSeriesList) hasMarkPoint() bool {
+ for _, s := range l {
+ if len(s.MarkPoint.Data) > 0 {
+ return true
+ }
+ }
+ return false
+}
+
+func (l LineSeriesList) setSeriesName(index int, name string) {
+ l[index].Name = name
+}
+
+func (l LineSeriesList) sortByNameIndex(dict map[string]int) {
+ sort.Slice(l, func(i, j int) bool {
+ return dict[l[i].Name] < dict[l[j].Name]
+ })
+}
+
+func (l LineSeriesList) ToGenericSeriesList() GenericSeriesList {
+ result := make([]GenericSeries, len(l))
+ for i, s := range l {
+ result[i] = GenericSeries{
+ Values: s.Values,
+ YAxisIndex: s.YAxisIndex,
+ Label: s.Label,
+ Name: s.Name,
+ Type: ChartTypeLine,
+ MarkLine: s.MarkLine,
+ MarkPoint: s.MarkPoint,
+ }
+ }
+ return result
+}
+
+// BarSeries references a population of data for bar charts.
+type BarSeries struct {
+ // Values provides the series data values.
+ Values []float64
+ // YAxisIndex is the index for the axis, it must be 0 or 1.
+ YAxisIndex int
+ // Label provides the series labels.
+ Label SeriesLabel
+ // Name specifies a name for the series.
+ Name string
+ // MarkPoint provides a series for mark points. If Label is also enabled, the MarkPoint will replace the label
+ // where rendered.
+ MarkPoint SeriesMarkPoint
+ // MarkLine provides a series for mark lines. When using a MarkLine, you will want to configure padding to the
+ // chart on the right for the values.
+ MarkLine SeriesMarkLine
+}
+
+func (b *BarSeries) getYAxisIndex() int {
+ return b.YAxisIndex
+}
+
+func (b *BarSeries) getValues() []float64 {
+ return b.Values
+}
+
+func (b *BarSeries) getType() string {
+ return ChartTypeBar
+}
+
+func (b *BarSeries) Summary() populationSummary {
+ return summarizePopulationData(b.Values)
+}
+
+// BarSeriesList provides the data populations for line charts (BarChartOption).
+type BarSeriesList []BarSeries
+
+func (b BarSeriesList) names() []string {
+ return seriesNames(b)
+}
+
+func (b BarSeriesList) SumSeries() []float64 {
+ return sumSeriesData(b, -1)
+}
+
+func (b BarSeriesList) len() int {
+ return len(b)
+}
+
+func (b BarSeriesList) getSeries(index int) series {
+ return &b[index]
+}
+
+func (b BarSeriesList) getSeriesName(index int) string {
+ return b[index].Name
+}
+
+func (b BarSeriesList) getSeriesValues(index int) []float64 {
+ return b[index].Values
+}
+
+func (b BarSeriesList) hasMarkPoint() bool {
+ for _, s := range b {
+ if len(s.MarkPoint.Data) > 0 {
+ return true
+ }
+ }
+ return false
+}
+
+func (b BarSeriesList) setSeriesName(index int, name string) {
+ b[index].Name = name
+}
+
+func (b BarSeriesList) sortByNameIndex(dict map[string]int) {
+ sort.Slice(b, func(i, j int) bool {
+ return dict[b[i].Name] < dict[b[j].Name]
+ })
+}
+
+func (b BarSeriesList) ToGenericSeriesList() GenericSeriesList {
+ result := make([]GenericSeries, len(b))
+ for i, s := range b {
+ result[i] = GenericSeries{
+ Values: s.Values,
+ YAxisIndex: s.YAxisIndex,
+ Label: s.Label,
+ Name: s.Name,
+ Type: ChartTypeBar,
+ MarkLine: s.MarkLine,
+ MarkPoint: s.MarkPoint,
+ }
+ }
+ return result
+}
+
+// HorizontalBarSeries references a population of data for horizontal bar charts.
+type HorizontalBarSeries struct {
+ // Values provides the series data values.
+ Values []float64
+ // Label provides the series labels.
+ Label SeriesLabel
+ // Name specifies a name for the series.
+ Name string
+}
+
+func (h *HorizontalBarSeries) getYAxisIndex() int {
+ return 0
+}
+
+func (h *HorizontalBarSeries) getValues() []float64 {
+ return h.Values
+}
+
+func (h *HorizontalBarSeries) getType() string {
+ return ChartTypeHorizontalBar
+}
+
+func (h *HorizontalBarSeries) Summary() populationSummary {
+ return summarizePopulationData(h.Values)
+}
+
+// HorizontalBarSeriesList provides the data populations for horizontal bar charts (HorizontalBarChartOption).
+type HorizontalBarSeriesList []HorizontalBarSeries
+
+func (h HorizontalBarSeriesList) names() []string {
+ return seriesNames(h)
+}
+
+func (h HorizontalBarSeriesList) SumSeries() []float64 {
+ return sumSeriesData(h, -1)
+}
+
+func (h HorizontalBarSeriesList) len() int {
+ return len(h)
+}
+
+func (h HorizontalBarSeriesList) getSeries(index int) series {
+ return &h[index]
+}
+
+func (h HorizontalBarSeriesList) getSeriesName(index int) string {
+ return h[index].Name
+}
+
+func (h HorizontalBarSeriesList) getSeriesValues(index int) []float64 {
+ return h[index].Values
+}
+
+func (h HorizontalBarSeriesList) hasMarkPoint() bool {
+ return false // not currently supported on this chart type
+}
+
+func (h HorizontalBarSeriesList) setSeriesName(index int, name string) {
+ h[index].Name = name
+}
+
+func (h HorizontalBarSeriesList) sortByNameIndex(dict map[string]int) {
+ sort.Slice(h, func(i, j int) bool {
+ return dict[h[i].Name] < dict[h[j].Name]
+ })
+}
+
+func (h HorizontalBarSeriesList) ToGenericSeriesList() GenericSeriesList {
+ result := make([]GenericSeries, len(h))
+ for i, s := range h {
+ result[i] = GenericSeries{
+ Values: s.Values,
+ Label: s.Label,
+ Name: s.Name,
+ Type: ChartTypeHorizontalBar,
+ }
+ }
+ return result
+}
+
+// FunnelSeries references a population of data for funnel charts.
+type FunnelSeries struct {
+ // Value provides the value for the funnel section.
+ Value float64
+ // Label provides the series labels.
+ Label SeriesLabel
+ // Name specifies a name for the series.
+ Name string
+}
+
+func (f *FunnelSeries) getYAxisIndex() int {
+ return 0
+}
+
+func (f *FunnelSeries) getValues() []float64 {
+ return []float64{f.Value}
+}
+
+func (f *FunnelSeries) getType() string {
+ return ChartTypeFunnel
+}
+
+// FunnelSeriesList provides the data populations for funnel charts (FunnelChartOption).
+type FunnelSeriesList []FunnelSeries
+
+func (f FunnelSeriesList) names() []string {
+ return seriesNames(f)
+}
+
+func (f FunnelSeriesList) len() int {
+ return len(f)
+}
+
+func (f FunnelSeriesList) getSeries(index int) series {
+ return &f[index]
+}
+
+func (f FunnelSeriesList) getSeriesName(index int) string {
+ return f[index].Name
+}
+
+func (f FunnelSeriesList) getSeriesValues(index int) []float64 {
+ return []float64{f[index].Value}
+}
-// Deprecated: Filter is deprecated, this function is not expected to be used outside the internal chart
-// implementation. If you make use of this function open a GitHub issue to mention its use.
-func (sl SeriesList) Filter(chartType string) SeriesList {
- arr := make(SeriesList, 0, len(sl))
- for index, item := range sl {
- if chartTypeMatch(chartType, item.Type) {
- arr = append(arr, sl[index])
+func (f FunnelSeriesList) hasMarkPoint() bool {
+ return false // not supported on this chart type
+}
+
+func (f FunnelSeriesList) setSeriesName(index int, name string) {
+ f[index].Name = name
+}
+
+func (f FunnelSeriesList) sortByNameIndex(dict map[string]int) {
+ sort.Slice(f, func(i, j int) bool {
+ return dict[f[i].Name] < dict[f[j].Name]
+ })
+}
+
+func (f FunnelSeriesList) ToGenericSeriesList() GenericSeriesList {
+ result := make([]GenericSeries, len(f))
+ for i, s := range f {
+ result[i] = GenericSeries{
+ Values: []float64{s.Value},
+ Label: s.Label,
+ Name: s.Name,
+ Type: ChartTypeFunnel,
}
}
- return arr
+ return result
+}
+
+// PieSeries references a population of data for pie charts.
+type PieSeries struct {
+ // Value provides the value for the pie section.
+ Value float64
+ // Label provides the series labels.
+ Label SeriesLabel
+ // Name specifies a name for the series.
+ Name string
+ // Radius for Pie chart, e.g.: 40%, default is "40%"
+ Radius string
+}
+
+func (p *PieSeries) getYAxisIndex() int {
+ return 0
+}
+
+func (p *PieSeries) getValues() []float64 {
+ return []float64{p.Value}
+}
+
+func (p *PieSeries) getType() string {
+ return ChartTypePie
+}
+
+// PieSeriesList provides the data populations for pie charts (PieChartOption).
+type PieSeriesList []PieSeries
+
+func (p PieSeriesList) names() []string {
+ return seriesNames(p)
+}
+
+func (p PieSeriesList) len() int {
+ return len(p)
+}
+
+func (p PieSeriesList) getSeries(index int) series {
+ return &p[index]
+}
+
+func (p PieSeriesList) getSeriesName(index int) string {
+ return p[index].Name
+}
+
+func (p PieSeriesList) getSeriesValues(index int) []float64 {
+ return []float64{p[index].Value}
+}
+
+func (p PieSeriesList) hasMarkPoint() bool {
+ return false // not supported on this chart type
+}
+
+func (p PieSeriesList) setSeriesName(index int, name string) {
+ p[index].Name = name
+}
+
+func (p PieSeriesList) sortByNameIndex(dict map[string]int) {
+ sort.Slice(p, func(i, j int) bool {
+ return dict[p[i].Name] < dict[p[j].Name]
+ })
+}
+
+func (p PieSeriesList) ToGenericSeriesList() GenericSeriesList {
+ result := make([]GenericSeries, len(p))
+ for i, s := range p {
+ result[i] = GenericSeries{
+ Values: []float64{s.Value},
+ Label: s.Label,
+ Name: s.Name,
+ Type: ChartTypePie,
+ Radius: s.Radius,
+ }
+ }
+ return result
+}
+
+// RadarSeries references a population of data for radar charts.
+type RadarSeries struct {
+ // Values provides the series data list.
+ Values []float64
+ // Label provides the series labels.
+ Label SeriesLabel
+ // Name specifies a name for the series.
+ Name string
+}
+
+func (r *RadarSeries) getYAxisIndex() int {
+ return 0
+}
+
+func (r *RadarSeries) getValues() []float64 {
+ return r.Values
+}
+
+func (r *RadarSeries) getType() string {
+ return ChartTypeRadar
+}
+
+// RadarSeriesList provides the data populations for line charts (RadarChartOption).
+type RadarSeriesList []RadarSeries
+
+func (r RadarSeriesList) names() []string {
+ return seriesNames(r)
+}
+
+func (r RadarSeriesList) len() int {
+ return len(r)
+}
+
+func (r RadarSeriesList) getSeries(index int) series {
+ return &r[index]
+}
+
+func (r RadarSeriesList) getSeriesName(index int) string {
+ return r[index].Name
+}
+
+func (r RadarSeriesList) getSeriesValues(index int) []float64 {
+ return r[index].Values
+}
+
+func (r RadarSeriesList) hasMarkPoint() bool {
+ return false // not supported on this chart type
+}
+
+func (r RadarSeriesList) setSeriesName(index int, name string) {
+ r[index].Name = name
+}
+
+func (r RadarSeriesList) sortByNameIndex(dict map[string]int) {
+ sort.Slice(r, func(i, j int) bool {
+ return dict[r[i].Name] < dict[r[j].Name]
+ })
+}
+
+func (r RadarSeriesList) ToGenericSeriesList() GenericSeriesList {
+ result := make([]GenericSeries, len(r))
+ for i, s := range r {
+ result[i] = GenericSeries{
+ Values: s.Values,
+ Label: s.Label,
+ Name: s.Name,
+ Type: ChartTypeRadar,
+ }
+ }
+ return result
+}
+
+// seriesList contains internal functions for operations that occur across chart types. Most of this interface usage
+// is within `series.go` and `charts.go`.
+type seriesList interface {
+ len() int
+ getSeries(index int) series
+ getSeriesName(index int) string
+ getSeriesValues(index int) []float64
+ names() []string
+ hasMarkPoint() bool
+ setSeriesName(index int, name string)
+ sortByNameIndex(dict map[string]int)
+}
+
+// series interface is used to provide the raw series struct to callers of seriesList, allowing direct type checks.
+type series interface {
+ getType() string
+ getYAxisIndex() int
+ getValues() []float64
+}
+
+func filterSeriesList[T any](sl seriesList, chartType string) T {
+ switch chartType {
+ case ChartTypeLine:
+ result := make(LineSeriesList, 0, sl.len())
+ for i := 0; i < sl.len(); i++ {
+ s := sl.getSeries(i)
+ if chartTypeMatch(chartType, s.getType()) {
+ switch v := s.(type) {
+ case *LineSeries:
+ result = append(result, *v)
+ case *GenericSeries:
+ result = append(result, LineSeries{
+ Values: v.Values,
+ YAxisIndex: v.YAxisIndex,
+ Label: v.Label,
+ Name: v.Name,
+ MarkLine: v.MarkLine,
+ MarkPoint: v.MarkPoint,
+ })
+ }
+ }
+ }
+ return any(result).(T)
+ case ChartTypeBar:
+ result := make(BarSeriesList, 0, sl.len())
+ for i := 0; i < sl.len(); i++ {
+ s := sl.getSeries(i)
+ if chartTypeMatch(chartType, s.getType()) {
+ switch v := s.(type) {
+ case *BarSeries:
+ result = append(result, *v)
+ case *GenericSeries:
+ result = append(result, BarSeries{
+ Values: v.Values,
+ YAxisIndex: v.YAxisIndex,
+ Label: v.Label,
+ Name: v.Name,
+ MarkLine: v.MarkLine,
+ MarkPoint: v.MarkPoint,
+ })
+ }
+ }
+ }
+ return any(result).(T)
+ case ChartTypeHorizontalBar:
+ result := make(HorizontalBarSeriesList, 0, sl.len())
+ for i := 0; i < sl.len(); i++ {
+ s := sl.getSeries(i)
+ if chartTypeMatch(chartType, s.getType()) {
+ switch v := s.(type) {
+ case *HorizontalBarSeries:
+ result = append(result, *v)
+ case *GenericSeries:
+ result = append(result, HorizontalBarSeries{
+ Values: v.Values,
+ Label: v.Label,
+ Name: v.Name,
+ })
+ }
+ }
+ }
+ return any(result).(T)
+ case ChartTypePie:
+ result := make(PieSeriesList, 0, sl.len())
+ for i := 0; i < sl.len(); i++ {
+ s := sl.getSeries(i)
+ if chartTypeMatch(chartType, s.getType()) {
+ switch v := s.(type) {
+ case *PieSeries:
+ result = append(result, *v)
+ case *GenericSeries:
+ result = append(result, PieSeries{
+ Value: chartdraw.SumFloat64(v.Values...),
+ Label: v.Label,
+ Name: v.Name,
+ Radius: v.Radius,
+ })
+ }
+ }
+ }
+ return any(result).(T)
+ case ChartTypeRadar:
+ result := make(RadarSeriesList, 0, sl.len())
+ for i := 0; i < sl.len(); i++ {
+ s := sl.getSeries(i)
+ if chartTypeMatch(chartType, s.getType()) {
+ switch v := s.(type) {
+ case *RadarSeries:
+ result = append(result, *v)
+ case *GenericSeries:
+ result = append(result, RadarSeries{
+ Values: v.Values,
+ Label: v.Label,
+ Name: v.Name,
+ })
+ }
+ }
+ }
+ return any(result).(T)
+ case ChartTypeFunnel:
+ result := make(FunnelSeriesList, 0, sl.len())
+ for i := 0; i < sl.len(); i++ {
+ s := sl.getSeries(i)
+ if chartTypeMatch(chartType, s.getType()) {
+ switch v := s.(type) {
+ case *FunnelSeries:
+ result = append(result, *v)
+ case *GenericSeries:
+ result = append(result, FunnelSeries{
+ Value: chartdraw.SumFloat64(v.Values...),
+ Label: v.Label,
+ Name: v.Name,
+ })
+ }
+ }
+ }
+ return any(result).(T)
+ default:
+ result := make(GenericSeriesList, 0, sl.len())
+ for i := 0; i < sl.len(); i++ {
+ s := sl.getSeries(i)
+ if chartTypeMatch(chartType, s.getType()) {
+ switch v := s.(type) {
+ case *LineSeries:
+ result = append(result, GenericSeries{
+ Values: v.Values,
+ YAxisIndex: v.YAxisIndex,
+ Label: v.Label,
+ Name: v.Name,
+ MarkLine: v.MarkLine,
+ MarkPoint: v.MarkPoint,
+ })
+ case *BarSeries:
+ result = append(result, GenericSeries{
+ Values: v.Values,
+ YAxisIndex: v.YAxisIndex,
+ Label: v.Label,
+ Name: v.Name,
+ MarkLine: v.MarkLine,
+ MarkPoint: v.MarkPoint,
+ })
+ case *HorizontalBarSeries:
+ result = append(result, GenericSeries{
+ Values: v.Values,
+ Label: v.Label,
+ Name: v.Name,
+ })
+ case *PieSeries:
+ result = append(result, GenericSeries{
+ Values: []float64{v.Value},
+ Label: v.Label,
+ Name: v.Name,
+ Radius: v.Radius,
+ })
+ case *RadarSeries:
+ result = append(result, GenericSeries{
+ Values: v.Values,
+ Label: v.Label,
+ Name: v.Name,
+ })
+ case *FunnelSeries:
+ result = append(result, GenericSeries{
+ Values: []float64{v.Value},
+ Label: v.Label,
+ Name: v.Name,
+ })
+ case *GenericSeries:
+ result = append(result, *v)
+ }
+ }
+ }
+ return any(result).(T)
+ }
}
func chartTypeMatch(expected, actual string) bool {
return expected == "" || expected == actual || (expected == ChartTypeLine && actual == "")
}
-func (sl SeriesList) getYAxisCount() int {
- for _, series := range sl {
- if series.YAxisIndex == 1 {
+func getSeriesYAxisCount(sl seriesList) int {
+ for i := 0; i < sl.len(); i++ {
+ axis := sl.getSeries(i).getYAxisIndex()
+ if axis == 1 {
return 2
- } else if series.YAxisIndex != 0 {
+ } else if axis != 0 {
return -1
}
}
return 1
}
-// getMinMaxSumMax returns the min, max, and maximum sum of the series for a given y-axis index (either 0 or 1).
+// getSeriesMinMaxSumMax returns the min, max, and maximum sum of the series for a given y-axis index (either 0 or 1).
// This is a higher performance option for internal use. calcSum provides an optimization to
// only calculate the sumMax if it will be used.
-func (sl SeriesList) getMinMaxSumMax(yaxisIndex int, calcSum bool) (float64, float64, float64) {
+func getSeriesMinMaxSumMax(sl seriesList, yaxisIndex int, calcSum bool) (float64, float64, float64) {
min := math.MaxFloat64
max := -math.MaxFloat64
var sums []float64
if calcSum {
- sums = make([]float64, sl.getMaxDataCount(""))
+ sums = make([]float64, getSeriesMaxDataCount(sl))
}
- for _, series := range sl {
- if series.YAxisIndex != yaxisIndex {
+ for i := 0; i < sl.len(); i++ {
+ series := sl.getSeries(i)
+ if series.getYAxisIndex() != yaxisIndex {
continue
}
- for i, item := range series.Data {
+ for i, item := range series.getValues() {
if item == GetNullValue() {
continue
}
@@ -174,6 +881,18 @@ func (sl SeriesList) getMinMaxSumMax(yaxisIndex int, calcSum bool) (float64, flo
return min, max, maxSum
}
+// NewSeriesListGeneric returns a Generic series list for the given values and chart type (used in ChartOption).
+func NewSeriesListGeneric(values [][]float64, chartType string) GenericSeriesList {
+ seriesList := make([]GenericSeries, len(values))
+ for index, v := range values {
+ seriesList[index] = GenericSeries{
+ Values: v,
+ Type: chartType,
+ }
+ }
+ return seriesList
+}
+
// LineSeriesOption provides series customization for NewSeriesListLine.
type LineSeriesOption struct {
Label SeriesLabel
@@ -184,13 +903,26 @@ type LineSeriesOption struct {
// NewSeriesListLine builds a SeriesList for a line chart. The first dimension of the values indicates the population
// of the data, while the second dimension provides the samples for the population.
-func NewSeriesListLine(values [][]float64, opts ...LineSeriesOption) SeriesList {
+func NewSeriesListLine(values [][]float64, opts ...LineSeriesOption) LineSeriesList {
var opt LineSeriesOption
if len(opts) != 0 {
opt = opts[0]
}
- return newSeriesListFromValues(values, ChartTypeLine,
- opt.Label, opt.Names, "", opt.MarkPoint, opt.MarkLine)
+
+ seriesList := make([]LineSeries, len(values))
+ for index, v := range values {
+ s := LineSeries{
+ Values: v,
+ Label: opt.Label,
+ MarkPoint: opt.MarkPoint,
+ MarkLine: opt.MarkLine,
+ }
+ if index < len(opt.Names) {
+ s.Name = opt.Names[index]
+ }
+ seriesList[index] = s
+ }
+ return seriesList
}
// BarSeriesOption provides series customization for NewSeriesListBar or NewSeriesListHorizontalBar.
@@ -203,24 +935,48 @@ type BarSeriesOption struct {
// NewSeriesListBar builds a SeriesList for a bar chart. The first dimension of the values indicates the population
// of the data, while the second dimension provides the samples for the population (on the X-Axis).
-func NewSeriesListBar(values [][]float64, opts ...BarSeriesOption) SeriesList {
+func NewSeriesListBar(values [][]float64, opts ...BarSeriesOption) BarSeriesList {
var opt BarSeriesOption
if len(opts) != 0 {
opt = opts[0]
}
- return newSeriesListFromValues(values, ChartTypeBar,
- opt.Label, opt.Names, "", opt.MarkPoint, opt.MarkLine)
+
+ seriesList := make([]BarSeries, len(values))
+ for index, v := range values {
+ s := BarSeries{
+ Values: v,
+ Label: opt.Label,
+ MarkPoint: opt.MarkPoint,
+ MarkLine: opt.MarkLine,
+ }
+ if index < len(opt.Names) {
+ s.Name = opt.Names[index]
+ }
+ seriesList[index] = s
+ }
+ return seriesList
}
// NewSeriesListHorizontalBar builds a SeriesList for a horizontal bar chart. Horizontal bar charts are unique in that
// these Series can not be combined with any other chart type.
-func NewSeriesListHorizontalBar(values [][]float64, opts ...BarSeriesOption) SeriesList {
+func NewSeriesListHorizontalBar(values [][]float64, opts ...BarSeriesOption) HorizontalBarSeriesList {
var opt BarSeriesOption
if len(opts) != 0 {
opt = opts[0]
}
- return newSeriesListFromValues(values, ChartTypeHorizontalBar,
- opt.Label, opt.Names, "", opt.MarkPoint, opt.MarkLine)
+
+ seriesList := make([]HorizontalBarSeries, len(values))
+ for index, v := range values {
+ s := HorizontalBarSeries{
+ Values: v,
+ Label: opt.Label,
+ }
+ if index < len(opt.Names) {
+ s.Name = opt.Names[index]
+ }
+ seriesList[index] = s
+ }
+ return seriesList
}
// PieSeriesOption provides series customization for NewSeriesListPie.
@@ -231,23 +987,21 @@ type PieSeriesOption struct {
}
// NewSeriesListPie builds a SeriesList for a pie chart.
-func NewSeriesListPie(values []float64, opts ...PieSeriesOption) SeriesList {
- result := make([]Series, len(values))
+func NewSeriesListPie(values []float64, opts ...PieSeriesOption) PieSeriesList {
var opt PieSeriesOption
if len(opts) != 0 {
opt = opts[0]
}
+
+ result := make([]PieSeries, len(values))
for index, v := range values {
- var name string
- if index < len(opt.Names) {
- name = opt.Names[index]
- }
- s := Series{
- Type: ChartTypePie,
- Data: []float64{v},
- Radius: opt.Radius,
+ s := PieSeries{
+ Value: v,
Label: opt.Label,
- Name: name,
+ Radius: opt.Radius,
+ }
+ if index < len(opt.Names) {
+ s.Name = opt.Names[index]
}
result[index] = s
}
@@ -261,13 +1015,24 @@ type RadarSeriesOption struct {
}
// NewSeriesListRadar builds a SeriesList for a Radar chart.
-func NewSeriesListRadar(values [][]float64, opts ...RadarSeriesOption) SeriesList {
+func NewSeriesListRadar(values [][]float64, opts ...RadarSeriesOption) RadarSeriesList {
var opt RadarSeriesOption
if len(opts) != 0 {
opt = opts[0]
}
- return newSeriesListFromValues(values, ChartTypeRadar,
- opt.Label, opt.Names, "", SeriesMarkPoint{}, SeriesMarkLine{})
+
+ result := make([]RadarSeries, len(values))
+ for index, v := range values {
+ s := RadarSeries{
+ Values: v,
+ Label: opt.Label,
+ }
+ if index < len(opt.Names) {
+ s.Name = opt.Names[index]
+ }
+ result[index] = s
+ }
+ return result
}
// FunnelSeriesOption provides series customization for NewSeriesListFunnel.
@@ -277,23 +1042,22 @@ type FunnelSeriesOption struct {
}
// NewSeriesListFunnel builds a series list for funnel charts.
-func NewSeriesListFunnel(values []float64, opts ...FunnelSeriesOption) SeriesList {
+func NewSeriesListFunnel(values []float64, opts ...FunnelSeriesOption) FunnelSeriesList {
var opt FunnelSeriesOption
if len(opts) != 0 {
opt = opts[0]
}
- seriesList := make(SeriesList, len(values))
+
+ seriesList := make([]FunnelSeries, len(values))
for index, value := range values {
- var name string
- if index < len(opt.Names) {
- name = opt.Names[index]
- }
- seriesList[index] = Series{
- Data: []float64{value},
- Type: ChartTypeFunnel,
+ s := FunnelSeries{
+ Value: value,
Label: opt.Label,
- Name: name,
}
+ if index < len(opt.Names) {
+ s.Name = opt.Names[index]
+ }
+ seriesList[index] = s
}
return seriesList
}
@@ -325,12 +1089,7 @@ type populationSummary struct {
Kurtosis float64
}
-// Summary returns numeric summary of series values (population statistics).
-func (s *Series) Summary() populationSummary {
- return summarizePopulationData(s.Data)
-}
-
-// summarizePopulationData returns numeric summary of series values (population statistics).
+// summarizePopulationData returns numeric summary of the values (population statistics).
func summarizePopulationData(data []float64) populationSummary {
n := float64(len(data))
if n == 0 {
@@ -422,61 +1181,49 @@ func summarizePopulationData(data []float64) populationSummary {
}
}
-// Deprecated: Names is deprecated, this is expected to be used internally only, if you use this function please open
-// a GitHub issue to let us know it's useful to you.
-func (sl SeriesList) Names() []string {
- names := make([]string, len(sl))
- for index, s := range sl {
- names[index] = s.Name
+// seriesNames returns the names of series list.
+func seriesNames(sl seriesList) []string {
+ names := make([]string, sl.len())
+ for index := range names {
+ names[index] = sl.getSeriesName(index)
}
return names
}
-// SumSeries will return a single Series which represents the sum of the entire SeriesList. This is useful for
-// providing global statistics through Series.Summary().
-func (sl SeriesList) SumSeries() Series {
- return sl.makeSumSeries("", -1)
-}
-
-func (sl SeriesList) makeSumSeries(chartType string, yaxisIndex int) Series {
- result := Series{
- Type: chartType,
- }
+func sumSeriesData(sl seriesList, yaxisIndex int) []float64 {
+ seriesLen := sl.len()
// check for fast path result
- switch len(sl) {
+ switch seriesLen {
case 0:
- return result
+ return make([]float64, 0)
case 1:
- if chartTypeMatch(chartType, sl[0].Type) && (yaxisIndex < 0 || sl[0].YAxisIndex == yaxisIndex) {
- return sl[0]
- } else {
- return result
+ s := sl.getSeries(0)
+ if yaxisIndex < 0 || s.getYAxisIndex() == yaxisIndex {
+ return s.getValues()
}
}
- sumValues := make([]float64, sl.getMaxDataCount(chartType))
- for _, s := range sl {
- if chartTypeMatch(chartType, s.Type) && (yaxisIndex < 0 || s.YAxisIndex == yaxisIndex) {
- result = s // ensure other series values are set into the result
- for i, f := range s.Data {
- if f != GetNullValue() {
- sumValues[i] += f
- }
+ sumValues := make([]float64, getSeriesMaxDataCount(sl))
+ for i1 := 0; i1 < seriesLen; i1++ {
+ s := sl.getSeries(i1)
+ if yaxisIndex > -1 && s.getYAxisIndex() != yaxisIndex {
+ continue
+ }
+ for i2, f := range s.getValues() {
+ if f != GetNullValue() {
+ sumValues[i2] += f
}
}
}
- result.Data = sumValues
- return result
+ return sumValues
}
-func (sl SeriesList) getMaxDataCount(chartType string) int {
+func getSeriesMaxDataCount(sl seriesList) int {
result := 0
- for _, s := range sl {
- if chartTypeMatch(chartType, s.Type) {
- count := len(s.Data)
- if count > result {
- result = count
- }
+ for i := 0; i < sl.len(); i++ {
+ count := len(sl.getSeriesValues(i))
+ if count > result {
+ result = count
}
}
return result
diff --git a/series_test.go b/series_test.go
index d5fd667..aa8e359 100644
--- a/series_test.go
+++ b/series_test.go
@@ -1,92 +1,129 @@
package charts
import (
+ "strconv"
"testing"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
-func TestNewSeriesListDataFromValues(t *testing.T) {
- t.Parallel()
-
- assert.Equal(t, SeriesList{
- {
- Type: ChartTypeBar,
- Data: []float64{1.0},
- },
- }, NewSeriesListBar([][]float64{
- {1},
- }))
-}
-
func TestSeriesLists(t *testing.T) {
t.Parallel()
- seriesList := NewSeriesListBar([][]float64{
+ values := [][]float64{
{1, 2},
{10},
{1, 2, 3, 4, 5, 6, 7, 8, 9},
- })
+ }
+ for i, tc := range []string{ChartTypeLine, ChartTypeBar, ChartTypeHorizontalBar} {
+ t.Run(strconv.Itoa(i)+"-"+tc, func(t *testing.T) {
+ var seriesList seriesList
+ switch tc { // switch case to ensure chart type and generic type match expectations
+ case ChartTypeLine:
+ seriesList = NewSeriesListLine(values)
+ assert.Len(t, filterSeriesList[LineSeriesList](seriesList, ChartTypeLine), 3)
+ assert.Empty(t, filterSeriesList[BarSeriesList](seriesList, ChartTypeBar))
+ case ChartTypeBar:
+ seriesList = NewSeriesListBar(values)
+ assert.Len(t, filterSeriesList[BarSeriesList](seriesList, ChartTypeBar), 3)
+ assert.Empty(t, filterSeriesList[LineSeriesList](seriesList, ChartTypeLine))
+ case ChartTypeHorizontalBar:
+ seriesList = NewSeriesListHorizontalBar(values)
+ assert.Len(t, filterSeriesList[HorizontalBarSeriesList](seriesList, ChartTypeHorizontalBar), 3)
+ assert.Empty(t, filterSeriesList[LineSeriesList](seriesList, ChartTypeLine))
+ default:
+ require.Fail(t, "Need to implement chart type test")
+ }
- assert.Len(t, seriesList.Filter(ChartTypeBar), 3)
- assert.Empty(t, seriesList.Filter(ChartTypeLine))
-
- min, max, maxSum := seriesList.getMinMaxSumMax(0, true)
- assert.InDelta(t, float64(12), maxSum, 0)
- assert.InDelta(t, float64(10), max, 0)
- assert.InDelta(t, float64(1), min, 0)
+ min, max, maxSum := getSeriesMinMaxSumMax(seriesList, 0, true)
+ assert.InDelta(t, float64(12), maxSum, 0)
+ assert.InDelta(t, float64(10), max, 0)
+ assert.InDelta(t, float64(1), min, 0)
+ })
+ }
}
func TestSumSeries(t *testing.T) {
t.Parallel()
- t.Run("empty", func(t *testing.T) {
- var sl SeriesList
- result := sl.SumSeries()
-
- assert.Equal(t, "", result.Type)
- assert.Empty(t, result.Data)
- })
-
- t.Run("single", func(t *testing.T) {
- sl := SeriesList{
- {
- Type: ChartTypeLine,
- Data: []float64{1.5, 2.5},
- Name: "SingleLine",
- YAxisIndex: 1,
- Radius: "50%",
+ type summableSeries interface {
+ SumSeries() []float64
+ }
+ testTypes := []struct {
+ name string
+ seriesFact func([][]float64) summableSeries
+ }{
+ {
+ name: "line",
+ seriesFact: func(values [][]float64) summableSeries {
+ return NewSeriesListLine(values)
},
- }
-
- result := sl.SumSeries()
-
- assert.Equal(t, sl[0], result)
- })
-
- t.Run("multiple", func(t *testing.T) {
- sl := NewSeriesListLine([][]float64{
- {1, 2, 3},
- {4, 5, 6},
- })
-
- result := sl.SumSeries()
-
- assert.Equal(t, ChartTypeLine, result.Type)
- assert.Equal(t, []float64{5, 7, 9}, result.Data)
- })
-
- t.Run("unequal_data_length", func(t *testing.T) {
- sl := NewSeriesListLine([][]float64{
- {1, 2},
- {3, 4, 5},
- })
+ },
+ {
+ name: "bar",
+ seriesFact: func(values [][]float64) summableSeries {
+ return NewSeriesListBar(values)
+ },
+ },
+ {
+ name: "horizontal_bar",
+ seriesFact: func(values [][]float64) summableSeries {
+ return NewSeriesListHorizontalBar(values)
+ },
+ },
+ }
+ tests := []struct {
+ name string
+ values [][]float64
+ expected []float64
+ }{
+ {
+ name: "empty",
+ values: [][]float64{},
+ expected: []float64{},
+ },
+ {
+ name: "single",
+ values: [][]float64{{1.5, 2.5}},
+ expected: []float64{1.5, 2.5},
+ },
+ {
+ name: "multiple",
+ values: [][]float64{
+ {1, 2, 3},
+ {4, 5, 6},
+ },
+ expected: []float64{5, 7, 9},
+ },
+ {
+ name: "unequal_data_length",
+ values: [][]float64{
+ {1, 2},
+ {3, 4, 5},
+ },
+ expected: []float64{4, 6, 5},
+ },
+ {
+ name: "null_values",
+ values: [][]float64{
+ {GetNullValue(), 2, 3},
+ {4, GetNullValue(), 6},
+ },
+ expected: []float64{4, 2, 9},
+ },
+ }
- result := sl.SumSeries()
+ for _, typeCase := range testTypes {
+ for _, tc := range tests {
+ t.Run(typeCase.name+"-"+tc.name, func(t *testing.T) {
+ series := typeCase.seriesFact(tc.values)
+ result := series.SumSeries()
- assert.Equal(t, ChartTypeLine, result.Type)
- assert.Equal(t, []float64{4, 6, 5}, result.Data)
- })
+ assert.Equal(t, tc.expected, result)
+ })
+ }
+ }
}
func TestSeriesSummary(t *testing.T) {
@@ -104,7 +141,7 @@ func TestSeriesSummary(t *testing.T) {
assert.Equal(t, populationSummary{
MaxIndex: -1,
MinIndex: -1,
- }, (&Series{}).Summary())
+ }, summarizePopulationData(nil))
})
t.Run("one_value", func(t *testing.T) {
assert.Equal(t, populationSummary{
@@ -182,3 +219,81 @@ func TestFormatter(t *testing.T) {
assert.Equal(t, "10",
labelFormatValue([]string{"a", "b"}, "", 0, 10, 0.12))
}
+
+func BenchmarkGetSeriesYAxisCount(b *testing.B) { // benchmark used to evaluate methods for iterating the series
+ nameCount := 100
+ seriesList := make(LineSeriesList, nameCount)
+ for i := 0; i < nameCount; i++ {
+ seriesList[i] = LineSeries{}
+ }
+
+ for i := 0; i < b.N; i++ {
+ _ = getSeriesYAxisCount(seriesList)
+ }
+}
+
+func BenchmarkGetSeriesMinMaxSumMax(b *testing.B) { // benchmark used to evaluate methods for iterating the series
+ seriesCount := 100
+ seriesSize := 100
+ seriesList := make(LineSeriesList, seriesCount)
+ for i := 0; i < seriesCount; i++ {
+ data := make([]float64, seriesSize)
+ for si := 0; si < seriesSize; si++ {
+ if si+1%10 == 0 {
+ data[si] = GetNullValue()
+ } else {
+ data[si] = float64(si)
+ }
+ }
+ seriesList[i] = LineSeries{
+ Values: data,
+ }
+ }
+
+ for i := 0; i < b.N; i++ {
+ _, _, _ = getSeriesMinMaxSumMax(seriesList, 0, true)
+ }
+}
+
+func BenchmarkSumSeries(b *testing.B) { // benchmark used to evaluate methods for iterating the series
+ seriesCount := 100
+ seriesSize := 100
+ seriesList := make(LineSeriesList, seriesCount)
+ for i := 0; i < seriesCount; i++ {
+ seriesList[i] = LineSeries{
+ Values: make([]float64, seriesSize),
+ }
+ }
+
+ for i := 0; i < b.N; i++ {
+ _ = seriesList.SumSeries()
+ }
+}
+
+func BenchmarkSeriesNames(b *testing.B) { // benchmark used to evaluate methods for iterating the series
+ nameCount := 100
+ seriesList := make(LineSeriesList, nameCount)
+ for i := 0; i < nameCount; i++ {
+ seriesList[i] = LineSeries{
+ Name: strconv.Itoa(i),
+ }
+ }
+
+ for i := 0; i < b.N; i++ {
+ _ = seriesList.names()
+ }
+}
+
+func BenchmarkGetSeriesMaxDataCount(b *testing.B) { // benchmark used to evaluate methods for iterating the series
+ seriesCount := 100
+ seriesList := make(LineSeriesList, seriesCount)
+ for i := 0; i < seriesCount; i++ {
+ seriesList[i] = LineSeries{
+ Values: make([]float64, i),
+ }
+ }
+
+ for i := 0; i < b.N; i++ {
+ _ = getSeriesMaxDataCount(seriesList)
+ }
+}