text options!

This commit is contained in:
Will Charczuk 2016-07-29 16:36:29 -07:00
parent b3386853bb
commit d84d6790c0
25 changed files with 526 additions and 287 deletions

View File

@ -50,7 +50,7 @@ func (as AnnotationSeries) Measure(r Renderer, canvasBox Box, xrange, yrange Ran
style := a.Style.InheritFrom(seriesStyle) style := a.Style.InheritFrom(seriesStyle)
lx := canvasBox.Left + xrange.Translate(a.XValue) lx := canvasBox.Left + xrange.Translate(a.XValue)
ly := canvasBox.Bottom - yrange.Translate(a.YValue) ly := canvasBox.Bottom - yrange.Translate(a.YValue)
ab := MeasureAnnotation(r, canvasBox, style, lx, ly, a.Label) ab := Draw.MeasureAnnotation(r, canvasBox, style, lx, ly, a.Label)
box.Top = MinInt(box.Top, ab.Top) box.Top = MinInt(box.Top, ab.Top)
box.Left = MinInt(box.Left, ab.Left) box.Left = MinInt(box.Left, ab.Left)
box.Right = MaxInt(box.Right, ab.Right) box.Right = MaxInt(box.Right, ab.Right)
@ -68,7 +68,7 @@ func (as AnnotationSeries) Render(r Renderer, canvasBox Box, xrange, yrange Rang
style := a.Style.InheritFrom(seriesStyle) style := a.Style.InheritFrom(seriesStyle)
lx := canvasBox.Left + xrange.Translate(a.XValue) lx := canvasBox.Left + xrange.Translate(a.XValue)
ly := canvasBox.Bottom - yrange.Translate(a.YValue) ly := canvasBox.Bottom - yrange.Translate(a.YValue)
DrawAnnotation(r, canvasBox, style, lx, ly, a.Label) Draw.Annotation(r, canvasBox, style, lx, ly, a.Label)
} }
} }
} }

View File

