6eff5912ca
Legends need to use their individual text spacing - not use the Tick spacing. For vertical lists it looks a lot better to use a spacing of 10 instead of 20. Prior to this commit this couldn't be set in the defaults without also manipulating the axis tick spacing. Using their own constant, this can be now easily configured.
450 lines
11 KiB
Go
450 lines
11 KiB
Go
package chart
|
|
|
|
import (
|
|
"github.com/wcharczuk/go-chart/v2/drawing"
|
|
)
|
|
|
|
// Legend returns a legend renderable function.
|
|
func Legend(c *Chart, userDefaults ...Style) Renderable {
|
|
return func(r Renderer, cb Box, chartDefaults Style) {
|
|
legendDefaults := Style{
|
|
FillColor: drawing.ColorWhite,
|
|
FontColor: DefaultTextColor,
|
|
FontSize: 8.0,
|
|
StrokeColor: DefaultAxisColor,
|
|
StrokeWidth: DefaultAxisLineWidth,
|
|
Padding: Box{
|
|
Top: 5,
|
|
Left: 5,
|
|
Right: 5,
|
|
Bottom: 5,
|
|
},
|
|
}
|
|
|
|
var legendStyle Style
|
|
if len(userDefaults) > 0 {
|
|
legendStyle = userDefaults[0].InheritFrom(chartDefaults.InheritFrom(legendDefaults))
|
|
} else {
|
|
legendStyle = chartDefaults.InheritFrom(legendDefaults)
|
|
}
|
|
|
|
// DEFAULTS
|
|
lineTextGap := 5
|
|
lineLengthMinimum := 25
|
|
|
|
var labels []string
|
|
var lines []Style
|
|
for index, s := range c.Series {
|
|
if !s.GetStyle().Hidden {
|
|
if _, isAnnotationSeries := s.(AnnotationSeries); !isAnnotationSeries {
|
|
labels = append(labels, s.GetName())
|
|
lines = append(lines, s.GetStyle().InheritFrom(c.styleDefaultsSeries(index)))
|
|
}
|
|
}
|
|
}
|
|
|
|
legend := Box{
|
|
Top: cb.Top,
|
|
Left: cb.Left,
|
|
// bottom and right will be sized by the legend content + relevant padding.
|
|
}
|
|
|
|
legendContent := Box{
|
|
Top: legend.Top + legendStyle.Padding.Top,
|
|
Left: legend.Left + legendStyle.Padding.Left,
|
|
Right: legend.Left + legendStyle.Padding.Left,
|
|
Bottom: legend.Top + legendStyle.Padding.Top,
|
|
}
|
|
|
|
legendStyle.GetTextOptions().WriteToRenderer(r)
|
|
|
|
// measure
|
|
labelCount := 0
|
|
for x := 0; x < len(labels); x++ {
|
|
if len(labels[x]) > 0 {
|
|
tb := r.MeasureText(labels[x])
|
|
if labelCount > 0 {
|
|
legendContent.Bottom += DefaultLegendVerticalSpacing
|
|
}
|
|
legendContent.Bottom += tb.Height()
|
|
right := legendContent.Left + tb.Width() + lineTextGap + lineLengthMinimum
|
|
legendContent.Right = MaxInt(legendContent.Right, right)
|
|
labelCount++
|
|
}
|
|
}
|
|
|
|
legend = legend.Grow(legendContent)
|
|
legend.Right = legendContent.Right + legendStyle.Padding.Right
|
|
legend.Bottom = legendContent.Bottom + legendStyle.Padding.Bottom
|
|
|
|
Draw.Box(r, legend, legendStyle)
|
|
|
|
legendStyle.GetTextOptions().WriteToRenderer(r)
|
|
|
|
ycursor := legendContent.Top
|
|
tx := legendContent.Left
|
|
legendCount := 0
|
|
var label string
|
|
for x := 0; x < len(labels); x++ {
|
|
label = labels[x]
|
|
if len(label) > 0 {
|
|
if legendCount > 0 {
|
|
ycursor += DefaultLegendVerticalSpacing
|
|
}
|
|
|
|
tb := r.MeasureText(label)
|
|
|
|
ty := ycursor + tb.Height()
|
|
r.Text(label, tx, ty)
|
|
|
|
th2 := tb.Height() >> 1
|
|
|
|
lx := tx + tb.Width() + lineTextGap
|
|
ly := ty - th2
|
|
lx2 := legendContent.Right - legendStyle.Padding.Right
|
|
|
|
r.SetStrokeColor(lines[x].GetStrokeColor())
|
|
r.SetStrokeWidth(lines[x].GetStrokeWidth())
|
|
r.SetStrokeDashArray(lines[x].GetStrokeDashArray())
|
|
|
|
r.MoveTo(lx, ly)
|
|
r.LineTo(lx2, ly)
|
|
r.Stroke()
|
|
|
|
ycursor += tb.Height()
|
|
legendCount++
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// LegendLineLeft is a legend with the line drawn left to the legend text.
|
|
func LegendLineLeft(c *Chart, userDefaults ...Style) Renderable {
|
|
return func(r Renderer, cb Box, chartDefaults Style) {
|
|
legendDefaults := Style{
|
|
FillColor: drawing.ColorWhite,
|
|
FontColor: DefaultTextColor,
|
|
FontSize: 8.0,
|
|
StrokeColor: DefaultAxisColor,
|
|
StrokeWidth: DefaultAxisLineWidth,
|
|
Padding: Box{
|
|
Top: 5,
|
|
Left: 5,
|
|
Right: 5,
|
|
Bottom: 5,
|
|
},
|
|
}
|
|
|
|
var legendStyle Style
|
|
if len(userDefaults) > 0 {
|
|
legendStyle = userDefaults[0].InheritFrom(chartDefaults.InheritFrom(legendDefaults))
|
|
} else {
|
|
legendStyle = chartDefaults.InheritFrom(legendDefaults)
|
|
}
|
|
|
|
// DEFAULTS
|
|
lineTextGap := 5
|
|
lineLengthMinimum := 25
|
|
strokeLength := 17
|
|
|
|
var labels []string
|
|
var lines []Style
|
|
for index, s := range c.Series {
|
|
if !s.GetStyle().Hidden {
|
|
if _, isAnnotationSeries := s.(AnnotationSeries); !isAnnotationSeries {
|
|
labels = append(labels, s.GetName())
|
|
lines = append(lines, s.GetStyle().InheritFrom(c.styleDefaultsSeries(index)))
|
|
}
|
|
}
|
|
}
|
|
|
|
legend := Box{
|
|
Top: cb.Top,
|
|
Left: cb.Left,
|
|
// bottom and right will be sized by the legend content + relevant padding.
|
|
}
|
|
|
|
legendContent := Box{
|
|
Top: legend.Top + legendStyle.Padding.Top,
|
|
Left: legend.Left + legendStyle.Padding.Left,
|
|
Right: legend.Left + legendStyle.Padding.Left,
|
|
Bottom: legend.Top + legendStyle.Padding.Top,
|
|
}
|
|
|
|
legendStyle.GetTextOptions().WriteToRenderer(r)
|
|
|
|
// measure
|
|
labelCount := 0
|
|
for x := 0; x < len(labels); x++ {
|
|
if len(labels[x]) > 0 {
|
|
tb := r.MeasureText(labels[x])
|
|
if labelCount > 0 {
|
|
legendContent.Bottom += DefaultLegendVerticalSpacing
|
|
}
|
|
legendContent.Bottom += tb.Height()
|
|
right := legendContent.Left + tb.Width() + lineTextGap + lineLengthMinimum
|
|
legendContent.Right = MaxInt(legendContent.Right, right)
|
|
labelCount++
|
|
}
|
|
}
|
|
|
|
legend = legend.Grow(legendContent)
|
|
legend.Right = legendContent.Right + legendStyle.Padding.Right
|
|
legend.Bottom = legendContent.Bottom + legendStyle.Padding.Bottom
|
|
|
|
Draw.Box(r, legend, legendStyle)
|
|
|
|
legendStyle.GetTextOptions().WriteToRenderer(r)
|
|
|
|
ycursor := legendContent.Top
|
|
lx := legendContent.Left
|
|
legendCount := 0
|
|
var label string
|
|
for x := 0; x < len(labels); x++ {
|
|
label = labels[x]
|
|
if len(label) > 0 {
|
|
if legendCount > 0 {
|
|
ycursor += DefaultLegendVerticalSpacing
|
|
}
|
|
|
|
// Calculate text dimensions
|
|
tb := r.MeasureText(label)
|
|
ty := ycursor + tb.Height()
|
|
th2 := tb.Height() >> 1
|
|
|
|
// Calculate line x and y coordinates
|
|
ly := ty - th2
|
|
|
|
// Calculate line ending x coordinate
|
|
lx2 := lx + strokeLength
|
|
|
|
r.SetStrokeColor(lines[x].GetStrokeColor())
|
|
r.SetStrokeWidth(lines[x].GetStrokeWidth())
|
|
r.SetStrokeDashArray(lines[x].GetStrokeDashArray())
|
|
|
|
r.MoveTo(lx, ly)
|
|
r.LineTo(lx2, ly)
|
|
r.Stroke()
|
|
|
|
// Calculate Text starting coordinates
|
|
textX := lx2 + lineTextGap
|
|
r.Text(label, textX, ty)
|
|
|
|
ycursor += tb.Height()
|
|
legendCount++
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// LegendThin is a legend that doesn't obscure the chart area.
|
|
func LegendThin(c *Chart, userDefaults ...Style) Renderable {
|
|
return func(r Renderer, cb Box, chartDefaults Style) {
|
|
legendDefaults := Style{
|
|
FillColor: drawing.ColorWhite,
|
|
FontColor: DefaultTextColor,
|
|
FontSize: 8.0,
|
|
StrokeColor: DefaultAxisColor,
|
|
StrokeWidth: DefaultAxisLineWidth,
|
|
Padding: Box{
|
|
Top: 2,
|
|
Left: 7,
|
|
Right: 7,
|
|
Bottom: 5,
|
|
},
|
|
}
|
|
|
|
var legendStyle Style
|
|
if len(userDefaults) > 0 {
|
|
legendStyle = userDefaults[0].InheritFrom(chartDefaults.InheritFrom(legendDefaults))
|
|
} else {
|
|
legendStyle = chartDefaults.InheritFrom(legendDefaults)
|
|
}
|
|
|
|
r.SetFont(legendStyle.GetFont())
|
|
r.SetFontColor(legendStyle.GetFontColor())
|
|
r.SetFontSize(legendStyle.GetFontSize())
|
|
|
|
var labels []string
|
|
var lines []Style
|
|
for index, s := range c.Series {
|
|
if !s.GetStyle().Hidden {
|
|
if _, isAnnotationSeries := s.(AnnotationSeries); !isAnnotationSeries {
|
|
labels = append(labels, s.GetName())
|
|
lines = append(lines, s.GetStyle().InheritFrom(c.styleDefaultsSeries(index)))
|
|
}
|
|
}
|
|
}
|
|
|
|
var textHeight int
|
|
var textWidth int
|
|
var textBox Box
|
|
for x := 0; x < len(labels); x++ {
|
|
if len(labels[x]) > 0 {
|
|
textBox = r.MeasureText(labels[x])
|
|
textHeight = MaxInt(textBox.Height(), textHeight)
|
|
textWidth = MaxInt(textBox.Width(), textWidth)
|
|
}
|
|
}
|
|
|
|
legendBoxHeight := textHeight + legendStyle.Padding.Top + legendStyle.Padding.Bottom
|
|
chartPadding := cb.Top
|
|
legendYMargin := (chartPadding - legendBoxHeight) >> 1
|
|
|
|
legendBox := Box{
|
|
Left: cb.Left,
|
|
Right: cb.Right,
|
|
Top: legendYMargin,
|
|
Bottom: legendYMargin + legendBoxHeight,
|
|
}
|
|
|
|
Draw.Box(r, legendBox, legendStyle)
|
|
|
|
r.SetFont(legendStyle.GetFont())
|
|
r.SetFontColor(legendStyle.GetFontColor())
|
|
r.SetFontSize(legendStyle.GetFontSize())
|
|
|
|
lineTextGap := 5
|
|
lineLengthMinimum := 25
|
|
|
|
tx := legendBox.Left + legendStyle.Padding.Left
|
|
ty := legendYMargin + legendStyle.Padding.Top + textHeight
|
|
var label string
|
|
var lx, ly int
|
|
th2 := textHeight >> 1
|
|
for index := range labels {
|
|
label = labels[index]
|
|
if len(label) > 0 {
|
|
textBox = r.MeasureText(label)
|
|
r.Text(label, tx, ty)
|
|
|
|
lx = tx + textBox.Width() + lineTextGap
|
|
ly = ty - th2
|
|
|
|
r.SetStrokeColor(lines[index].GetStrokeColor())
|
|
r.SetStrokeWidth(lines[index].GetStrokeWidth())
|
|
r.SetStrokeDashArray(lines[index].GetStrokeDashArray())
|
|
|
|
r.MoveTo(lx, ly)
|
|
r.LineTo(lx+lineLengthMinimum, ly)
|
|
r.Stroke()
|
|
|
|
tx += textBox.Width() + DefaultLegendHorizontalSpacing + lineTextGap + lineLengthMinimum
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// LegendLeft is a legend that is designed for longer series lists.
|
|
func LegendLeft(c *Chart, userDefaults ...Style) Renderable {
|
|
return func(r Renderer, cb Box, chartDefaults Style) {
|
|
legendDefaults := Style{
|
|
FillColor: drawing.ColorWhite,
|
|
FontColor: DefaultTextColor,
|
|
FontSize: 8.0,
|
|
StrokeColor: DefaultAxisColor,
|
|
StrokeWidth: DefaultAxisLineWidth,
|
|
Padding: Box{
|
|
Top: 5,
|
|
Left: 5,
|
|
Right: 5,
|
|
Bottom: 5,
|
|
},
|
|
}
|
|
|
|
var legendStyle Style
|
|
if len(userDefaults) > 0 {
|
|
legendStyle = userDefaults[0].InheritFrom(chartDefaults.InheritFrom(legendDefaults))
|
|
} else {
|
|
legendStyle = chartDefaults.InheritFrom(legendDefaults)
|
|
}
|
|
|
|
// DEFAULTS
|
|
lineTextGap := 5
|
|
lineLengthMinimum := 25
|
|
|
|
var labels []string
|
|
var lines []Style
|
|
for index, s := range c.Series {
|
|
if !s.GetStyle().Hidden {
|
|
if _, isAnnotationSeries := s.(AnnotationSeries); !isAnnotationSeries {
|
|
labels = append(labels, s.GetName())
|
|
lines = append(lines, s.GetStyle().InheritFrom(c.styleDefaultsSeries(index)))
|
|
}
|
|
}
|
|
}
|
|
|
|
legend := Box{
|
|
Top: 5,
|
|
Left: 5,
|
|
// bottom and right will be sized by the legend content + relevant padding.
|
|
}
|
|
|
|
legendContent := Box{
|
|
Top: legend.Top + legendStyle.Padding.Top,
|
|
Left: legend.Left + legendStyle.Padding.Left,
|
|
Right: legend.Left + legendStyle.Padding.Left,
|
|
Bottom: legend.Top + legendStyle.Padding.Top,
|
|
}
|
|
|
|
legendStyle.GetTextOptions().WriteToRenderer(r)
|
|
|
|
// measure
|
|
labelCount := 0
|
|
for x := 0; x < len(labels); x++ {
|
|
if len(labels[x]) > 0 {
|
|
tb := r.MeasureText(labels[x])
|
|
if labelCount > 0 {
|
|
legendContent.Bottom += DefaultLegendVerticalSpacing
|
|
}
|
|
legendContent.Bottom += tb.Height()
|
|
right := legendContent.Left + tb.Width() + lineTextGap + lineLengthMinimum
|
|
legendContent.Right = MaxInt(legendContent.Right, right)
|
|
labelCount++
|
|
}
|
|
}
|
|
|
|
legend = legend.Grow(legendContent)
|
|
legend.Right = legendContent.Right + legendStyle.Padding.Right
|
|
legend.Bottom = legendContent.Bottom + legendStyle.Padding.Bottom
|
|
|
|
Draw.Box(r, legend, legendStyle)
|
|
|
|
legendStyle.GetTextOptions().WriteToRenderer(r)
|
|
|
|
ycursor := legendContent.Top
|
|
tx := legendContent.Left
|
|
legendCount := 0
|
|
var label string
|
|
for x := 0; x < len(labels); x++ {
|
|
label = labels[x]
|
|
if len(label) > 0 {
|
|
if legendCount > 0 {
|
|
ycursor += DefaultLegendVerticalSpacing
|
|
}
|
|
|
|
tb := r.MeasureText(label)
|
|
|
|
ty := ycursor + tb.Height()
|
|
r.Text(label, tx, ty)
|
|
|
|
th2 := tb.Height() >> 1
|
|
|
|
lx := tx + tb.Width() + lineTextGap
|
|
ly := ty - th2
|
|
lx2 := legendContent.Right - legendStyle.Padding.Right
|
|
|
|
r.SetStrokeColor(lines[x].GetStrokeColor())
|
|
r.SetStrokeWidth(lines[x].GetStrokeWidth())
|
|
r.SetStrokeDashArray(lines[x].GetStrokeDashArray())
|
|
|
|
r.MoveTo(lx, ly)
|
|
r.LineTo(lx2, ly)
|
|
r.Stroke()
|
|
|
|
ycursor += tb.Height()
|
|
legendCount++
|
|
}
|
|
}
|
|
}
|
|
}
|