text options!
This commit is contained in:
parent
b3386853bb
commit
d84d6790c0
25 changed files with 526 additions and 287 deletions
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
4
chart.go
4
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) {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,3 @@
|
|||
// Copyright 2010 The draw2d Authors. All rights reserved.
|
||||
// created: 13/12/2010 by Laurent Le Goff
|
||||
|
||||
package drawing
|
||||
|
||||
import (
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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"},
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 32 KiB |
116
legend.go
Normal file
116
legend.go
Normal 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++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
143
style.go
143
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 ""
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
|
|
153
text.go
Normal file
153
text.go
Normal 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
32
text_test.go
Normal 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])
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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(`<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
|
||||
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) {
|
||||
c.w.Write([]byte(fmt.Sprintf(`<text x="%d" y="%d" style="%s">%s</text>`, x, y, style.SVG(c.dpi), body)))
|
||||
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, c.styleAsSVG(style), body)))
|
||||
}
|
||||
|
||||
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))))
|
||||
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, c.styleAsSVG(style))))
|
||||
}
|
||||
|
||||
func (c *canvas) End() {
|
||||
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}, ";")
|
||||
}
|
||||
|
|
|
@ -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)"))
|
||||
}
|
||||
|
|
4
xaxis.go
4
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)
|
||||
|
|
4
yaxis.go
4
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))
|
||||
|
||||
|
|
Loading…
Reference in a new issue