2016-07-29 19:36:29 -04:00
|
|
|
package chart
|
|
|
|
|
2017-05-12 20:12:23 -04:00
|
|
|
import (
|
2020-11-22 19:45:10 -05:00
|
|
|
"github.com/wcharczuk/go-chart/v2/drawing"
|
2017-05-12 20:12:23 -04:00
|
|
|
)
|
2016-07-29 19:36:29 -04:00
|
|
|
|
|
|
|
// 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,
|
2022-02-07 21:51:14 -05:00
|
|
|
Padding: Box{
|
|
|
|
Top: 5,
|
|
|
|
Left: 5,
|
|
|
|
Right: 5,
|
|
|
|
Bottom: 5,
|
|
|
|
},
|
2016-07-29 19:36:29 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
2019-02-13 21:55:13 -05:00
|
|
|
if !s.GetStyle().Hidden {
|
2016-07-29 19:36:29 -04:00
|
|
|
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{
|
2022-02-07 21:51:14 -05:00
|
|
|
Top: legend.Top + legendStyle.Padding.Top,
|
|
|
|
Left: legend.Left + legendStyle.Padding.Left,
|
|
|
|
Right: legend.Left + legendStyle.Padding.Left,
|
|
|
|
Bottom: legend.Top + legendStyle.Padding.Top,
|
2016-07-29 19:36:29 -04:00
|
|
|
}
|
|
|
|
|
2016-10-21 15:44:37 -04:00
|
|
|
legendStyle.GetTextOptions().WriteToRenderer(r)
|
2016-07-29 19:36:29 -04:00
|
|
|
|
|
|
|
// 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 += DefaultMinimumTickVerticalSpacing
|
|
|
|
}
|
|
|
|
legendContent.Bottom += tb.Height()
|
|
|
|
right := legendContent.Left + tb.Width() + lineTextGap + lineLengthMinimum
|
2019-02-13 19:09:26 -05:00
|
|
|
legendContent.Right = MaxInt(legendContent.Right, right)
|
2016-07-29 19:36:29 -04:00
|
|
|
labelCount++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
legend = legend.Grow(legendContent)
|
2022-02-07 21:51:14 -05:00
|
|
|
legend.Right = legendContent.Right + legendStyle.Padding.Right
|
|
|
|
legend.Bottom = legendContent.Bottom + legendStyle.Padding.Bottom
|
2016-07-29 19:36:29 -04:00
|
|
|
|
|
|
|
Draw.Box(r, legend, legendStyle)
|
|
|
|
|
2016-10-21 15:44:37 -04:00
|
|
|
legendStyle.GetTextOptions().WriteToRenderer(r)
|
|
|
|
|
2016-07-29 19:36:29 -04:00
|
|
|
ycursor := legendContent.Top
|
|
|
|
tx := legendContent.Left
|
|
|
|
legendCount := 0
|
2016-08-27 16:45:38 -04:00
|
|
|
var label string
|
2016-07-29 19:36:29 -04:00
|
|
|
for x := 0; x < len(labels); x++ {
|
2016-08-27 16:45:38 -04:00
|
|
|
label = labels[x]
|
|
|
|
if len(label) > 0 {
|
2016-07-29 19:36:29 -04:00
|
|
|
if legendCount > 0 {
|
|
|
|
ycursor += DefaultMinimumTickVerticalSpacing
|
|
|
|
}
|
|
|
|
|
2016-08-27 16:45:38 -04:00
|
|
|
tb := r.MeasureText(label)
|
2016-07-29 19:36:29 -04:00
|
|
|
|
|
|
|
ty := ycursor + tb.Height()
|
2016-08-27 16:45:38 -04:00
|
|
|
r.Text(label, tx, ty)
|
2016-07-29 19:36:29 -04:00
|
|
|
|
|
|
|
th2 := tb.Height() >> 1
|
|
|
|
|
|
|
|
lx := tx + tb.Width() + lineTextGap
|
|
|
|
ly := ty - th2
|
2022-02-07 21:51:14 -05:00
|
|
|
lx2 := legendContent.Right - legendStyle.Padding.Right
|
2016-07-29 19:36:29 -04:00
|
|
|
|
|
|
|
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++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-08-27 16:45:38 -04:00
|
|
|
|
2022-02-05 11:07:04 -05:00
|
|
|
// 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,
|
2022-02-07 21:10:53 -05:00
|
|
|
Padding: Box{
|
|
|
|
Top: 5,
|
|
|
|
Left: 5,
|
|
|
|
Right: 5,
|
|
|
|
Bottom: 5,
|
|
|
|
},
|
2022-02-05 11:07:04 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
var legendStyle Style
|
|
|
|
if len(userDefaults) > 0 {
|
|
|
|
legendStyle = userDefaults[0].InheritFrom(chartDefaults.InheritFrom(legendDefaults))
|
|
|
|
} else {
|
|
|
|
legendStyle = chartDefaults.InheritFrom(legendDefaults)
|
|
|
|
}
|
|
|
|
|
|
|
|
// DEFAULTS
|
|
|
|
lineTextGap := 5
|
|
|
|
lineLengthMinimum := 25
|
2022-02-07 21:47:05 -05:00
|
|
|
strokeLength := 17
|
2022-02-05 11:07:04 -05:00
|
|
|
|
|
|
|
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{
|
2022-02-07 21:10:53 -05:00
|
|
|
Top: legend.Top + legendStyle.Padding.Top,
|
|
|
|
Left: legend.Left + legendStyle.Padding.Left,
|
|
|
|
Right: legend.Left + legendStyle.Padding.Left,
|
|
|
|
Bottom: legend.Top + legendStyle.Padding.Top,
|
2022-02-05 11:07:04 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
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 += DefaultMinimumTickVerticalSpacing
|
|
|
|
}
|
|
|
|
legendContent.Bottom += tb.Height()
|
|
|
|
right := legendContent.Left + tb.Width() + lineTextGap + lineLengthMinimum
|
|
|
|
legendContent.Right = MaxInt(legendContent.Right, right)
|
|
|
|
labelCount++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
legend = legend.Grow(legendContent)
|
2022-02-07 21:10:53 -05:00
|
|
|
legend.Right = legendContent.Right + legendStyle.Padding.Right
|
|
|
|
legend.Bottom = legendContent.Bottom + legendStyle.Padding.Bottom
|
2022-02-05 11:07:04 -05:00
|
|
|
|
|
|
|
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 += DefaultMinimumTickVerticalSpacing
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
2022-02-07 21:47:05 -05:00
|
|
|
lx2 := lx + strokeLength
|
2022-02-05 11:07:04 -05:00
|
|
|
|
|
|
|
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++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-27 16:45:38 -04:00
|
|
|
// 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 {
|
2019-02-13 21:55:13 -05:00
|
|
|
if !s.GetStyle().Hidden {
|
2016-08-27 16:45:38 -04:00
|
|
|
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])
|
2019-02-13 19:09:26 -05:00
|
|
|
textHeight = MaxInt(textBox.Height(), textHeight)
|
|
|
|
textWidth = MaxInt(textBox.Width(), textWidth)
|
2016-08-27 16:45:38 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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,
|
|
|
|
}
|
|
|
|
|
2022-02-07 21:51:14 -05:00
|
|
|
Draw.Box(r, legendBox, legendStyle)
|
2016-08-27 16:45:38 -04:00
|
|
|
|
|
|
|
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() + DefaultMinimumTickHorizontalSpacing + lineTextGap + lineLengthMinimum
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-02-13 03:27:02 -05:00
|
|
|
|
|
|
|
// 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,
|
2022-02-07 21:51:14 -05:00
|
|
|
Padding: Box{
|
|
|
|
Top: 5,
|
|
|
|
Left: 5,
|
|
|
|
Right: 5,
|
|
|
|
Bottom: 5,
|
|
|
|
},
|
2017-02-13 03:27:02 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
2019-02-13 21:55:13 -05:00
|
|
|
if !s.GetStyle().Hidden {
|
2017-02-13 03:27:02 -05:00
|
|
|
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{
|
2022-02-07 21:51:14 -05:00
|
|
|
Top: legend.Top + legendStyle.Padding.Top,
|
|
|
|
Left: legend.Left + legendStyle.Padding.Left,
|
|
|
|
Right: legend.Left + legendStyle.Padding.Left,
|
|
|
|
Bottom: legend.Top + legendStyle.Padding.Top,
|
2017-02-13 03:27:02 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
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 += DefaultMinimumTickVerticalSpacing
|
|
|
|
}
|
|
|
|
legendContent.Bottom += tb.Height()
|
|
|
|
right := legendContent.Left + tb.Width() + lineTextGap + lineLengthMinimum
|
2019-02-13 19:09:26 -05:00
|
|
|
legendContent.Right = MaxInt(legendContent.Right, right)
|
2017-02-13 03:27:02 -05:00
|
|
|
labelCount++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
legend = legend.Grow(legendContent)
|
2022-02-07 21:51:14 -05:00
|
|
|
legend.Right = legendContent.Right + legendStyle.Padding.Right
|
|
|
|
legend.Bottom = legendContent.Bottom + legendStyle.Padding.Bottom
|
2017-02-13 03:27:02 -05:00
|
|
|
|
|
|
|
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 += DefaultMinimumTickVerticalSpacing
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
2022-02-07 21:51:14 -05:00
|
|
|
lx2 := legendContent.Right - legendStyle.Padding.Right
|
2017-02-13 03:27:02 -05:00
|
|
|
|
|
|
|
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++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|