Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v0.5] - Series API change #45

Merged
merged 1 commit into from
Feb 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 15 additions & 16 deletions bar_chart.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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
}
Expand Down Expand Up @@ -230,15 +230,15 @@ 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,
fontColor: opt.Theme.GetTextColor(),
strokeColor: defaultGlobalMarkFillColor,
font: opt.Font,
markline: series.MarkLine,
seriesData: globalSeriesData,
seriesValues: globalSeriesData,
axisRange: yRange,
valueFormatterDefault: markLineValueFormatter,
})
Expand All @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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)
}
2 changes: 1 addition & 1 deletion bar_chart_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
7 changes: 3 additions & 4 deletions benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ func makeDefaultMultiChartOptions() ChartOption {
},
YAxis: []YAxisOption{
{

Min: Ptr(0.0),
Max: Ptr(90.0),
},
Expand All @@ -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{
Expand All @@ -50,7 +49,7 @@ func makeDefaultMultiChartOptions() ChartOption {
435.9, 354.3, 285.9, 204.5,
}, PieSeriesOption{
Radius: "35%",
}),
}).ToGenericSeriesList(),
},
},
}
Expand Down
18 changes: 9 additions & 9 deletions chart_option.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")
}
Expand Down Expand Up @@ -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...)
}
9 changes: 4 additions & 5 deletions chart_option_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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) {
Expand Down Expand Up @@ -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"},
},
Expand Down
48 changes: 22 additions & 26 deletions charts.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package charts
import (
"errors"
"math"
"sort"

"github.com/go-analyze/charts/chartdraw"
)
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
}
}

Expand All @@ -120,22 +116,22 @@ 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{}
for index, name := range opt.legend.SeriesNames {
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
Expand Down Expand Up @@ -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
}
Expand All @@ -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
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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 {
Expand Down
Loading