diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..b523396 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,13 @@ +language: go + +go: + - 1.6.2 + +sudo: false + +before_script: + - go build -i ./... + +script: + - go test + - go test ./drawing/ \ No newline at end of file diff --git a/annotation_series.go b/annotation_series.go index 678577c..519242e 100644 --- a/annotation_series.go +++ b/annotation_series.go @@ -29,10 +29,49 @@ func (as AnnotationSeries) GetYAxis() YAxisType { return as.YAxis } +// Measure returns a bounds box of the series. +func (as AnnotationSeries) Measure(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) Box { + box := Box{ + Top: canvasBox.Bottom, + Left: canvasBox.Right, + Right: canvasBox.Left, + Bottom: canvasBox.Top, + } + if as.Style.Show { + style := as.Style.WithDefaultsFrom(Style{ + Font: defaults.Font, + FillColor: DefaultAnnotationFillColor, + FontSize: DefaultAnnotationFontSize, + StrokeColor: defaults.StrokeColor, + StrokeWidth: defaults.StrokeWidth, + Padding: DefaultAnnotationPadding, + }) + for _, a := range as.Annotations { + lx := canvasBox.Right - xrange.Translate(a.X) + ly := yrange.Translate(a.Y) + canvasBox.Top + aBox := MeasureAnnotation(r, canvasBox, xrange, yrange, style, lx, ly, a.Label) + if aBox.Top < box.Top { + box.Top = aBox.Top + } + if aBox.Left < box.Left { + box.Left = aBox.Left + } + if aBox.Right > box.Right { + box.Right = aBox.Right + } + if aBox.Bottom > box.Bottom { + box.Bottom = aBox.Bottom + } + } + } + return box +} + // Render draws the series. func (as AnnotationSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) { if as.Style.Show { style := as.Style.WithDefaultsFrom(Style{ + Font: defaults.Font, FillColor: DefaultAnnotationFillColor, FontSize: DefaultAnnotationFontSize, StrokeColor: defaults.StrokeColor, diff --git a/chart.go b/chart.go index 47db2b6..f102487 100644 --- a/chart.go +++ b/chart.go @@ -69,24 +69,32 @@ func (c Chart) Render(rp RendererProvider, w io.Writer) error { r.SetFont(font) r.SetDPI(c.GetDPI(DefaultDPI)) - xrange, yrange, yrangeAlt := c.getRanges() + c.drawBackground(r) + + var xt, yt, yta []Tick + xr, yr, yra := c.getRanges() canvasBox := c.getDefaultCanvasBox() xf, yf, yfa := c.getValueFormatters() + xr, yr, yra = c.setRangeDomains(canvasBox, xr, yr, yra) - xrange, yrange, yrangeAlt = c.setRangeDomains(canvasBox, xrange, yrange, yrangeAlt) - xticks, yticks, yticksAlt := c.getAxesTicks(r, xrange, yrange, yrangeAlt, xf, yf, yfa) - canvasBox = c.getAdjustedCanvasBox(r, canvasBox, xticks, yticks, yticksAlt) + if c.hasAxes() { + xt, yt, yta = c.getAxesTicks(r, xr, yr, yra, xf, yf, yfa) + canvasBox = c.getAxisAdjustedCanvasBox(r, canvasBox, xt, yt, yta) + xr, yr, yra = c.setRangeDomains(canvasBox, xr, yr, yra) + xt, yt, yta = c.getAxesTicks(r, xr, yr, yra, xf, yf, yfa) + } - // we do a second pass to take the updated domains into account - xrange, yrange, yrangeAlt = c.setRangeDomains(canvasBox, xrange, yrange, yrangeAlt) - xticks, yticks, yticksAlt = c.getAxesTicks(r, xrange, yrange, yrangeAlt, xf, yf, yfa) - canvasBox = c.getAdjustedCanvasBox(r, canvasBox, xticks, yticks, yticksAlt) + if c.hasAnnotationSeries() { + canvasBox = c.getAnnotationAdjustedCanvasBox(r, canvasBox, xr, yr, yra, xf, yf, yfa) + xr, yr, yra = c.setRangeDomains(canvasBox, xr, yr, yra) + xt, yt, yta = c.getAxesTicks(r, xr, yr, yra, xf, yf, yfa) + } - c.drawBackground(r) c.drawCanvas(r, canvasBox) - c.drawAxes(r, canvasBox, xrange, yrange, yrangeAlt, xticks, yticks, yticksAlt) + c.drawAxes(r, canvasBox, xr, yr, yra, xt, yt, yta) + for index, series := range c.Series { - c.drawSeries(r, canvasBox, xrange, yrange, yrangeAlt, series, index) + c.drawSeries(r, canvasBox, xr, yr, yra, series, index) } c.drawTitle(r) return r.Save(w) @@ -207,6 +215,10 @@ func (c Chart) getValueFormatters() (x, y, ya ValueFormatter) { return } +func (c Chart) hasAxes() bool { + return c.XAxis.Style.Show || c.YAxis.Style.Show || c.YAxisSecondary.Style.Show +} + func (c Chart) getAxesTicks(r Renderer, xr, yr, yar Range, xf, yf, yfa ValueFormatter) (xticks, yticks, yticksAlt []Tick) { if c.XAxis.Style.Show { xticks = c.XAxis.GetTicks(r, xr, xf) @@ -220,7 +232,7 @@ func (c Chart) getAxesTicks(r Renderer, xr, yr, yar Range, xf, yf, yfa ValueForm return } -func (c Chart) getAdjustedCanvasBox(r Renderer, defaults Box, xticks, yticks, yticksAlt []Tick) Box { +func (c Chart) getAxisAdjustedCanvasBox(r Renderer, defaults Box, xticks, yticks, yticksAlt []Tick) Box { canvasBox := Box{} var dpl, dpr, dpb int @@ -303,6 +315,85 @@ func (c Chart) setRangeDomains(canvasBox Box, xrange, yrange, yrangeAlt Range) ( return xrange, yrange, yrangeAlt } +func (c Chart) hasAnnotationSeries() bool { + for _, s := range c.Series { + if as, isAnnotationSeries:= s.(AnnotationSeries); isAnnotationSeries { + if as.Style.Show { + return true + } + } + } + return false +} + +func (c Chart) getAnnotationAdjustedCanvasBox(r Renderer, canvasBox Box, xr, yr, yra Range, xf, yf, yfa ValueFormatter) Box { + annotationMinX, annotationMaxX, annotationMinY, annotationMaxY := canvasBox.Right, canvasBox.Left, canvasBox.Bottom, canvasBox.Top + for seriesIndex, s := range c.Series { + if as, isAnnotationSeries:= s.(AnnotationSeries); isAnnotationSeries { + if as.Style.Show { + style := c.getSeriesStyleDefaults(seriesIndex) + var annotationBounds Box + if as.YAxis == YAxisPrimary { + annotationBounds = as.Measure(r, canvasBox, xr, yr, style) + } else if as.YAxis == YAxisSecondary { + annotationBounds = as.Measure(r, canvasBox, xr, yr, style) + } + + if annotationMinY > annotationBounds.Top { + annotationMinY = annotationBounds.Top + } + + if annotationMinX > annotationBounds.Left { + annotationMinX = annotationBounds.Left + } + + if annotationMaxX < annotationBounds.Right { + annotationMaxX = annotationBounds.Right + } + + if annotationMaxY < annotationBounds.Bottom { + annotationMaxY = annotationBounds.Bottom + } + } + } + } + + newBox := Box{ + Top: canvasBox.Top, + Left: canvasBox.Left, + Right: canvasBox.Right, + Bottom: canvasBox.Bottom, + } + if annotationMinY < 0 { + // figure out how much top padding to add + delta := -1*annotationMinY + newBox.Top = canvasBox.Top+delta + } + + if annotationMaxX > c.Width { + // figure out how much right padding to add + delta := annotationMaxX - c.Width + newBox.Right = canvasBox.Right - delta + } + + if annotationMinX < 0 { + // figure out how much left padding to add + delta := -1*annotationMinX + newBox.Left = canvasBox.Left + delta + } + + if annotationMaxY > c.Height { + //figure out how much bottom padding to add + delta := annotationMaxY - c.Height + newBox.Bottom = canvasBox.Bottom - delta + } + + newBox.Height = newBox.Bottom - newBox.Top + newBox.Width = newBox.Right - newBox.Left + + return newBox +} + func (c Chart) drawBackground(r Renderer) { r.SetFillColor(c.Background.GetFillColor(DefaultBackgroundColor)) r.SetStrokeColor(c.Background.GetStrokeColor(DefaultBackgroundStrokeColor)) diff --git a/drawing_helpers.go b/drawing_helpers.go index 03623d2..f0ed697 100644 --- a/drawing_helpers.go +++ b/drawing_helpers.go @@ -49,8 +49,36 @@ func DrawLineSeries(r Renderer, canvasBox Box, xrange, yrange Range, s Style, vs r.Stroke() } +// MeasureAnnotation measures how big an annotation would be. +func MeasureAnnotation(r Renderer, canvasBox Box, xrange, yrange Range, s Style, lx, ly int, label string) Box { + r.SetFont(s.GetFont()) + r.SetFontSize(s.GetFontSize(DefaultAnnotationFontSize)) + textWidth, _ := r.MeasureText(label) + textHeight := int(math.Floor(DefaultAnnotationFontSize)) + halfTextHeight := textHeight >> 1 + + pt := s.Padding.GetTop(DefaultAnnotationPadding.Top) + pl := s.Padding.GetLeft(DefaultAnnotationPadding.Left) + pr := s.Padding.GetRight(DefaultAnnotationPadding.Right) + pb := s.Padding.GetBottom(DefaultAnnotationPadding.Bottom) + + ltly := ly - (pt + halfTextHeight) + ltrx := lx + pl + pr + textWidth + lbry := ly + (pb + halfTextHeight) + + return Box{ + Top: ltly, + Left: lx, + Right: ltrx, + Bottom: lbry, + Width: ltrx - lx, + Height: lbry - ltly, + } +} + // DrawAnnotation draws an anotation with a renderer. func DrawAnnotation(r Renderer, canvasBox Box, xrange, yrange Range, s Style, lx, ly int, label string) { + r.SetFont(s.GetFont()) r.SetFontSize(s.GetFontSize(DefaultAnnotationFontSize)) textWidth, _ := r.MeasureText(label) textHeight := int(math.Floor(DefaultAnnotationFontSize)) diff --git a/testserver/main.go b/testserver/main.go index 0cee661..e65265b 100644 --- a/testserver/main.go +++ b/testserver/main.go @@ -35,12 +35,12 @@ func chartHandler(rc *web.RequestContext) web.ControllerResult { Height: 400, XAxis: chart.XAxis{ Style: chart.Style{ - Show: true, + Show: false, }, }, YAxis: chart.YAxis{ Style: chart.Style{ - Show: true, + Show: false, }, Range: chart.Range{ Min: 0.0, @@ -49,7 +49,7 @@ func chartHandler(rc *web.RequestContext) web.ControllerResult { }, YAxisSecondary: chart.YAxis{ Style: chart.Style{ - Show: true, + Show: false, }, Range: chart.Range{ Min: 0.8,