@ -114,7 +114,7 @@ func (bbs *BollingerBandsSeries) Render(r Renderer, canvasBox Box, xrange, yrang
FillColor: DefaultAxisColor.WithAlpha(32), FillColor: DefaultAxisColor.WithAlpha(32),
})) }))
DrawBoundedSeries(r, canvasBox, xrange, yrange, s, bbs, bbs.GetPeriod()) Draw.BoundedSeries(r, canvasBox, xrange, yrange, s, bbs, bbs.GetPeriod())
} }
func (bbs BollingerBandsSeries) getAverage(valueBuffer *RingBuffer) float64 { func (bbs BollingerBandsSeries) getAverage(valueBuffer *RingBuffer) float64 {

View File

@ -391,7 +391,7 @@ func (c Chart) getBackgroundStyle() Style {
} }
func (c Chart) drawBackground(r Renderer) { func (c Chart) drawBackground(r Renderer) {
DrawBox(r, Box{ Draw.Box(r, Box{
Right: c.GetWidth(), Right: c.GetWidth(),
Bottom: c.GetHeight(), Bottom: c.GetHeight(),
}, c.getBackgroundStyle()) }, c.getBackgroundStyle())
@ -402,7 +402,7 @@ func (c Chart) getCanvasStyle() Style {
} }
func (c Chart) drawCanvas(r Renderer, canvasBox Box) { func (c Chart) drawCanvas(r Renderer, canvasBox Box) {
DrawBox(r, canvasBox, c.getCanvasStyle()) Draw.Box(r, canvasBox, c.getCanvasStyle())
} }
func (c Chart) drawAxes(r Renderer, canvasBox Box, xrange, yrange, yrangeAlt Range, xticks, yticks, yticksAlt []Tick) { func (c Chart) drawAxes(r Renderer, canvasBox Box, xrange, yrange, yrangeAlt Range, xticks, yticks, yticksAlt []Tick) {

View File

@ -51,5 +51,5 @@ func (cs ContinuousSeries) GetYAxis() YAxisType {
// Render renders the series. // Render renders the series.
func (cs ContinuousSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) { func (cs ContinuousSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
style := cs.Style.InheritFrom(defaults) style := cs.Style.InheritFrom(defaults)
DrawLineSeries(r, canvasBox, xrange, yrange, style, cs) Draw.LineSeries(r, canvasBox, xrange, yrange, style, cs)
} }

View File

@ -1,13 +1,16 @@
package chart package chart
import ( import "math"
"math"
"github.com/wcharczuk/go-chart/drawing" var (
// Draw contains helpers for drawing common objects.
Draw = &draw{}
) )
// DrawLineSeries draws a line series with a renderer. type draw struct{}
func DrawLineSeries(r Renderer, canvasBox Box, xrange, yrange Range, s Style, vs ValueProvider) {
// LineSeries draws a line series with a renderer.
func (d draw) LineSeries(r Renderer, canvasBox Box, xrange, yrange Range, s Style, vs ValueProvider) {
if vs.Len() == 0 { if vs.Len() == 0 {
return return
} }
@ -52,8 +55,8 @@ func DrawLineSeries(r Renderer, canvasBox Box, xrange, yrange Range, s Style, vs
r.Stroke() r.Stroke()
} }
// DrawBoundedSeries draws a series that implements BoundedValueProvider. // BoundedSeries draws a series that implements BoundedValueProvider.
func DrawBoundedSeries(r Renderer, canvasBox Box, xrange, yrange Range, s Style, bbs BoundedValueProvider, drawOffsetIndexes ...int) { func (d draw) BoundedSeries(r Renderer, canvasBox Box, xrange, yrange Range, s Style, bbs BoundedValueProvider, drawOffsetIndexes ...int) {
drawOffsetIndex := 0 drawOffsetIndex := 0
if len(drawOffsetIndexes) > 0 { if len(drawOffsetIndexes) > 0 {
drawOffsetIndex = drawOffsetIndexes[0] drawOffsetIndex = drawOffsetIndexes[0]
@ -106,8 +109,8 @@ func DrawBoundedSeries(r Renderer, canvasBox Box, xrange, yrange Range, s Style,
r.FillStroke() r.FillStroke()
} }
// DrawHistogramSeries draws a value provider as boxes from 0. // HistogramSeries draws a value provider as boxes from 0.
func DrawHistogramSeries(r Renderer, canvasBox Box, xrange, yrange Range, s Style, vs ValueProvider, barWidths ...int) { func (d draw) HistogramSeries(r Renderer, canvasBox Box, xrange, yrange Range, s Style, vs ValueProvider, barWidths ...int) {
if vs.Len() == 0 { if vs.Len() == 0 {
return return
} }
@ -129,7 +132,7 @@ func DrawHistogramSeries(r Renderer, canvasBox Box, xrange, yrange Range, s Styl
x := cl + xrange.Translate(vx) x := cl + xrange.Translate(vx)
y := yrange.Translate(vy) y := yrange.Translate(vy)
DrawBox(r, Box{ d.Box(r, Box{
Top: cb - y0, Top: cb - y0,
Left: x - (barWidth >> 1), Left: x - (barWidth >> 1),
Right: x + (barWidth >> 1), Right: x + (barWidth >> 1),
@ -139,7 +142,7 @@ func DrawHistogramSeries(r Renderer, canvasBox Box, xrange, yrange Range, s Styl
} }
// MeasureAnnotation measures how big an annotation would be. // MeasureAnnotation measures how big an annotation would be.
func MeasureAnnotation(r Renderer, canvasBox Box, s Style, lx, ly int, label string) Box { func (d draw) MeasureAnnotation(r Renderer, canvasBox Box, s Style, lx, ly int, label string) Box {
r.SetFillColor(s.GetFillColor(DefaultAnnotationFillColor)) r.SetFillColor(s.GetFillColor(DefaultAnnotationFillColor))
r.SetStrokeColor(s.GetStrokeColor()) r.SetStrokeColor(s.GetStrokeColor())
r.SetStrokeWidth(s.GetStrokeWidth()) r.SetStrokeWidth(s.GetStrokeWidth())
@ -171,8 +174,8 @@ func MeasureAnnotation(r Renderer, canvasBox Box, s Style, lx, ly int, label str
} }
} }
// DrawAnnotation draws an anotation with a renderer. // Annotation draws an anotation with a renderer.
func DrawAnnotation(r Renderer, canvasBox Box, style Style, lx, ly int, label string) { func (d draw) Annotation(r Renderer, canvasBox Box, style Style, lx, ly int, label string) {
r.SetFillColor(style.GetFillColor()) r.SetFillColor(style.GetFillColor())
r.SetStrokeColor(style.GetStrokeColor()) r.SetStrokeColor(style.GetStrokeColor())
r.SetStrokeWidth(style.GetStrokeWidth()) r.SetStrokeWidth(style.GetStrokeWidth())
@ -218,8 +221,8 @@ func DrawAnnotation(r Renderer, canvasBox Box, style Style, lx, ly int, label st
r.Text(label, textX, textY) r.Text(label, textX, textY)
} }
// DrawBox draws a box with a given style. // Box draws a box with a given style.
func DrawBox(r Renderer, b Box, s Style) { func (d draw) Box(r Renderer, b Box, s Style) {
r.SetFillColor(s.GetFillColor()) r.SetFillColor(s.GetFillColor())
r.SetStrokeColor(s.GetStrokeColor()) r.SetStrokeColor(s.GetStrokeColor())
r.SetStrokeWidth(s.GetStrokeWidth(DefaultStrokeWidth)) r.SetStrokeWidth(s.GetStrokeWidth(DefaultStrokeWidth))
@ -234,7 +237,7 @@ func DrawBox(r Renderer, b Box, s Style) {
} }
// DrawText draws text with a given style. // DrawText draws text with a given style.
func DrawText(r Renderer, text string, x, y int, s Style) { func (d draw) Text(r Renderer, text string, x, y int, s Style) {
r.SetFontColor(s.GetFontColor(DefaultTextColor)) r.SetFontColor(s.GetFontColor(DefaultTextColor))
r.SetStrokeColor(s.GetStrokeColor()) r.SetStrokeColor(s.GetStrokeColor())
r.SetStrokeWidth(s.GetStrokeWidth()) r.SetStrokeWidth(s.GetStrokeWidth())
@ -244,129 +247,7 @@ func DrawText(r Renderer, text string, x, y int, s Style) {
r.Text(text, x, y) r.Text(text, x, y)
} }
// DrawTextCentered draws text with a given style centered. // TextWithin draws the text within a given box.
func DrawTextCentered(r Renderer, text string, x, y int, s Style) { func (d draw) TextWithin(r Renderer, text string, box Box, s Style) {
r.SetFontColor(s.GetFontColor(DefaultTextColor))
r.SetStrokeColor(s.GetStrokeColor())
r.SetStrokeWidth(s.GetStrokeWidth())
r.SetFont(s.GetFont())
r.SetFontSize(s.GetFontSize())
tb := r.MeasureText(text)
tx := x - (tb.Width() >> 1)
ty := y - (tb.Height() >> 1)
r.Text(text, tx, ty)
}
// CreateLegend returns a legend renderable function.
func CreateLegend(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: cb.Top,
Left: cb.Left,
// 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,
}
r.SetFont(legendStyle.GetFont())
r.SetFontColor(legendStyle.GetFontColor())
r.SetFontSize(legendStyle.GetFontSize())
// 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)
legend.Right = legendContent.Right + legendPadding.Right
legend.Bottom = legendContent.Bottom + legendPadding.Bottom
DrawBox(r, legend, legendStyle)
ycursor := legendContent.Top
tx := legendContent.Left
legendCount := 0
for x := 0; x < len(labels); x++ {
if len(labels[x]) > 0 {
if legendCount > 0 {
ycursor += DefaultMinimumTickVerticalSpacing
}
tb := r.MeasureText(labels[x])
ty := ycursor + tb.Height()
r.Text(labels[x], 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++
}
}
}
} }

View File

@ -1,6 +1,3 @@
// Copyright 2010 The draw2d Authors. All rights reserved.
// created: 13/12/2010 by Laurent Le Goff
package drawing package drawing
import ( import (

View File

@ -97,5 +97,5 @@ func (ema *EMASeries) ensureCachedValues() {
// Render renders the series. // Render renders the series.
func (ema *EMASeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) { func (ema *EMASeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
style := ema.Style.InheritFrom(defaults) style := ema.Style.InheritFrom(defaults)
DrawLineSeries(r, canvasBox, xrange, yrange, style, ema) Draw.LineSeries(r, canvasBox, xrange, yrange, style, ema)
} }

View File

@ -10,11 +10,13 @@ import (
func drawChart(res http.ResponseWriter, req *http.Request) { func drawChart(res http.ResponseWriter, req *http.Request) {
pie := chart.PieChart{ pie := chart.PieChart{
Title: "test\nchart",
TitleStyle: chart.Style{
Show: true,
FontSize: 32,
},
Width: 512, Width: 512,
Height: 512, Height: 512,
Canvas: chart.Style{
FillColor: chart.ColorLightGray,
},
Values: []chart.Value{ Values: []chart.Value{
{Value: 5, Label: "Blue"}, {Value: 5, Label: "Blue"},
{Value: 5, Label: "Green"}, {Value: 5, Label: "Green"},

View File

@ -53,5 +53,5 @@ func (hs HistogramSeries) GetBoundedValue(index int) (x, y1, y2 float64) {
// Render implements Series.Render. // Render implements Series.Render.
func (hs HistogramSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) { func (hs HistogramSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
style := hs.Style.InheritFrom(defaults) style := hs.Style.InheritFrom(defaults)
DrawHistogramSeries(r, canvasBox, xrange, yrange, style, hs) Draw.HistogramSeries(r, canvasBox, xrange, yrange, style, hs)
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 32 KiB

116
legend.go Normal file
View File

@ -0,0 +1,116 @@
package chart
import "github.com/wcharczuk/go-chart/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,
}
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: cb.Top,
Left: cb.Left,
// 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,
}
r.SetFont(legendStyle.GetFont())
r.SetFontColor(legendStyle.GetFontColor())
r.SetFontSize(legendStyle.GetFontSize())
// 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)
legend.Right = legendContent.Right + legendPadding.Right
legend.Bottom = legendContent.Bottom + legendPadding.Bottom
Draw.Box(r, legend, legendStyle)
ycursor := legendContent.Top
tx := legendContent.Left
legendCount := 0
for x := 0; x < len(labels); x++ {
if len(labels[x]) > 0 {
if legendCount > 0 {
ycursor += DefaultMinimumTickVerticalSpacing
}
tb := r.MeasureText(labels[x])
ty := ycursor + tb.Height()
r.Text(labels[x], 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++
}
}
}
}

View File

@ -128,5 +128,5 @@ func (lrs *LinearRegressionSeries) computeCoefficients() {
// Render renders the series. // Render renders the series.
func (lrs *LinearRegressionSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) { func (lrs *LinearRegressionSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
style := lrs.Style.InheritFrom(defaults) style := lrs.Style.InheritFrom(defaults)
DrawLineSeries(r, canvasBox, xrange, yrange, style, lrs) Draw.LineSeries(r, canvasBox, xrange, yrange, style, lrs)
} }

View File

@ -193,7 +193,7 @@ func (macds *MACDSignalSeries) ensureSignal() {
// Render renders the series. // Render renders the series.
func (macds *MACDSignalSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) { func (macds *MACDSignalSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
style := macds.Style.InheritFrom(defaults) style := macds.Style.InheritFrom(defaults)
DrawLineSeries(r, canvasBox, xrange, yrange, style, macds) Draw.LineSeries(r, canvasBox, xrange, yrange, style, macds)
} }
// MACDLineSeries is a series that computes the inner ema1-ema2 value as a series. // MACDLineSeries is a series that computes the inner ema1-ema2 value as a series.
@ -285,5 +285,5 @@ func (macdl *MACDLineSeries) ensureEMASeries() {
// Render renders the series. // Render renders the series.
func (macdl *MACDLineSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) { func (macdl *MACDLineSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
style := macdl.Style.InheritFrom(defaults) style := macdl.Style.InheritFrom(defaults)
DrawLineSeries(r, canvasBox, xrange, yrange, style, macdl) Draw.LineSeries(r, canvasBox, xrange, yrange, style, macdl)
} }

View File

@ -99,14 +99,14 @@ func (pc PieChart) Render(rp RendererProvider, w io.Writer) error {
} }
func (pc PieChart) drawBackground(r Renderer) { func (pc PieChart) drawBackground(r Renderer) {
DrawBox(r, Box{ Draw.Box(r, Box{
Right: pc.GetWidth(), Right: pc.GetWidth(),
Bottom: pc.GetHeight(), Bottom: pc.GetHeight(),
}, pc.getBackgroundStyle()) }, pc.getBackgroundStyle())
} }
func (pc PieChart) drawCanvas(r Renderer, canvasBox Box) { func (pc PieChart) drawCanvas(r Renderer, canvasBox Box) {
DrawBox(r, canvasBox, pc.getCanvasStyle()) Draw.Box(r, canvasBox, pc.getCanvasStyle())
} }
func (pc PieChart) drawTitle(r Renderer) { func (pc PieChart) drawTitle(r Renderer) {
@ -138,7 +138,7 @@ func (pc PieChart) drawSlices(r Renderer, canvasBox Box, values []Value) {
var rads, delta, delta2, total float64 var rads, delta, delta2, total float64
var lx, ly int var lx, ly int
for index, v := range values { for index, v := range values {
v.Style.InheritFrom(pc.stylePieChartValue(index)).PersistToRenderer(r) v.Style.InheritFrom(pc.stylePieChartValue(index)).WriteToRenderer(r)
r.MoveTo(cx, cy) r.MoveTo(cx, cy)
rads = PercentToRadians(total) rads = PercentToRadians(total)
@ -155,7 +155,7 @@ func (pc PieChart) drawSlices(r Renderer, canvasBox Box, values []Value) {
// draw the labels // draw the labels
total = 0 total = 0
for index, v := range values { for index, v := range values {
v.Style.InheritFrom(pc.stylePieChartValue(index)).PersistToRenderer(r) v.Style.InheritFrom(pc.stylePieChartValue(index)).WriteToRenderer(r)
if len(v.Label) > 0 { if len(v.Label) > 0 {
delta2 = PercentToRadians(total + (v.Value / 2.0)) delta2 = PercentToRadians(total + (v.Value / 2.0))
delta2 = RadianAdd(delta2, _pi2) delta2 = RadianAdd(delta2, _pi2)

View File

@ -86,5 +86,5 @@ func (sma SMASeries) getAverage(index int) float64 {
// Render renders the series. // Render renders the series.
func (sma SMASeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) { func (sma SMASeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
style := sma.Style.InheritFrom(defaults) style := sma.Style.InheritFrom(defaults)
DrawLineSeries(r, canvasBox, xrange, yrange, style, sma) Draw.LineSeries(r, canvasBox, xrange, yrange, style, sma)
} }

View File

@ -134,7 +134,7 @@ func (sbc StackedBarChart) drawBar(r Renderer, canvasBox Box, xoffset int, bar S
for index, bv := range normalizedBarComponents { for index, bv := range normalizedBarComponents {
barHeight := int(bv.Value * float64(canvasBox.Height())) barHeight := int(bv.Value * float64(canvasBox.Height()))
barBox := Box{Top: yoffset, Left: bxl, Right: bxr, Bottom: yoffset + barHeight} barBox := Box{Top: yoffset, Left: bxl, Right: bxr, Bottom: yoffset + barHeight}
DrawBox(r, barBox, bv.Style.InheritFrom(sbc.styleDefaultsStackedBarValue(index))) Draw.Box(r, barBox, bv.Style.InheritFrom(sbc.styleDefaultsStackedBarValue(index)))
yoffset += barHeight yoffset += barHeight
} }

143
style.go
View File

@ -21,6 +21,10 @@ type Style struct {
FontSize float64 FontSize float64
FontColor drawing.Color FontColor drawing.Color
Font *truetype.Font Font *truetype.Font
TextHorizontalAlign textHorizontalAlign
TextVerticalAlign textVerticalAlign
TextWrap textWrap
} }
// IsZero returns if the object is set or not. // IsZero returns if the object is set or not.
@ -185,8 +189,41 @@ func (s Style) GetPadding(defaults ...Box) Box {
return s.Padding return s.Padding
} }
// PersistToRenderer passes the style onto a renderer. // GetTextHorizontalAlign returns the horizontal alignment.
func (s Style) PersistToRenderer(r Renderer) { func (s Style) GetTextHorizontalAlign(defaults ...textHorizontalAlign) textHorizontalAlign {
if s.TextHorizontalAlign == TextHorizontalAlignUnset {
if len(defaults) > 0 {
return defaults[0]
}
return TextHorizontalAlignLeft
}
return s.TextHorizontalAlign
}
// GetTextVerticalAlign returns the vertical alignment.
func (s Style) GetTextVerticalAlign(defaults ...textVerticalAlign) textVerticalAlign {
if s.TextVerticalAlign == TextVerticalAlignUnset {
if len(defaults) > 0 {
return defaults[0]
}
return TextVerticalAlignBaseline
}
return s.TextVerticalAlign
}
// GetTextWrap returns the word wrap.
func (s Style) GetTextWrap(defaults ...textWrap) textWrap {
if s.TextWrap == TextWrapUnset {
if len(defaults) > 0 {
return defaults[0]
}
return TextWrapWord
}
return s.TextWrap
}
// WriteToRenderer passes the style's options to a renderer.
func (s Style) WriteToRenderer(r Renderer) {
r.SetStrokeColor(s.GetStrokeColor()) r.SetStrokeColor(s.GetStrokeColor())
r.SetStrokeWidth(s.GetStrokeWidth()) r.SetStrokeWidth(s.GetStrokeWidth())
r.SetStrokeDashArray(s.GetStrokeDashArray()) r.SetStrokeDashArray(s.GetStrokeDashArray())
@ -196,6 +233,21 @@ func (s Style) PersistToRenderer(r Renderer) {
r.SetFontSize(s.GetFontSize()) r.SetFontSize(s.GetFontSize())
} }
// WriteDrawingOptionsToRenderer passes just the drawing style options to a renderer.
func (s Style) WriteDrawingOptionsToRenderer(r Renderer) {
r.SetStrokeColor(s.GetStrokeColor())
r.SetStrokeWidth(s.GetStrokeWidth())
r.SetStrokeDashArray(s.GetStrokeDashArray())
r.SetFillColor(s.GetFillColor())
}
// WriteTextOptionsToRenderer passes just the text style options to a renderer.
func (s Style) WriteTextOptionsToRenderer(r Renderer) {
r.SetFont(s.GetFont())
r.SetFontColor(s.GetFontColor())
r.SetFontSize(s.GetFontSize())
}
// InheritFrom coalesces two styles into a new style. // InheritFrom coalesces two styles into a new style.
func (s Style) InheritFrom(defaults Style) (final Style) { func (s Style) InheritFrom(defaults Style) (final Style) {
final.StrokeColor = s.GetStrokeColor(defaults.StrokeColor) final.StrokeColor = s.GetStrokeColor(defaults.StrokeColor)
@ -206,47 +258,14 @@ func (s Style) InheritFrom(defaults Style) (final Style) {
final.FontSize = s.GetFontSize(defaults.FontSize) final.FontSize = s.GetFontSize(defaults.FontSize)
final.Font = s.GetFont(defaults.Font) final.Font = s.GetFont(defaults.Font)
final.Padding = s.GetPadding(defaults.Padding) final.Padding = s.GetPadding(defaults.Padding)
final.TextHorizontalAlign = s.GetTextHorizontalAlign(defaults.TextHorizontalAlign)
final.TextVerticalAlign = s.GetTextVerticalAlign(defaults.TextVerticalAlign)
final.TextWrap = s.GetTextWrap(defaults.TextWrap)
return return
} }
// SVG returns the style as a svg style string. // GetStrokeOptions returns the stroke components.
func (s Style) SVG(dpi float64) string { func (s Style) GetStrokeOptions() Style {
sw := s.StrokeWidth
sc := s.StrokeColor
fc := s.FillColor
fs := s.FontSize
fnc := s.FontColor
strokeWidthText := "stroke-width:0"
if sw != 0 {
strokeWidthText = "stroke-width:" + fmt.Sprintf("%d", int(sw))
}
strokeText := "stroke:none"
if !sc.IsZero() {
strokeText = "stroke:" + sc.String()
}
fillText := "fill:none"
if !fc.IsZero() {
fillText = "fill:" + fc.String()
}
fontSizeText := ""
if fs != 0 {
fontSizeText = "font-size:" + fmt.Sprintf("%.1fpx", drawing.PointsToPixels(dpi, fs))
}
if !fnc.IsZero() {
fillText = "fill:" + fnc.String()
}
fontText := s.SVGFontFace()
return strings.Join([]string{strokeWidthText, strokeText, fillText, fontSizeText, fontText}, ";")
}
// SVGStroke returns the stroke components.
func (s Style) SVGStroke() Style {
return Style{ return Style{
StrokeDashArray: s.StrokeDashArray, StrokeDashArray: s.StrokeDashArray,
StrokeColor: s.StrokeColor, StrokeColor: s.StrokeColor,
@ -254,15 +273,15 @@ func (s Style) SVGStroke() Style {
} }
} }
// SVGFill returns the fill components. // GetFillOptions returns the fill components.
func (s Style) SVGFill() Style { func (s Style) GetFillOptions() Style {
return Style{ return Style{
FillColor: s.FillColor, FillColor: s.FillColor,
} }
} }
// SVGFillAndStroke returns the fill and stroke components. // GetFillAndStrokeOptions returns the fill and stroke components.
func (s Style) SVGFillAndStroke() Style { func (s Style) GetFillAndStrokeOptions() Style {
return Style{ return Style{
StrokeDashArray: s.StrokeDashArray, StrokeDashArray: s.StrokeDashArray,
FillColor: s.FillColor, FillColor: s.FillColor,
@ -271,34 +290,14 @@ func (s Style) SVGFillAndStroke() Style {
} }
} }
// SVGText returns just the text components of the style. // GetTextOptions returns just the text components of the style.
func (s Style) SVGText() Style { func (s Style) GetTextOptions() Style {
return Style{ return Style{
FontColor: s.FontColor, FontColor: s.FontColor,
FontSize: s.FontSize, FontSize: s.FontSize,
Font: s.Font,
TextHorizontalAlign: s.TextHorizontalAlign,
TextVerticalAlign: s.TextVerticalAlign,
TextWrap: s.TextWrap,
} }
} }
// SVGFontFace returns the font face for the style.
func (s Style) SVGFontFace() string {
family := "sans-serif"
if s.GetFont() != nil {
name := s.GetFont().Name(truetype.NameIDFontFamily)
if len(name) != 0 {
family = fmt.Sprintf(`'%s',%s`, name, family)
}
}
return fmt.Sprintf("font-family:%s", family)
}
// SVGStrokeDashArray returns the stroke-dasharray property of a style.
func (s Style) SVGStrokeDashArray() string {
if len(s.StrokeDashArray) > 0 {
var values []string
for _, v := range s.StrokeDashArray {
values = append(values, fmt.Sprintf("%0.1f", v))
}
return "stroke-dasharray=\"" + strings.Join(values, ", ") + "\""
}
return ""
}

View File

@ -1,7 +1,6 @@
package chart package chart
import ( import (
"strings"
"testing" "testing"
"github.com/blendlabs/go-assert" "github.com/blendlabs/go-assert"
@ -146,29 +145,7 @@ func TestStyleWithDefaultsFrom(t *testing.T) {
assert.Equal(set, coalesced) assert.Equal(set, coalesced)
} }
func TestStyleSVG(t *testing.T) { func TestStyleGetStrokeOptions(t *testing.T) {
assert := assert.New(t)
f, err := GetDefaultFont()
assert.Nil(err)
set := Style{
StrokeColor: drawing.ColorWhite,
StrokeWidth: 5.0,
FillColor: drawing.ColorWhite,
FontColor: drawing.ColorWhite,
Font: f,
Padding: DefaultBackgroundPadding,
}
svgString := set.SVG(DefaultDPI)
assert.NotEmpty(svgString)
assert.True(strings.Contains(svgString, "stroke:rgba(255,255,255,1.0)"))
assert.True(strings.Contains(svgString, "stroke-width:5"))
assert.True(strings.Contains(svgString, "fill:rgba(255,255,255,1.0)"))
}
func TestStyleSVGStroke(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
set := Style{ set := Style{
@ -178,14 +155,14 @@ func TestStyleSVGStroke(t *testing.T) {
FontColor: drawing.ColorWhite, FontColor: drawing.ColorWhite,
Padding: DefaultBackgroundPadding, Padding: DefaultBackgroundPadding,
} }
svgStroke := set.SVGStroke() svgStroke := set.GetStrokeOptions()
assert.False(svgStroke.StrokeColor.IsZero()) assert.False(svgStroke.StrokeColor.IsZero())
assert.NotZero(svgStroke.StrokeWidth) assert.NotZero(svgStroke.StrokeWidth)
assert.True(svgStroke.FillColor.IsZero()) assert.True(svgStroke.FillColor.IsZero())
assert.True(svgStroke.FontColor.IsZero()) assert.True(svgStroke.FontColor.IsZero())
} }
func TestStyleSVGFill(t *testing.T) { func TestStyleGetFillOptions(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
set := Style{ set := Style{
@ -195,14 +172,14 @@ func TestStyleSVGFill(t *testing.T) {
FontColor: drawing.ColorWhite, FontColor: drawing.ColorWhite,
Padding: DefaultBackgroundPadding, Padding: DefaultBackgroundPadding,
} }
svgFill := set.SVGFill() svgFill := set.GetFillOptions()
assert.False(svgFill.FillColor.IsZero()) assert.False(svgFill.FillColor.IsZero())
assert.Zero(svgFill.StrokeWidth) assert.Zero(svgFill.StrokeWidth)
assert.True(svgFill.StrokeColor.IsZero()) assert.True(svgFill.StrokeColor.IsZero())
assert.True(svgFill.FontColor.IsZero()) assert.True(svgFill.FontColor.IsZero())
} }
func TestStyleSVGFillAndStroke(t *testing.T) { func TestStyleGetFillAndStrokeOptions(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
set := Style{ set := Style{
@ -212,14 +189,14 @@ func TestStyleSVGFillAndStroke(t *testing.T) {
FontColor: drawing.ColorWhite, FontColor: drawing.ColorWhite,
Padding: DefaultBackgroundPadding, Padding: DefaultBackgroundPadding,
} }
svgFillAndStroke := set.SVGFillAndStroke() svgFillAndStroke := set.GetFillAndStrokeOptions()
assert.False(svgFillAndStroke.FillColor.IsZero()) assert.False(svgFillAndStroke.FillColor.IsZero())
assert.NotZero(svgFillAndStroke.StrokeWidth) assert.NotZero(svgFillAndStroke.StrokeWidth)
assert.False(svgFillAndStroke.StrokeColor.IsZero()) assert.False(svgFillAndStroke.StrokeColor.IsZero())
assert.True(svgFillAndStroke.FontColor.IsZero()) assert.True(svgFillAndStroke.FontColor.IsZero())
} }
func TestStyleSVGText(t *testing.T) { func TestStyleGetTextOptions(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
set := Style{ set := Style{
@ -229,7 +206,7 @@ func TestStyleSVGText(t *testing.T) {
FontColor: drawing.ColorWhite, FontColor: drawing.ColorWhite,
Padding: DefaultBackgroundPadding, Padding: DefaultBackgroundPadding,
} }
svgStroke := set.SVGText() svgStroke := set.GetTextOptions()
assert.True(svgStroke.StrokeColor.IsZero()) assert.True(svgStroke.StrokeColor.IsZero())
assert.Zero(svgStroke.StrokeWidth) assert.Zero(svgStroke.StrokeWidth)
assert.True(svgStroke.FillColor.IsZero()) assert.True(svgStroke.FillColor.IsZero())

153
text.go Normal file
View File

@ -0,0 +1,153 @@
package chart
import "strings"
// TextHorizontalAlign is an enum for the horizontal alignment options.
type textHorizontalAlign int
const (
// TextHorizontalAlignUnset is the unset state for text horizontal alignment.
TextHorizontalAlignUnset textHorizontalAlign = 0
// TextHorizontalAlignLeft aligns a string horizontally so that it's left ligature starts at horizontal pixel 0.
TextHorizontalAlignLeft textHorizontalAlign = 1
// TextHorizontalAlignCenter left aligns a string horizontally so that there are equal pixels
// to the left and to the right of a string within a box.
TextHorizontalAlignCenter textHorizontalAlign = 2
// TextHorizontalAlignRight right aligns a string horizontally so that the right ligature ends at the right-most pixel
// of a box.
TextHorizontalAlignRight textHorizontalAlign = 3
)
// TextWrap is an enum for the word wrap options.
type textWrap int
const (
// TextWrapUnset is the unset state for text wrap options.
TextWrapUnset textWrap = 0
// TextWrapNone will spill text past horizontal boundaries.
TextWrapNone textWrap = 1
// TextWrapWord will split a string on words (i.e. spaces) to fit within a horizontal boundary.
TextWrapWord textWrap = 2
// TextWrapRune will split a string on a rune (i.e. utf-8 codepage) to fit within a horizontal boundary.
TextWrapRune textWrap = 3
)
// TextVerticalAlign is an enum for the vertical alignment options.
type textVerticalAlign int
const (
// TextVerticalAlignUnset is the unset state for vertical alignment options.
TextVerticalAlignUnset textVerticalAlign = 0
// TextVerticalAlignBaseline aligns text according to the "baseline" of the string, or where a normal ascender begins.
TextVerticalAlignBaseline textVerticalAlign = 1
// TextVerticalAlignBottom aligns the text according to the lowers pixel of any of the ligatures (ex. g or q both extend below the baseline).
TextVerticalAlignBottom textVerticalAlign = 2
// TextVerticalAlignMiddle aligns the text so that there is an equal amount of space above and below the top and bottom of the ligatures.
TextVerticalAlignMiddle textVerticalAlign = 3
// TextVerticalAlignMiddleBaseline aligns the text veritcally so that there is an equal number of pixels above and below the baseline of the string.
TextVerticalAlignMiddleBaseline textVerticalAlign = 4
// TextVerticalAlignTop alignts the text so that the top of the ligatures are at y-pixel 0 in the container.
TextVerticalAlignTop textVerticalAlign = 5
)
var (
// Text contains utilities for text.
Text = &text{}
)
// TextStyle encapsulates text style options.
type TextStyle struct {
HorizontalAlign textHorizontalAlign
VerticalAlign textVerticalAlign
Wrap textWrap
}
type text struct{}
func (t text) WrapFit(r Renderer, value string, width int, style Style, wrapOption textWrap) []string {
valueBox := r.MeasureText(value)
if valueBox.Width() > width {
switch wrapOption {
case TextWrapRune:
return t.WrapFitRune(r, value, width, style)
case TextWrapWord:
return t.WrapFitWord(r, value, width, style)
}
}
return []string{value}
}
func (t text) WrapFitWord(r Renderer, value string, width int, style Style) []string {
style.WriteToRenderer(r)
var output []string
var line string
var word string
var textBox Box
for _, c := range value {
if c == rune('\n') { // commit the line to output
output = append(output, t.Trim(line+word))
line = ""
word = ""
continue
}
textBox = r.MeasureText(line + word + string(c))
if textBox.Width() >= width {
output = append(output, t.Trim(line))
line = word
word = string(c)
continue
}
if c == rune(' ') || c == rune('\t') {
line = line + word + string(c)
word = ""
continue
}
word = word + string(c)
}
return append(output, t.Trim(line+word))
}
func (t text) WrapFitRune(r Renderer, value string, width int, style Style) []string {
style.WriteToRenderer(r)
var output []string
var line string
var textBox Box
for _, c := range value {
if c == rune('\n') {
output = append(output, line)
line = ""
continue
}
textBox = r.MeasureText(line + string(c))
if textBox.Width() >= width {
output = append(output, line)
line = string(c)
continue
}
line = line + string(c)
}
return t.appendLast(output, line)
}
func (t text) Trim(value string) string {
return strings.Trim(value, " \t\n\r")
}
func (t text) appendLast(lines []string, text string) []string {
if len(lines) == 0 {
return []string{text}
}
lastLine := lines[len(lines)-1]
lines[len(lines)-1] = lastLine + text
return lines
}

32
text_test.go Normal file
View File

@ -0,0 +1,32 @@
package chart
import (
"testing"
assert "github.com/blendlabs/go-assert"
)
func TestTextWrapWord(t *testing.T) {
assert := assert.New(t)
r, err := PNG(1024, 1024)
assert.Nil(err)
f, err := GetDefaultFont()
assert.Nil(err)
basicTextStyle := Style{Font: f, FontSize: 24}
output := Text.WrapFitWord(r, "this is a test string", 100, basicTextStyle)
assert.NotEmpty(output)
assert.Len(output, 3)
for _, line := range output {
basicTextStyle.WriteToRenderer(r)
lineBox := r.MeasureText(line)
assert.True(lineBox.Width() < 100, line+": "+lineBox.String())
}
output = Text.WrapFitWord(r, "foo", 100, basicTextStyle)
assert.Len(output, 1)
assert.Equal("foo", output[0])
}

View File

@ -57,5 +57,5 @@ func (ts TimeSeries) GetYAxis() YAxisType {
// Render renders the series. // Render renders the series.
func (ts TimeSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) { func (ts TimeSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
style := ts.Style.InheritFrom(defaults) style := ts.Style.InheritFrom(defaults)
DrawLineSeries(r, canvasBox, xrange, yrange, style, ts) Draw.LineSeries(r, canvasBox, xrange, yrange, style, ts)
} }

View File

@ -110,30 +110,28 @@ func (vr *vectorRenderer) Close() {
// Stroke draws the path with no fill. // Stroke draws the path with no fill.
func (vr *vectorRenderer) Stroke() { func (vr *vectorRenderer) Stroke() {
vr.drawPath(vr.s.SVGStroke()) vr.drawPath(vr.s.GetStrokeOptions())
} }
// Fill draws the path with no stroke. // Fill draws the path with no stroke.
func (vr *vectorRenderer) Fill() { func (vr *vectorRenderer) Fill() {
vr.drawPath(vr.s.SVGFill()) vr.drawPath(vr.s.GetFillOptions())
} }
// FillStroke draws the path with both fill and stroke. // FillStroke draws the path with both fill and stroke.
func (vr *vectorRenderer) FillStroke() { func (vr *vectorRenderer) FillStroke() {
s := vr.s.SVGFillAndStroke() vr.drawPath(vr.s.GetFillAndStrokeOptions())
vr.drawPath(s)
} }
// drawPath draws a path. // drawPath draws a path.
func (vr *vectorRenderer) drawPath(s Style) { func (vr *vectorRenderer) drawPath(s Style) {
vr.c.Path(strings.Join(vr.p, "\n"), &s) vr.c.Path(strings.Join(vr.p, "\n"), vr.s.GetFillAndStrokeOptions())
vr.p = []string{} // clear the path vr.p = []string{} // clear the path
} }
// Circle implements the interface method. // Circle implements the interface method.
func (vr *vectorRenderer) Circle(radius float64, x, y int) { func (vr *vectorRenderer) Circle(radius float64, x, y int) {
style := vr.s.SVGFillAndStroke() vr.c.Circle(x, y, int(radius), vr.s.GetFillAndStrokeOptions())
vr.c.Circle(x, y, int(radius), &style)
} }
// SetFont implements the interface method. // SetFont implements the interface method.
@ -153,8 +151,7 @@ func (vr *vectorRenderer) SetFontSize(size float64) {
// Text draws a text blob. // Text draws a text blob.
func (vr *vectorRenderer) Text(body string, x, y int) { func (vr *vectorRenderer) Text(body string, x, y int) {
style := vr.s.SVGText() vr.c.Text(x, y, body, vr.s.GetTextOptions())
vr.c.Text(x, y, body, &style)
} }
// MeasureText uses the truetype font drawer to measure the width of text. // MeasureText uses the truetype font drawer to measure the width of text.
@ -200,22 +197,82 @@ func (c *canvas) Start(width, height int) {
c.w.Write([]byte(fmt.Sprintf(`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="%d" height="%d">\n`, c.width, c.height))) c.w.Write([]byte(fmt.Sprintf(`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="%d" height="%d">\n`, c.width, c.height)))
} }
func (c *canvas) Path(d string, style *Style) { func (c *canvas) Path(d string, style Style) {
var strokeDashArrayProperty string var strokeDashArrayProperty string
if len(style.StrokeDashArray) > 0 { if len(style.StrokeDashArray) > 0 {
strokeDashArrayProperty = style.SVGStrokeDashArray() strokeDashArrayProperty = c.getStrokeDashArray(style)
} }
c.w.Write([]byte(fmt.Sprintf(`<path %s d="%s" style="%s"/>\n`, strokeDashArrayProperty, d, style.SVG(c.dpi)))) c.w.Write([]byte(fmt.Sprintf(`<path %s d="%s" style="%s"/>\n`, strokeDashArrayProperty, d, c.styleAsSVG(style))))
} }
func (c *canvas) Text(x, y int, body string, style *Style) { func (c *canvas) Text(x, y int, body string, style Style) {
c.w.Write([]byte(fmt.Sprintf(`<text x="%d" y="%d" style="%s">%s</text>`, x, y, style.SVG(c.dpi), body))) c.w.Write([]byte(fmt.Sprintf(`<text x="%d" y="%d" style="%s">%s</text>`, x, y, c.styleAsSVG(style), body)))
} }
func (c *canvas) Circle(x, y, r int, style *Style) { func (c *canvas) Circle(x, y, r int, style Style) {
c.w.Write([]byte(fmt.Sprintf(`<circle cx="%d" cy="%d" r="%d" style="%s">`, x, y, r, style.SVG(c.dpi)))) c.w.Write([]byte(fmt.Sprintf(`<circle cx="%d" cy="%d" r="%d" style="%s">`, x, y, r, c.styleAsSVG(style))))
} }
func (c *canvas) End() { func (c *canvas) End() {
c.w.Write([]byte("</svg>")) c.w.Write([]byte("</svg>"))
} }
// getStrokeDashArray returns the stroke-dasharray property of a style.
func (c *canvas) getStrokeDashArray(s Style) string {
if len(s.StrokeDashArray) > 0 {
var values []string
for _, v := range s.StrokeDashArray {
values = append(values, fmt.Sprintf("%0.1f", v))
}
return "stroke-dasharray=\"" + strings.Join(values, ", ") + "\""
}
return ""
}
// GetFontFace returns the font face for the style.
func (c *canvas) getFontFace(s Style) string {
family := "sans-serif"
if s.GetFont() != nil {
name := s.GetFont().Name(truetype.NameIDFontFamily)
if len(name) != 0 {
family = fmt.Sprintf(`'%s',%s`, name, family)
}
}
return fmt.Sprintf("font-family:%s", family)
}
// styleAsSVG returns the style as a svg style string.
func (c *canvas) styleAsSVG(s Style) string {
sw := s.StrokeWidth
sc := s.StrokeColor
fc := s.FillColor
fs := s.FontSize
fnc := s.FontColor
strokeWidthText := "stroke-width:0"
if sw != 0 {
strokeWidthText = "stroke-width:" + fmt.Sprintf("%d", int(sw))
}
strokeText := "stroke:none"
if !sc.IsZero() {
strokeText = "stroke:" + sc.String()
}
fillText := "fill:none"
if !fc.IsZero() {
fillText = "fill:" + fc.String()
}
fontSizeText := ""
if fs != 0 {
fontSizeText = "font-size:" + fmt.Sprintf("%.1fpx", drawing.PointsToPixels(c.dpi, fs))
}
if !fnc.IsZero() {
fillText = "fill:" + fnc.String()
}
fontText := c.getFontFace(s)
return strings.Join([]string{strokeWidthText, strokeText, fillText, fontSizeText, fontText}, ";")
}

View File

@ -6,6 +6,7 @@ import (
"testing" "testing"
"github.com/blendlabs/go-assert" "github.com/blendlabs/go-assert"
"github.com/wcharczuk/go-chart/drawing"
) )
func TestVectorRendererPath(t *testing.T) { func TestVectorRendererPath(t *testing.T) {
@ -50,3 +51,27 @@ func TestVectorRendererMeasureText(t *testing.T) {
assert.Equal(21, tb.Width()) assert.Equal(21, tb.Width())
assert.Equal(15, tb.Height()) assert.Equal(15, tb.Height())
} }
func TestCanvasStyleSVG(t *testing.T) {
assert := assert.New(t)
f, err := GetDefaultFont()
assert.Nil(err)
set := Style{
StrokeColor: drawing.ColorWhite,
StrokeWidth: 5.0,
FillColor: drawing.ColorWhite,
FontColor: drawing.ColorWhite,
Font: f,
Padding: DefaultBackgroundPadding,
}
canvas := &canvas{dpi: DefaultDPI}
svgString := canvas.styleAsSVG(set)
assert.NotEmpty(svgString)
assert.True(strings.Contains(svgString, "stroke:rgba(255,255,255,1.0)"))
assert.True(strings.Contains(svgString, "stroke-width:5"))
assert.True(strings.Contains(svgString, "fill:rgba(255,255,255,1.0)"))
}

View File

@ -54,7 +54,7 @@ func (xa XAxis) GetGridLines(ticks []Tick) []GridLine {
// Measure returns the bounds of the axis. // Measure returns the bounds of the axis.
func (xa XAxis) Measure(r Renderer, canvasBox Box, ra Range, defaults Style, ticks []Tick) Box { func (xa XAxis) Measure(r Renderer, canvasBox Box, ra Range, defaults Style, ticks []Tick) Box {
xa.Style.InheritFrom(defaults).PersistToRenderer(r) xa.Style.InheritFrom(defaults).WriteToRenderer(r)
sort.Sort(Ticks(ticks)) sort.Sort(Ticks(ticks))
var left, right, top, bottom = math.MaxInt32, 0, math.MaxInt32, 0 var left, right, top, bottom = math.MaxInt32, 0, math.MaxInt32, 0
@ -82,7 +82,7 @@ func (xa XAxis) Measure(r Renderer, canvasBox Box, ra Range, defaults Style, tic
// Render renders the axis // Render renders the axis
func (xa XAxis) Render(r Renderer, canvasBox Box, ra Range, defaults Style, ticks []Tick) { func (xa XAxis) Render(r Renderer, canvasBox Box, ra Range, defaults Style, ticks []Tick) {
xa.Style.InheritFrom(defaults).PersistToRenderer(r) xa.Style.InheritFrom(defaults).WriteToRenderer(r)
r.MoveTo(canvasBox.Left, canvasBox.Bottom) r.MoveTo(canvasBox.Left, canvasBox.Bottom)
r.LineTo(canvasBox.Right, canvasBox.Bottom) r.LineTo(canvasBox.Right, canvasBox.Bottom)

View File

@ -61,7 +61,7 @@ func (ya YAxis) GetGridLines(ticks []Tick) []GridLine {
// Measure returns the bounds of the axis. // Measure returns the bounds of the axis.
func (ya YAxis) Measure(r Renderer, canvasBox Box, ra Range, defaults Style, ticks []Tick) Box { func (ya YAxis) Measure(r Renderer, canvasBox Box, ra Range, defaults Style, ticks []Tick) Box {
ya.Style.InheritFrom(defaults).PersistToRenderer(r) ya.Style.InheritFrom(defaults).WriteToRenderer(r)
sort.Sort(Ticks(ticks)) sort.Sort(Ticks(ticks))
@ -104,7 +104,7 @@ func (ya YAxis) Measure(r Renderer, canvasBox Box, ra Range, defaults Style, tic
// Render renders the axis. // Render renders the axis.
func (ya YAxis) Render(r Renderer, canvasBox Box, ra Range, defaults Style, ticks []Tick) { func (ya YAxis) Render(r Renderer, canvasBox Box, ra Range, defaults Style, ticks []Tick) {
ya.Style.InheritFrom(defaults).PersistToRenderer(r) ya.Style.InheritFrom(defaults).WriteToRenderer(r)
sort.Sort(Ticks(ticks)) sort.Sort(Ticks(ticks))