Skip to content

Commit

Permalink
Minor bug fixes and code style changes
Browse files Browse the repository at this point in the history
This commit introduces some style changes independent of an upcoming feature PR.
In addition a few minor bugs were found and fixed:
  * Bar and Line ValueFormatter can now be used for the MarkLine and MarkPoint.
  * Horizontal bar Value Labels sometimes were the wrong font color, this logic is now fixed.
  * Inequal SeriesList sizes will now result in an error instead of a panic
  * Minor doc and code comment improvements
  * Struct initialization improvements
  • Loading branch information
jentfoo committed Jan 27, 2025
1 parent f831577 commit de83cf6
Show file tree
Hide file tree
Showing 11 changed files with 217 additions and 193 deletions.
122 changes: 58 additions & 64 deletions bar_chart.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ type BarChartOption struct {
BarWidth int
// BarMargin specifies the margin between bars grouped together. BarWidth takes priority over the margin.
BarMargin *float64
// RoundedBarCaps set to `true` to produce a bar graph where the bars have rounded tops.
// RoundedBarCaps set to *true to produce a bar graph where the bars have rounded tops.
RoundedBarCaps *bool
// ValueFormatter defines how float values should be rendered to strings, notably for numeric axis labels.
ValueFormatter ValueFormatter
Expand Down Expand Up @@ -106,110 +106,104 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B
if seriesCount == 0 {
return BoxZero, errors.New("empty series list")
}
margin, barMargin, barWidth := calculateBarMarginsAndSize(seriesCount, width, opt.BarWidth, opt.BarMargin)
barMaxHeight := seriesPainter.Height()
barMaxHeight := seriesPainter.Height() // total vertical space for bars
seriesNames := seriesList.Names()
divideValues := xRange.AutoDivide()
margin, barMargin, barWidth := calculateBarMarginsAndSize(seriesCount, width, opt.BarWidth, opt.BarMargin)

markPointPainter := newMarkPointPainter(seriesPainter)
markLinePainter := newMarkLinePainter(seriesPainter)
rendererList := []renderer{
markPointPainter,
markLinePainter,
}
for index := range seriesList {
series := seriesList[index]
// render list must start with the markPointPainter, as it can influence label painters (if enabled)
rendererList := []renderer{markPointPainter, markLinePainter}

for index, series := range seriesList {
yRange := result.axisRanges[series.YAxisIndex]
seriesColor := opt.Theme.GetSeriesColor(series.index)

divideValues := xRange.AutoDivide()
points := make([]Point, len(series.Data))
var labelPainter *seriesLabelPainter
if flagIs(true, series.Label.Show) {
labelPainter = newSeriesLabelPainter(seriesPainter, seriesNames, series.Label, opt.Theme, opt.Font)
rendererList = append(rendererList, labelPainter)
}

points := make([]Point, len(series.Data)) // used for mark points
for j, item := range series.Data {
if j >= xRange.divideCount {
continue
}
x := divideValues[j]
x += margin
if index != 0 {
x += index * (barWidth + barMargin)
}

var x, top, bottom int
h := yRange.getHeight(item)
top := barMaxHeight - h
x = divideValues[j] + margin + index*(barWidth+barMargin)
top = barMaxHeight - h
bottom = barMaxHeight - 1

if flagIs(true, opt.RoundedBarCaps) {
seriesPainter.roundedRect(Box{
Top: top,
Left: x,
Right: x + barWidth,
Bottom: barMaxHeight - 1,
IsSet: true,
}, barWidth, true, false,
seriesPainter.roundedRect(
Box{Top: top, Left: x, Right: x + barWidth, Bottom: bottom, IsSet: true},
barWidth, true, false,
seriesColor, seriesColor, 0.0)
} else {
seriesPainter.FilledRect(x, top, x+barWidth, barMaxHeight-1,
seriesColor, seriesColor, 0.0)
seriesPainter.FilledRect(x, top, x+barWidth, bottom, seriesColor, seriesColor, 0.0)
}
// generate marker point by hand

// Prepare point for mark points
points[j] = Point{
X: x + (barWidth >> 1), // centered position
Y: top,
}
// return if the label does not need to be displayed
if labelPainter == nil {
continue
X: x + (barWidth >> 1), // center of the bar horizontally
Y: top, // top of bar
}
y := barMaxHeight - h
radians := float64(0)
fontStyle := series.Label.FontStyle
if series.Label.Position == PositionBottom {
y = barMaxHeight
radians = -math.Pi / 2
if fontStyle.FontColor.IsZero() {

if labelPainter != nil {
labelY := top
radians := float64(0)
fontStyle := series.Label.FontStyle
if series.Label.Position == PositionBottom {
labelY = barMaxHeight
radians = -math.Pi / 2 // Rotated label at the bottom
}
if series.Label.Position == PositionBottom && fontStyle.FontColor.IsZero() {
if isLightColor(seriesColor) {
fontStyle.FontColor = defaultLightFontColor
} else {
fontStyle.FontColor = defaultDarkFontColor
}
}

labelPainter.Add(labelValue{
vertical: true, // label is vertically oriented
index: index,
value: item,
fontStyle: fontStyle,
x: x + (barWidth >> 1),
y: labelY,
radians: radians,
offset: series.Label.Offset,
})
}
labelPainter.Add(labelValue{
vertical: true, // label is above bar
index: index,
value: item,
fontStyle: fontStyle,
x: x + (barWidth >> 1),
y: y,
radians: radians,
offset: series.Label.Offset,
})
}

markPointPainter.Add(markPointRenderOption{
FillColor: seriesColor,
Font: opt.Font,
Series: series,
Points: points,
})
markLinePainter.Add(markLineRenderOption{
FillColor: seriesColor,
FontColor: opt.Theme.GetTextColor(),
StrokeColor: seriesColor,
Font: opt.Font,
Series: series,
Range: yRange,
fillColor: seriesColor,
fontColor: opt.Theme.GetTextColor(),
strokeColor: seriesColor,
font: opt.Font,
series: series,
axisRange: yRange,
valueFormatter: opt.ValueFormatter,
})
markPointPainter.Add(markPointRenderOption{
fillColor: seriesColor,
font: opt.Font,
series: series,
points: points,
valueFormatter: opt.ValueFormatter,
seriesLabelPainter: labelPainter,
})
}
// the largest and smallest mark point

if err := doRender(rendererList...); err != nil {
return BoxZero, err
}

return p.box, nil
}

Expand Down
75 changes: 41 additions & 34 deletions horizontal_bar_chart.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,67 +68,74 @@ func (h *horizontalBarChart) render(result *defaultRenderResult, seriesList Seri
if seriesCount == 0 {
return BoxZero, errors.New("empty series list")
}
min, max := seriesList.GetMinMax(0)
margin, barMargin, barHeight := calculateBarMarginsAndSize(seriesCount, height, opt.BarHeight, opt.BarMargin)
seriesNames := seriesList.Names()

min, max := seriesList.GetMinMax(0)
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)
divideValues := yRange.AutoDivide()

var rendererList []renderer
for index := range seriesList {
series := seriesList[index]
seriesColor := opt.Theme.GetSeriesColor(series.index)
divideValues := yRange.AutoDivide()

var labelPainter *seriesLabelPainter
if flagIs(true, series.Label.Show) {
labelPainter = newSeriesLabelPainter(seriesPainter, seriesNames, series.Label, opt.Theme, opt.Font)
rendererList = append(rendererList, labelPainter)
}

for j, item := range series.Data {
if j >= yRange.divideCount {
continue
}
// display position switch
j = yRange.divideCount - j - 1
y := divideValues[j]
y += margin
// Reverse the category index for drawing from top to bottom
reversedJ := yRange.divideCount - j - 1

// Compute the top of this bar “row”
y := divideValues[reversedJ] + margin

// Determine the width (horizontal length) of the bar based on the data value
w := xRange.getHeight(item)

// Offset each series in its own lane
if index != 0 {
y += index * (barHeight + barMargin)
}

w := xRange.getHeight(item)
fillColor := seriesColor
right := w
seriesPainter.FilledRect(0, y, right, y+barHeight, fillColor, fillColor, 0.0)
// if the label does not need to be displayed, return
if labelPainter == nil {
continue
}
fontStyle := series.Label.FontStyle
if fontStyle.FontColor.IsZero() {
if isLightColor(fillColor) {
fontStyle.FontColor = defaultLightFontColor
} else {
fontStyle.FontColor = defaultDarkFontColor
seriesPainter.FilledRect(0, y, w, y+barHeight, seriesColor, seriesColor, 0.0)

if labelPainter != nil {
labelX := w
if series.Label.Position == PositionLeft {
labelX = 0
}
labelY := y + (barHeight >> 1)
fontStyle := series.Label.FontStyle
if series.Label.Position == PositionLeft && fontStyle.FontColor.IsZero() {
if isLightColor(seriesColor) {
fontStyle.FontColor = defaultLightFontColor
} else {
fontStyle.FontColor = defaultDarkFontColor
}
}

labelPainter.Add(labelValue{
vertical: false, // horizontal label
index: index,
value: item,
x: labelX,
y: labelY,
offset: series.Label.Offset,
fontStyle: fontStyle,
})
}
labelValue := labelValue{
vertical: false, // label beside bar
index: index,
value: item,
x: right,
y: y + (barHeight >> 1),
offset: series.Label.Offset,
fontStyle: fontStyle,
}
if series.Label.Position == PositionLeft {
labelValue.x = 0
}
labelPainter.Add(labelValue)
}
}

if err := doRender(rendererList...); err != nil {
return BoxZero, err
}
Expand Down
Loading

0 comments on commit de83cf6

Please sign in to comment.