diff --git a/_examples/legend_left/main.go b/_examples/legend_left/main.go new file mode 100644 index 0000000..b54673c --- /dev/null +++ b/_examples/legend_left/main.go @@ -0,0 +1,111 @@ +package main + +import ( + "net/http" + + "github.com/wcharczuk/go-chart" +) + +func drawChart(res http.ResponseWriter, req *http.Request) { + + /* + In this example we add a `Renderable` or a custom component to the `Elements` array. + In this specific case it is a pre-built renderable (`CreateLegend`) that draws a legend for the chart's series. + If you like, you can use `CreateLegend` as a template for writing your own renderable, or even your own legend. + */ + + graph := chart.Chart{ + XAxis: chart.XAxis{ + Style: chart.Style{Show: true}, + }, + YAxis: chart.YAxis{ + Style: chart.Style{Show: true}, + }, + Background: chart.Style{ + Padding: chart.Box{ + Top: 20, + Left: 260, + }, + }, + Series: []chart.Series{ + chart.ContinuousSeries{ + Name: "A test series", + XValues: []float64{1.0, 2.0, 3.0, 4.0, 5.0}, + YValues: []float64{1.0, 2.0, 3.0, 4.0, 5.0}, + }, + + chart.ContinuousSeries{ + Name: "Another test series", + XValues: []float64{1.0, 2.0, 3.0, 4.0, 5.0}, + YValues: []float64{1.0, 2.0, 3.0, 4.0, 5.0}, + }, + + chart.ContinuousSeries{ + Name: "Yet Another test series", + XValues: []float64{1.0, 2.0, 3.0, 4.0, 5.0}, + YValues: []float64{1.0, 2.0, 3.0, 4.0, 5.0}, + }, + + chart.ContinuousSeries{ + Name: "Even More series", + XValues: []float64{1.0, 2.0, 3.0, 4.0, 5.0}, + YValues: []float64{1.0, 2.0, 3.0, 4.0, 5.0}, + }, + + chart.ContinuousSeries{ + Name: "Foo Bar", + XValues: []float64{1.0, 2.0, 3.0, 4.0, 5.0}, + YValues: []float64{1.0, 2.0, 3.0, 4.0, 5.0}, + }, + + chart.ContinuousSeries{ + Name: "Bar Baz", + XValues: []float64{1.0, 2.0, 3.0, 4.0, 5.0}, + YValues: []float64{1.0, 2.0, 3.0, 4.0, 5.0}, + }, + + chart.ContinuousSeries{ + Name: "Free Yourself", + XValues: []float64{1.0, 2.0, 3.0, 4.0, 5.0}, + YValues: []float64{1.0, 2.0, 3.0, 4.0, 5.0}, + }, + + chart.ContinuousSeries{ + Name: "Wheres Ja?", + XValues: []float64{1.0, 2.0, 3.0, 4.0, 5.0}, + YValues: []float64{1.0, 2.0, 3.0, 4.0, 5.0}, + }, + + chart.ContinuousSeries{ + Name: "Fast and the Furious", + XValues: []float64{1.0, 2.0, 3.0, 4.0, 5.0}, + YValues: []float64{5.0, 4.0, 3.0, 2.0, 1.0}, + }, + + chart.ContinuousSeries{ + Name: "2 Fast 2 Furious", + XValues: []float64{1.0, 2.0, 3.0, 4.0, 5.0}, + YValues: []float64{5.0, 4.0, 3.0, 2.0, 1.0}, + }, + + chart.ContinuousSeries{ + Name: "They only get more fast and more furious", + XValues: []float64{1.0, 2.0, 3.0, 4.0, 5.0}, + YValues: []float64{5.0, 4.0, 3.0, 2.0, 1.0}, + }, + }, + } + + //note we have to do this as a separate step because we need a reference to graph + graph.Elements = []chart.Renderable{ + chart.LegendLeft(&graph), + } + + res.Header().Set("Content-Type", "image/png") + graph.Render(chart.PNG, res) +} + +func main() { + http.HandleFunc("/", drawChart) + http.ListenAndServe(":8080", nil) +} diff --git a/legend.go b/legend.go index 20d4a04..4b2037b 100644 --- a/legend.go +++ b/legend.go @@ -213,3 +213,117 @@ func LegendThin(c *Chart, userDefaults ...Style) Renderable { } } } + +// 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, + } + + var legendStyle Style + if len(userDefaults) > 0 { + legendStyle = userDefaults[0].InheritFrom(chartDefaults.InheritFrom(legendDefaults)) + } else { + legendStyle = chartDefaults.InheritFrom(legendDefaults) + } + + // DEFAULTS + legendPadding := Box{ + Top: 5, + Left: 5, + Right: 5, + Bottom: 5, + } + lineTextGap := 5 + lineLengthMinimum := 25 + + var labels []string + var lines []Style + for index, s := range c.Series { + if s.GetStyle().IsZero() || s.GetStyle().Show { + 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 + legendPadding.Top, + Left: legend.Left + legendPadding.Left, + Right: legend.Left + legendPadding.Left, + Bottom: legend.Top + legendPadding.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 += DefaultMinimumTickVerticalSpacing + } + legendContent.Bottom += tb.Height() + right := legendContent.Left + tb.Width() + lineTextGap + lineLengthMinimum + legendContent.Right = Math.MaxInt(legendContent.Right, right) + labelCount++ + } + } + + legend = legend.Grow(legendContent) + legend.Right = legendContent.Right + legendPadding.Right + legend.Bottom = legendContent.Bottom + legendPadding.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 += 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 + lx2 := legendContent.Right - legendPadding.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++ + } + } + } +}