From d84d6790c0e926449d3f1eba2cb2074b5c7e8eda Mon Sep 17 00:00:00 2001 From: Will Charczuk Date: Fri, 29 Jul 2016 16:36:29 -0700 Subject: [PATCH] text options! --- annotation_series.go | 4 +- bollinger_band_series.go | 2 +- chart.go | 4 +- continuous_series.go | 2 +- drawing_helpers.go => draw.go | 161 +++++----------------------------- drawing/text.go | 3 - ema_series.go | 2 +- examples/pie_chart/main.go | 8 +- histogram_series.go | 2 +- images/pie_chart.png | Bin 37157 -> 32994 bytes legend.go | 116 ++++++++++++++++++++++++ linear_regression_series.go | 2 +- macd_series.go | 4 +- pie_chart.go | 8 +- sma_series.go | 2 +- stacked_bar_chart.go | 2 +- style.go | 143 +++++++++++++++--------------- style_test.go | 39 ++------ text.go | 153 ++++++++++++++++++++++++++++++++ text_test.go | 32 +++++++ time_series.go | 2 +- vector_renderer.go | 89 +++++++++++++++---- vector_renderer_test.go | 25 ++++++ xaxis.go | 4 +- yaxis.go | 4 +- 25 files changed, 526 insertions(+), 287 deletions(-) rename drawing_helpers.go => draw.go (55%) create mode 100644 legend.go create mode 100644 text.go create mode 100644 text_test.go diff --git a/annotation_series.go b/annotation_series.go index 1b2c3b0..f622b8a 100644 --- a/annotation_series.go +++ b/annotation_series.go @@ -50,7 +50,7 @@ func (as AnnotationSeries) Measure(r Renderer, canvasBox Box, xrange, yrange Ran style := a.Style.InheritFrom(seriesStyle) lx := canvasBox.Left + xrange.Translate(a.XValue) 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.Left = MinInt(box.Left, ab.Left) 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) lx := canvasBox.Left + xrange.Translate(a.XValue) ly := canvasBox.Bottom - yrange.Translate(a.YValue) - DrawAnnotation(r, canvasBox, style, lx, ly, a.Label) + Draw.Annotation(r, canvasBox, style, lx, ly, a.Label) } } } diff --git a/bollinger_band_series.go b/bollinger_band_series.go index c0fe7a6..f74b489 100644 --- a/bollinger_band_series.go +++ b/bollinger_band_series.go @@ -114,7 +114,7 @@ func (bbs *BollingerBandsSeries) Render(r Renderer, canvasBox Box, xrange, yrang 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 { diff --git a/chart.go b/chart.go index c4e7526..0a77b07 100644 --- a/chart.go +++ b/chart.go @@ -391,7 +391,7 @@ func (c Chart) getBackgroundStyle() Style { } func (c Chart) drawBackground(r Renderer) { - DrawBox(r, Box{ + Draw.Box(r, Box{ Right: c.GetWidth(), Bottom: c.GetHeight(), }, c.getBackgroundStyle()) @@ -402,7 +402,7 @@ func (c Chart) getCanvasStyle() Style { } 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) { diff --git a/continuous_series.go b/continuous_series.go index 0a122d0..fe8d068 100644 --- a/continuous_series.go +++ b/continuous_series.go @@ -51,5 +51,5 @@ func (cs ContinuousSeries) GetYAxis() YAxisType { // Render renders the series. func (cs ContinuousSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) { style := cs.Style.InheritFrom(defaults) - DrawLineSeries(r, canvasBox, xrange, yrange, style, cs) + Draw.LineSeries(r, canvasBox, xrange, yrange, style, cs) } diff --git a/drawing_helpers.go b/draw.go similarity index 55% rename from drawing_helpers.go rename to draw.go index beada8e..c669791 100644 --- a/drawing_helpers.go +++ b/draw.go @@ -1,13 +1,16 @@ package chart -import ( - "math" +import "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. -func DrawLineSeries(r Renderer, canvasBox Box, xrange, yrange Range, s Style, vs ValueProvider) { +type draw struct{} + +// 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 { return } @@ -52,8 +55,8 @@ func DrawLineSeries(r Renderer, canvasBox Box, xrange, yrange Range, s Style, vs r.Stroke() } -// DrawBoundedSeries draws a series that implements BoundedValueProvider. -func DrawBoundedSeries(r Renderer, canvasBox Box, xrange, yrange Range, s Style, bbs BoundedValueProvider, drawOffsetIndexes ...int) { +// BoundedSeries draws a series that implements BoundedValueProvider. +func (d draw) BoundedSeries(r Renderer, canvasBox Box, xrange, yrange Range, s Style, bbs BoundedValueProvider, drawOffsetIndexes ...int) { drawOffsetIndex := 0 if len(drawOffsetIndexes) > 0 { drawOffsetIndex = drawOffsetIndexes[0] @@ -106,8 +109,8 @@ func DrawBoundedSeries(r Renderer, canvasBox Box, xrange, yrange Range, s Style, r.FillStroke() } -// DrawHistogramSeries draws a value provider as boxes from 0. -func DrawHistogramSeries(r Renderer, canvasBox Box, xrange, yrange Range, s Style, vs ValueProvider, barWidths ...int) { +// HistogramSeries draws a value provider as boxes from 0. +func (d draw) HistogramSeries(r Renderer, canvasBox Box, xrange, yrange Range, s Style, vs ValueProvider, barWidths ...int) { if vs.Len() == 0 { return } @@ -129,7 +132,7 @@ func DrawHistogramSeries(r Renderer, canvasBox Box, xrange, yrange Range, s Styl x := cl + xrange.Translate(vx) y := yrange.Translate(vy) - DrawBox(r, Box{ + d.Box(r, Box{ Top: cb - y0, Left: 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. -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.SetStrokeColor(s.GetStrokeColor()) 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. -func DrawAnnotation(r Renderer, canvasBox Box, style Style, lx, ly int, label string) { +// Annotation draws an anotation with a renderer. +func (d draw) Annotation(r Renderer, canvasBox Box, style Style, lx, ly int, label string) { r.SetFillColor(style.GetFillColor()) r.SetStrokeColor(style.GetStrokeColor()) 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) } -// DrawBox draws a box with a given style. -func DrawBox(r Renderer, b Box, s Style) { +// Box draws a box with a given style. +func (d draw) Box(r Renderer, b Box, s Style) { r.SetFillColor(s.GetFillColor()) r.SetStrokeColor(s.GetStrokeColor()) r.SetStrokeWidth(s.GetStrokeWidth(DefaultStrokeWidth)) @@ -234,7 +237,7 @@ func DrawBox(r Renderer, b Box, s 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.SetStrokeColor(s.GetStrokeColor()) r.SetStrokeWidth(s.GetStrokeWidth()) @@ -244,129 +247,7 @@ func DrawText(r Renderer, text string, x, y int, s Style) { r.Text(text, x, y) } -// DrawTextCentered draws text with a given style centered. -func DrawTextCentered(r Renderer, text string, x, y int, s Style) { - r.SetFontColor(s.GetFontColor(DefaultTextColor)) - r.SetStrokeColor(s.GetStrokeColor()) - r.SetStrokeWidth(s.GetStrokeWidth()) - r.SetFont(s.GetFont()) - r.SetFontSize(s.GetFontSize()) +// TextWithin draws the text within a given box. +func (d draw) TextWithin(r Renderer, text string, box Box, s Style) { - 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++ - } - } - } } diff --git a/drawing/text.go b/drawing/text.go index 52f6349..e1b40f2 100644 --- a/drawing/text.go +++ b/drawing/text.go @@ -1,6 +1,3 @@ -// Copyright 2010 The draw2d Authors. All rights reserved. -// created: 13/12/2010 by Laurent Le Goff - package drawing import ( diff --git a/ema_series.go b/ema_series.go index 2bd808d..affadc1 100644 --- a/ema_series.go +++ b/ema_series.go @@ -97,5 +97,5 @@ func (ema *EMASeries) ensureCachedValues() { // Render renders the series. func (ema *EMASeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) { style := ema.Style.InheritFrom(defaults) - DrawLineSeries(r, canvasBox, xrange, yrange, style, ema) + Draw.LineSeries(r, canvasBox, xrange, yrange, style, ema) } diff --git a/examples/pie_chart/main.go b/examples/pie_chart/main.go index a1e2097..e4b3967 100644 --- a/examples/pie_chart/main.go +++ b/examples/pie_chart/main.go @@ -10,11 +10,13 @@ import ( func drawChart(res http.ResponseWriter, req *http.Request) { pie := chart.PieChart{ + Title: "test\nchart", + TitleStyle: chart.Style{ + Show: true, + FontSize: 32, + }, Width: 512, Height: 512, - Canvas: chart.Style{ - FillColor: chart.ColorLightGray, - }, Values: []chart.Value{ {Value: 5, Label: "Blue"}, {Value: 5, Label: "Green"}, diff --git a/histogram_series.go b/histogram_series.go index 08ba1b9..0542c1a 100644 --- a/histogram_series.go +++ b/histogram_series.go @@ -53,5 +53,5 @@ func (hs HistogramSeries) GetBoundedValue(index int) (x, y1, y2 float64) { // Render implements Series.Render. func (hs HistogramSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) { style := hs.Style.InheritFrom(defaults) - DrawHistogramSeries(r, canvasBox, xrange, yrange, style, hs) + Draw.HistogramSeries(r, canvasBox, xrange, yrange, style, hs) } diff --git a/images/pie_chart.png b/images/pie_chart.png index 14a171d18bb1cf765d0b62d93acc6e02f48f0f03..4b2740eb3806344a8cca0af8d6886d8a64a44456 100644 GIT binary patch literal 32994 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%0AvYppPj`n z4RxT^z&|@KnVpb>6BoQtyQMB6(|dSzVCW~GT=M6QN|KzNk~}}nn5^*S?LitJ>3y5c z2EP6%0t`TGg3JI(lE@5@J}Ur^IPC|!8vlOh{PEsq*eH%|wq@3RvuLsR(CKzRe&m$z zx8&(ac3js1iFd2Xy}j7}!4GbSRvpQBOxz7BYMVg@h(aRk+G z0GWaDii~=3VeaF-&40gh;XvnQ*d&M)DNH`FBQ(UiYfubZ$P zAb5LX3@`xk2uYGMnG6|&2^R#gNf1k9D)n#8e26}BYuId5%6+@|DX1wwcza?DFaYrj zwID#2!1qQmMVD>(gPx{CUDuBG+!%2}LRqx(#*96Aszm>P9!>V`b~|``Vihm|z-R&i zWeFy94I%7q=c&Hd_q&@8bzf~B>5YJRz~sqN>Kl>@8j=g>`M^FvGkr?$LpXnI0|o#X z0~r9yHRL;oz)Q2a7g>V$yRV(-yJ>fUb3j~3aiU(IH9x6feo}sp(%{cqsHUXg>WLq~ z002Drzezwb?6e*0ZGNxoDzXRAo=jNM6gs5w=OyLOG~_68sXuQ)k32QscQ_p377q{= zU;uz|p*9I9{D0TQq4q;v*G~4go*QU`@^g_vNl2@7Y4Z~Ek?Jp0C;R_Kuigh2e|!Q4 z0GJpmub8p~ey#~HPb}%x;jWMSZ=LCHL$57n*f0ns*-FEdg!CF+dQCz`k;X`1{{CO6 z`M$#gxcK87FaSVc5ClP40)KXl>2%vi&i3CrJ8=6<|E*64Zd+a8Z3qlB&9Bs@P1R-8 zB&1jC(hX8&KyRn^b&=9X-@tqypa25^1PPAg)Up7*`oAooaJXF;hdR)*0DYbAztv$K z43m)2PSDeQ3iK>fahx#O2Y3Mk08AF7{k_WqKf{Mu^x4f#BR$tgx~~m)UmNLa8t%Dm z9f-qOYKmN&r%uXOCFZF{Uy;U7lB$BZ4w~ardLJhHfN#J60HK6RDM0oBEenD(1{5Ps zJF*AYM|zqlt1#T%V(xXiV`mO4#fcoHAzz)8r%KFMjlLqKuOb52Y-fQc_+GsalYPKX zzyJW@0vQ0x5+DNIFyP{FyPD0t$P(Nb>FKl$b=ihTzpTR@wjqm%s};#`GFhfhmaC2E zRi@5X8Yo?#sZ78F^yhG)G(P3l2c-`L0T=)v5>Rd;uRREK&%if>4(n(3!1L8%9YPkO zbJRSH+KB;&)fKj-11^yyN>!AKAAOZ+Jg;M4T5^Jl$^qe><~u!rdk2^#U;qFCpf->x zdocOM!Gs`9X>%bvfWDj-m)-2N+gwi1p~E&dNPX`hB)A+WNsuD5**?n=L#qMY1F{6vYsgIkA(1Evsd5q?_J~0_ zbLg)y%?Bb0dj&v$RskR~P*y?mIg{B>7BiH7rvP0KL>`$;#(KEe1^@s60EiP}bE*XZ z002w~7ytkOfH(yV0000$oB{>_001CP`F{Zb0RR7=0$KQ_^d+kR0000 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++ + } + } + } +} diff --git a/linear_regression_series.go b/linear_regression_series.go index 41739b4..9c9756f 100644 --- a/linear_regression_series.go +++ b/linear_regression_series.go @@ -128,5 +128,5 @@ func (lrs *LinearRegressionSeries) computeCoefficients() { // Render renders the series. func (lrs *LinearRegressionSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) { style := lrs.Style.InheritFrom(defaults) - DrawLineSeries(r, canvasBox, xrange, yrange, style, lrs) + Draw.LineSeries(r, canvasBox, xrange, yrange, style, lrs) } diff --git a/macd_series.go b/macd_series.go index b174b74..b3b80c0 100644 --- a/macd_series.go +++ b/macd_series.go @@ -193,7 +193,7 @@ func (macds *MACDSignalSeries) ensureSignal() { // Render renders the series. func (macds *MACDSignalSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) { 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. @@ -285,5 +285,5 @@ func (macdl *MACDLineSeries) ensureEMASeries() { // Render renders the series. func (macdl *MACDLineSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) { style := macdl.Style.InheritFrom(defaults) - DrawLineSeries(r, canvasBox, xrange, yrange, style, macdl) + Draw.LineSeries(r, canvasBox, xrange, yrange, style, macdl) } diff --git a/pie_chart.go b/pie_chart.go index 363f4c3..4f3ce4a 100644 --- a/pie_chart.go +++ b/pie_chart.go @@ -99,14 +99,14 @@ func (pc PieChart) Render(rp RendererProvider, w io.Writer) error { } func (pc PieChart) drawBackground(r Renderer) { - DrawBox(r, Box{ + Draw.Box(r, Box{ Right: pc.GetWidth(), Bottom: pc.GetHeight(), }, pc.getBackgroundStyle()) } 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) { @@ -138,7 +138,7 @@ func (pc PieChart) drawSlices(r Renderer, canvasBox Box, values []Value) { var rads, delta, delta2, total float64 var lx, ly int 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) rads = PercentToRadians(total) @@ -155,7 +155,7 @@ func (pc PieChart) drawSlices(r Renderer, canvasBox Box, values []Value) { // draw the labels total = 0 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 { delta2 = PercentToRadians(total + (v.Value / 2.0)) delta2 = RadianAdd(delta2, _pi2) diff --git a/sma_series.go b/sma_series.go index 63a8708..9538d3b 100644 --- a/sma_series.go +++ b/sma_series.go @@ -86,5 +86,5 @@ func (sma SMASeries) getAverage(index int) float64 { // Render renders the series. func (sma SMASeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) { style := sma.Style.InheritFrom(defaults) - DrawLineSeries(r, canvasBox, xrange, yrange, style, sma) + Draw.LineSeries(r, canvasBox, xrange, yrange, style, sma) } diff --git a/stacked_bar_chart.go b/stacked_bar_chart.go index c479cf4..f15e21b 100644 --- a/stacked_bar_chart.go +++ b/stacked_bar_chart.go @@ -134,7 +134,7 @@ func (sbc StackedBarChart) drawBar(r Renderer, canvasBox Box, xoffset int, bar S for index, bv := range normalizedBarComponents { barHeight := int(bv.Value * float64(canvasBox.Height())) 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 } diff --git a/style.go b/style.go index 6742554..1f506d5 100644 --- a/style.go +++ b/style.go @@ -21,6 +21,10 @@ type Style struct { FontSize float64 FontColor drawing.Color Font *truetype.Font + + TextHorizontalAlign textHorizontalAlign + TextVerticalAlign textVerticalAlign + TextWrap textWrap } // IsZero returns if the object is set or not. @@ -185,8 +189,41 @@ func (s Style) GetPadding(defaults ...Box) Box { return s.Padding } -// PersistToRenderer passes the style onto a renderer. -func (s Style) PersistToRenderer(r Renderer) { +// GetTextHorizontalAlign returns the horizontal alignment. +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.SetStrokeWidth(s.GetStrokeWidth()) r.SetStrokeDashArray(s.GetStrokeDashArray()) @@ -196,6 +233,21 @@ func (s Style) PersistToRenderer(r Renderer) { 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. func (s Style) InheritFrom(defaults Style) (final Style) { 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.Font = s.GetFont(defaults.Font) 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 } -// SVG returns the style as a svg style string. -func (s Style) SVG(dpi float64) 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(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 { +// GetStrokeOptions returns the stroke components. +func (s Style) GetStrokeOptions() Style { return Style{ StrokeDashArray: s.StrokeDashArray, StrokeColor: s.StrokeColor, @@ -254,15 +273,15 @@ func (s Style) SVGStroke() Style { } } -// SVGFill returns the fill components. -func (s Style) SVGFill() Style { +// GetFillOptions returns the fill components. +func (s Style) GetFillOptions() Style { return Style{ FillColor: s.FillColor, } } -// SVGFillAndStroke returns the fill and stroke components. -func (s Style) SVGFillAndStroke() Style { +// GetFillAndStrokeOptions returns the fill and stroke components. +func (s Style) GetFillAndStrokeOptions() Style { return Style{ StrokeDashArray: s.StrokeDashArray, FillColor: s.FillColor, @@ -271,34 +290,14 @@ func (s Style) SVGFillAndStroke() Style { } } -// SVGText returns just the text components of the style. -func (s Style) SVGText() Style { +// GetTextOptions returns just the text components of the style. +func (s Style) GetTextOptions() Style { return Style{ - FontColor: s.FontColor, - FontSize: s.FontSize, + FontColor: s.FontColor, + 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 "" -} diff --git a/style_test.go b/style_test.go index 520692e..4fe8303 100644 --- a/style_test.go +++ b/style_test.go @@ -1,7 +1,6 @@ package chart import ( - "strings" "testing" "github.com/blendlabs/go-assert" @@ -146,29 +145,7 @@ func TestStyleWithDefaultsFrom(t *testing.T) { assert.Equal(set, coalesced) } -func TestStyleSVG(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) { +func TestStyleGetStrokeOptions(t *testing.T) { assert := assert.New(t) set := Style{ @@ -178,14 +155,14 @@ func TestStyleSVGStroke(t *testing.T) { FontColor: drawing.ColorWhite, Padding: DefaultBackgroundPadding, } - svgStroke := set.SVGStroke() + svgStroke := set.GetStrokeOptions() assert.False(svgStroke.StrokeColor.IsZero()) assert.NotZero(svgStroke.StrokeWidth) assert.True(svgStroke.FillColor.IsZero()) assert.True(svgStroke.FontColor.IsZero()) } -func TestStyleSVGFill(t *testing.T) { +func TestStyleGetFillOptions(t *testing.T) { assert := assert.New(t) set := Style{ @@ -195,14 +172,14 @@ func TestStyleSVGFill(t *testing.T) { FontColor: drawing.ColorWhite, Padding: DefaultBackgroundPadding, } - svgFill := set.SVGFill() + svgFill := set.GetFillOptions() assert.False(svgFill.FillColor.IsZero()) assert.Zero(svgFill.StrokeWidth) assert.True(svgFill.StrokeColor.IsZero()) assert.True(svgFill.FontColor.IsZero()) } -func TestStyleSVGFillAndStroke(t *testing.T) { +func TestStyleGetFillAndStrokeOptions(t *testing.T) { assert := assert.New(t) set := Style{ @@ -212,14 +189,14 @@ func TestStyleSVGFillAndStroke(t *testing.T) { FontColor: drawing.ColorWhite, Padding: DefaultBackgroundPadding, } - svgFillAndStroke := set.SVGFillAndStroke() + svgFillAndStroke := set.GetFillAndStrokeOptions() assert.False(svgFillAndStroke.FillColor.IsZero()) assert.NotZero(svgFillAndStroke.StrokeWidth) assert.False(svgFillAndStroke.StrokeColor.IsZero()) assert.True(svgFillAndStroke.FontColor.IsZero()) } -func TestStyleSVGText(t *testing.T) { +func TestStyleGetTextOptions(t *testing.T) { assert := assert.New(t) set := Style{ @@ -229,7 +206,7 @@ func TestStyleSVGText(t *testing.T) { FontColor: drawing.ColorWhite, Padding: DefaultBackgroundPadding, } - svgStroke := set.SVGText() + svgStroke := set.GetTextOptions() assert.True(svgStroke.StrokeColor.IsZero()) assert.Zero(svgStroke.StrokeWidth) assert.True(svgStroke.FillColor.IsZero()) diff --git a/text.go b/text.go new file mode 100644 index 0000000..7011095 --- /dev/null +++ b/text.go @@ -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 +} diff --git a/text_test.go b/text_test.go new file mode 100644 index 0000000..b1f577b --- /dev/null +++ b/text_test.go @@ -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]) +} diff --git a/time_series.go b/time_series.go index 5287cf7..df779eb 100644 --- a/time_series.go +++ b/time_series.go @@ -57,5 +57,5 @@ func (ts TimeSeries) GetYAxis() YAxisType { // Render renders the series. func (ts TimeSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) { style := ts.Style.InheritFrom(defaults) - DrawLineSeries(r, canvasBox, xrange, yrange, style, ts) + Draw.LineSeries(r, canvasBox, xrange, yrange, style, ts) } diff --git a/vector_renderer.go b/vector_renderer.go index 1f030be..3f3f359 100644 --- a/vector_renderer.go +++ b/vector_renderer.go @@ -110,30 +110,28 @@ func (vr *vectorRenderer) Close() { // Stroke draws the path with no fill. func (vr *vectorRenderer) Stroke() { - vr.drawPath(vr.s.SVGStroke()) + vr.drawPath(vr.s.GetStrokeOptions()) } // Fill draws the path with no stroke. func (vr *vectorRenderer) Fill() { - vr.drawPath(vr.s.SVGFill()) + vr.drawPath(vr.s.GetFillOptions()) } // FillStroke draws the path with both fill and stroke. func (vr *vectorRenderer) FillStroke() { - s := vr.s.SVGFillAndStroke() - vr.drawPath(s) + vr.drawPath(vr.s.GetFillAndStrokeOptions()) } // drawPath draws a path. 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 } // Circle implements the interface method. func (vr *vectorRenderer) Circle(radius float64, x, y int) { - style := vr.s.SVGFillAndStroke() - vr.c.Circle(x, y, int(radius), &style) + vr.c.Circle(x, y, int(radius), vr.s.GetFillAndStrokeOptions()) } // SetFont implements the interface method. @@ -153,8 +151,7 @@ func (vr *vectorRenderer) SetFontSize(size float64) { // Text draws a text blob. func (vr *vectorRenderer) Text(body string, x, y int) { - style := vr.s.SVGText() - vr.c.Text(x, y, body, &style) + vr.c.Text(x, y, body, vr.s.GetTextOptions()) } // 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(`\n`, c.width, c.height))) } -func (c *canvas) Path(d string, style *Style) { +func (c *canvas) Path(d string, style Style) { var strokeDashArrayProperty string if len(style.StrokeDashArray) > 0 { - strokeDashArrayProperty = style.SVGStrokeDashArray() + strokeDashArrayProperty = c.getStrokeDashArray(style) } - c.w.Write([]byte(fmt.Sprintf(`\n`, strokeDashArrayProperty, d, style.SVG(c.dpi)))) + c.w.Write([]byte(fmt.Sprintf(`\n`, strokeDashArrayProperty, d, c.styleAsSVG(style)))) } -func (c *canvas) Text(x, y int, body string, style *Style) { - c.w.Write([]byte(fmt.Sprintf(`%s`, x, y, style.SVG(c.dpi), body))) +func (c *canvas) Text(x, y int, body string, style Style) { + c.w.Write([]byte(fmt.Sprintf(`%s`, x, y, c.styleAsSVG(style), body))) } -func (c *canvas) Circle(x, y, r int, style *Style) { - c.w.Write([]byte(fmt.Sprintf(``, x, y, r, style.SVG(c.dpi)))) +func (c *canvas) Circle(x, y, r int, style Style) { + c.w.Write([]byte(fmt.Sprintf(``, x, y, r, c.styleAsSVG(style)))) } func (c *canvas) End() { c.w.Write([]byte("")) } + +// 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}, ";") +} diff --git a/vector_renderer_test.go b/vector_renderer_test.go index a9020ef..f802970 100644 --- a/vector_renderer_test.go +++ b/vector_renderer_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/blendlabs/go-assert" + "github.com/wcharczuk/go-chart/drawing" ) func TestVectorRendererPath(t *testing.T) { @@ -50,3 +51,27 @@ func TestVectorRendererMeasureText(t *testing.T) { assert.Equal(21, tb.Width()) 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)")) +} diff --git a/xaxis.go b/xaxis.go index 5ebe2cb..5a1ddd4 100644 --- a/xaxis.go +++ b/xaxis.go @@ -54,7 +54,7 @@ func (xa XAxis) GetGridLines(ticks []Tick) []GridLine { // Measure returns the bounds of the axis. 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)) 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 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.LineTo(canvasBox.Right, canvasBox.Bottom) diff --git a/yaxis.go b/yaxis.go index 0d81f56..7c1e26f 100644 --- a/yaxis.go +++ b/yaxis.go @@ -61,7 +61,7 @@ func (ya YAxis) GetGridLines(ticks []Tick) []GridLine { // Measure returns the bounds of the axis. 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)) @@ -104,7 +104,7 @@ func (ya YAxis) Measure(r Renderer, canvasBox Box, ra Range, defaults Style, tic // Render renders the axis. 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))