diff --git a/examples/stacked_bar/main.go b/examples/stacked_bar/main.go index 2bdbb9b..5096f61 100644 --- a/examples/stacked_bar/main.go +++ b/examples/stacked_bar/main.go @@ -13,8 +13,15 @@ func drawChart(res http.ResponseWriter, req *http.Request) { Background: chart.Style{ Padding: chart.Box{Top: 50, Left: 50, Right: 50, Bottom: 50}, }, + XAxis: chart.Style{ + Show: true, + }, + YAxis: chart.Style{ + Show: true, + }, Bars: []chart.StackedBar{ { + Name: "Funnel", Values: []chart.Value{ {Value: 5, Label: "Blue"}, {Value: 5, Label: "Green"}, @@ -26,6 +33,7 @@ func drawChart(res http.ResponseWriter, req *http.Request) { }, }, { + Name: "Test", Values: []chart.Value{ {Value: 10, Label: "Blue"}, {Value: 5, Label: "Green"}, diff --git a/stacked_bar_chart.go b/stacked_bar_chart.go index f15e21b..1fe22e2 100644 --- a/stacked_bar_chart.go +++ b/stacked_bar_chart.go @@ -2,6 +2,7 @@ package chart import ( "errors" + "fmt" "io" "github.com/golang/freetype/truetype" @@ -17,7 +18,7 @@ type StackedBar struct { // GetWidth returns the width of the bar. func (sb StackedBar) GetWidth() int { if sb.Width == 0 { - return 20 + return 50 } return sb.Width } @@ -34,6 +35,9 @@ type StackedBarChart struct { Background Style Canvas Style + XAxis Style + YAxis Style + BarSpacing int Font *truetype.Font @@ -108,6 +112,8 @@ func (sbc StackedBarChart) Render(rp RendererProvider, w io.Writer) error { canvasBox := sbc.getAdjustedCanvasBox(sbc.getDefaultCanvasBox()) sbc.drawBars(r, canvasBox) + sbc.drawXAxis(r, canvasBox) + sbc.drawYAxis(r, canvasBox) sbc.drawTitle(r) for _, a := range sbc.Elements { @@ -126,8 +132,8 @@ func (sbc StackedBarChart) drawBars(r Renderer, canvasBox Box) { } func (sbc StackedBarChart) drawBar(r Renderer, canvasBox Box, xoffset int, bar StackedBar) int { - bxl := xoffset - bxr := xoffset + bar.GetWidth() + bxl := xoffset + Math.AbsInt(bar.GetWidth()>>1-sbc.GetBarSpacing()>>1) + bxr := bxl + bar.GetWidth() normalizedBarComponents := Values(bar.Values).Normalize() yoffset := canvasBox.Top @@ -141,22 +147,71 @@ func (sbc StackedBarChart) drawBar(r Renderer, canvasBox Box, xoffset int, bar S return bxr } +func (sbc StackedBarChart) drawXAxis(r Renderer, canvasBox Box) { + if sbc.XAxis.Show { + axisStyle := sbc.XAxis.InheritFrom(sbc.styleDefaultsAxes()) + axisStyle.WriteToRenderer(r) + + r.MoveTo(canvasBox.Left, canvasBox.Bottom) + r.LineTo(canvasBox.Right, canvasBox.Bottom) + r.Stroke() + + r.MoveTo(canvasBox.Left, canvasBox.Bottom) + r.LineTo(canvasBox.Left, canvasBox.Bottom+DefaultVerticalTickHeight) + r.Stroke() + + cursor := canvasBox.Left + for _, bar := range sbc.Bars { + + spacing := (sbc.GetBarSpacing() >> 1) + + barLabelBox := Box{ + Top: canvasBox.Bottom + DefaultXAxisMargin, + Left: cursor, + Right: cursor + bar.GetWidth() + spacing, + Bottom: sbc.GetHeight(), + } + if len(bar.Name) > 0 { + Draw.TextWithin(r, bar.Name, barLabelBox, axisStyle) + } + axisStyle.WriteToRenderer(r) + r.MoveTo(barLabelBox.Right, canvasBox.Bottom) + r.LineTo(barLabelBox.Right, canvasBox.Bottom+DefaultVerticalTickHeight) + r.Stroke() + cursor += bar.GetWidth() + spacing + } + } +} + +func (sbc StackedBarChart) drawYAxis(r Renderer, canvasBox Box) { + if sbc.YAxis.Show { + axisStyle := sbc.YAxis.InheritFrom(sbc.styleDefaultsAxes()) + axisStyle.WriteToRenderer(r) + r.MoveTo(canvasBox.Right, canvasBox.Top) + r.LineTo(canvasBox.Right, canvasBox.Bottom) + r.Stroke() + + r.MoveTo(canvasBox.Right, canvasBox.Bottom) + r.LineTo(canvasBox.Right+DefaultHorizontalTickWidth, canvasBox.Bottom) + r.Stroke() + + ticks := Sequence.Float64(1.0, 0.0, 0.2) + for _, t := range ticks { + ty := canvasBox.Bottom - int(t*float64(canvasBox.Height())) + r.MoveTo(canvasBox.Right, ty) + r.LineTo(canvasBox.Right+DefaultHorizontalTickWidth, ty) + r.Stroke() + + text := fmt.Sprintf("%0.0f%%", t*100) + Draw.Text(r, text, canvasBox.Right+DefaultYAxisMargin, ty, axisStyle) + } + + } +} + func (sbc StackedBarChart) drawTitle(r Renderer) { if len(sbc.Title) > 0 && sbc.TitleStyle.Show { - r.SetFont(sbc.TitleStyle.GetFont(sbc.GetFont())) - r.SetFontColor(sbc.TitleStyle.GetFontColor(DefaultTextColor)) - titleFontSize := sbc.TitleStyle.GetFontSize(DefaultTitleFontSize) - r.SetFontSize(titleFontSize) - - textBox := r.MeasureText(sbc.Title) - - textWidth := textBox.Width() - textHeight := textBox.Height() - - titleX := (sbc.GetWidth() >> 1) - (textWidth >> 1) - titleY := sbc.TitleStyle.Padding.GetTop(DefaultTitleTop) + textHeight - - r.Text(sbc.Title, titleX, titleY) + Draw.TextWithin(r, sbc.Title, sbc.Box(), sbc.styleDefaultsTitle()) } } @@ -173,22 +228,22 @@ func (sbc StackedBarChart) getAdjustedCanvasBox(canvasBox Box) Box { } } - return canvasBox.OuterConstrain(sbc.Box(), Box{ + return Box{ Top: canvasBox.Top, Left: canvasBox.Left, Right: canvasBox.Left + totalWidth, Bottom: canvasBox.Bottom, - }) + } } // Box returns the chart bounds as a box. func (sbc StackedBarChart) Box() Box { - dpr := sbc.Background.Padding.GetRight(DefaultBackgroundPadding.Right) - dpb := sbc.Background.Padding.GetBottom(DefaultBackgroundPadding.Bottom) + dpr := sbc.Background.Padding.GetRight(10) + dpb := sbc.Background.Padding.GetBottom(50) return Box{ - Top: sbc.Background.Padding.GetTop(DefaultBackgroundPadding.Top), - Left: sbc.Background.Padding.GetLeft(DefaultBackgroundPadding.Left), + Top: 20, + Left: 20, Right: sbc.GetWidth() - dpr, Bottom: sbc.GetHeight() - dpb, } @@ -202,6 +257,42 @@ func (sbc StackedBarChart) styleDefaultsStackedBarValue(index int) Style { } } +func (sbc StackedBarChart) styleDefaultsTitle() Style { + return sbc.TitleStyle.InheritFrom(Style{ + FontColor: DefaultTextColor, + Font: sbc.GetFont(), + FontSize: sbc.getTitleFontSize(), + TextHorizontalAlign: TextHorizontalAlignCenter, + TextVerticalAlign: TextVerticalAlignTop, + TextWrap: TextWrapWord, + }) +} + +func (sbc StackedBarChart) getTitleFontSize() float64 { + effectiveDimension := Math.MinInt(sbc.GetWidth(), sbc.GetHeight()) + if effectiveDimension >= 2048 { + return 48 + } else if effectiveDimension >= 1024 { + return 24 + } else if effectiveDimension >= 512 { + return 18 + } else if effectiveDimension >= 256 { + return 12 + } + return 10 +} + +func (sbc StackedBarChart) styleDefaultsAxes() Style { + return Style{ + StrokeColor: DefaultAxisColor, + Font: sbc.GetFont(), + FontSize: DefaultAxisFontSize, + FontColor: DefaultAxisColor, + TextHorizontalAlign: TextHorizontalAlignCenter, + TextVerticalAlign: TextVerticalAlignTop, + TextWrap: TextWrapWord, + } +} func (sbc StackedBarChart) styleDefaultsElements() Style { return Style{ Font: sbc.GetFont(), diff --git a/text.go b/text.go index f1d73d2..6e45c1c 100644 --- a/text.go +++ b/text.go @@ -1,9 +1,6 @@ package chart -import ( - "fmt" - "strings" -) +import "strings" // TextHorizontalAlign is an enum for the horizontal alignment options. type textHorizontalAlign int @@ -74,7 +71,6 @@ func (t text) WrapFit(r Renderer, value string, width int, style Style) []string case TextWrapWord: return t.WrapFitWord(r, value, width, style) } - fmt.Printf("text wrap: %#v\n", style.TextWrap) return []string{value} }