vector renderer works

This commit is contained in:
Will Charczuk 2016-07-28 13:22:18 -07:00
parent b600cb1994
commit 3d9cf0da0c
5 changed files with 129 additions and 16 deletions

View file

@ -14,17 +14,18 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
FillColor: chart.ColorLightGray, FillColor: chart.ColorLightGray,
}, },
Values: []chart.PieChartValue{ Values: []chart.PieChartValue{
{Value: 0.3, Label: "Blue"}, {Value: 0.2, Label: "Blue"},
{Value: 0.2, Label: "Green"}, {Value: 0.2, Label: "Green"},
{Value: 0.2, Label: "Gray"}, {Value: 0.2, Label: "Gray"},
{Value: 0.1, Label: "Orange"}, {Value: 0.1, Label: "Orange"},
{Value: 0.1, Label: "HEANG"},
{Value: 0.1, Label: "??"}, {Value: 0.1, Label: "??"},
{Value: 0.1, Label: "!!"}, {Value: 0.1, Label: "!!"},
}, },
} }
res.Header().Set("Content-Type", "image/png") res.Header().Set("Content-Type", "image/svg+xml")
err := pie.Render(chart.PNG, res) err := pie.Render(chart.SVG, res)
if err != nil { if err != nil {
fmt.Printf("Error rendering pie chart: %v\n", err) fmt.Printf("Error rendering pie chart: %v\n", err)
} }

View file

@ -3,7 +3,6 @@ package chart
import ( import (
"errors" "errors"
"io" "io"
"math"
"github.com/golang/freetype/truetype" "github.com/golang/freetype/truetype"
) )
@ -142,14 +141,15 @@ func (pc PieChart) drawSlices(r Renderer, canvasBox Box, values []PieChartValue)
cx, cy := canvasBox.Center() cx, cy := canvasBox.Center()
diameter := MinInt(canvasBox.Width(), canvasBox.Height()) diameter := MinInt(canvasBox.Width(), canvasBox.Height())
radius := float64(diameter >> 1) radius := float64(diameter >> 1)
radius2 := (radius * 2.0) / 3.0 labelRadius := (radius * 2.0) / 3.0
// draw the pie slices
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)).PersistToRenderer(r)
r.MoveTo(cx, cy)
r.MoveTo(cx, cy)
rads = PercentToRadians(total) rads = PercentToRadians(total)
delta = PercentToRadians(v.Value) delta = PercentToRadians(v.Value)
@ -161,13 +161,14 @@ func (pc PieChart) drawSlices(r Renderer, canvasBox Box, values []PieChartValue)
total = total + v.Value total = total + v.Value
} }
// 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)).PersistToRenderer(r)
if len(v.Label) > 0 { if len(v.Label) > 0 {
delta2 = RadianAdd(PercentToRadians(total+(v.Value/2.0)), _pi2) delta2 = PercentToRadians(total + (v.Value / 2.0))
lx = cx + int(radius2*math.Sin(delta2)) delta2 = RadianAdd(delta2, _pi2)
ly = cy - int(radius2*math.Cos(delta2)) lx, ly = CirclePoint(cx, cy, labelRadius, delta2)
tb := r.MeasureText(v.Label) tb := r.MeasureText(v.Label)
lx = lx - (tb.Width() >> 1) lx = lx - (tb.Width() >> 1)

48
util.go
View file

@ -191,18 +191,30 @@ func PercentDifference(v1, v2 float64) float64 {
return (v2 - v1) / v1 return (v2 - v1) / v1
} }
// DegreesToRadians returns degrees as radians.
func DegreesToRadians(degrees float64) float64 {
return degrees * (math.Pi / 180.0)
}
const ( const (
_pi = math.Pi
_2pi = 2 * math.Pi _2pi = 2 * math.Pi
_3pi4 = (3 * math.Pi) / 4.0 _3pi4 = (3 * math.Pi) / 4.0
_4pi3 = (4 * math.Pi) / 3.0
_3pi2 = (3 * math.Pi) / 2.0
_5pi4 = (5 * math.Pi) / 4.0
_7pi4 = (7 * math.Pi) / 4.0
_pi2 = math.Pi / 2.0 _pi2 = math.Pi / 2.0
_pi4 = math.Pi / 4.0 _pi4 = math.Pi / 4.0
_d2r = (math.Pi / 180.0)
_r2d = (180.0 / math.Pi)
) )
// DegreesToRadians returns degrees as radians.
func DegreesToRadians(degrees float64) float64 {
return degrees * _d2r
}
// RadiansToDegrees translates a radian value to a degree value.
func RadiansToDegrees(value float64) float64 {
return math.Mod(value, _2pi) * _r2d
}
// PercentToRadians converts a normalized value (0,1) to radians. // PercentToRadians converts a normalized value (0,1) to radians.
func PercentToRadians(pct float64) float64 { func PercentToRadians(pct float64) float64 {
return DegreesToRadians(360.0 * pct) return DegreesToRadians(360.0 * pct)
@ -214,7 +226,31 @@ func RadianAdd(base, delta float64) float64 {
if value > _2pi { if value > _2pi {
return math.Mod(value, _2pi) return math.Mod(value, _2pi)
} else if value < 0 { } else if value < 0 {
return _2pi + value return math.Mod(_2pi+value, _2pi)
} }
return value return value
} }
// DegreesAdd adds a delta to a base in radians.
func DegreesAdd(baseDegrees, deltaDegrees float64) float64 {
value := baseDegrees + deltaDegrees
if value > _2pi {
return math.Mod(value, 360.0)
} else if value < 0 {
return math.Mod(360.0+value, 360.0)
}
return value
}
// DegreesToCompass returns the degree value in compass / clock orientation.
func DegreesToCompass(deg float64) float64 {
return DegreesAdd(deg, -90.0)
}
// CirclePoint returns the absolute position of a circle diameter point given
// by the radius and the angle.
func CirclePoint(cx, cy int, radius, angleRadians float64) (x, y int) {
x = cx + int(radius*math.Sin(angleRadians))
y = cy - int(radius*math.Cos(angleRadians))
return
}

View file

@ -100,3 +100,60 @@ func TestPercentDifference(t *testing.T) {
assert.Equal(0.5, PercentDifference(1.0, 1.5)) assert.Equal(0.5, PercentDifference(1.0, 1.5))
assert.Equal(-0.5, PercentDifference(2.0, 1.0)) assert.Equal(-0.5, PercentDifference(2.0, 1.0))
} }
var (
_degreesToRadians = map[float64]float64{
0: 0, // !_2pi b/c no irrational nums in floats.
45: _pi4,
90: _pi2,
135: _3pi4,
180: _pi,
225: _5pi4,
270: _3pi2,
315: _7pi4,
}
_compassToRadians = map[float64]float64{
0: _pi2,
45: _pi4,
90: 0, // !_2pi b/c no irrational nums in floats.
135: _7pi4,
180: _3pi2,
225: _5pi4,
270: _pi,
315: _3pi4,
}
)
func TestDegreesToRadians(t *testing.T) {
assert := assert.New(t)
for d, r := range _degreesToRadians {
assert.Equal(r, DegreesToRadians(d))
}
}
func TestPercentToRadians(t *testing.T) {
assert := assert.New(t)
for d, r := range _degreesToRadians {
assert.Equal(r, PercentToRadians(d/360.0))
}
}
func TestRadiansToDegrees(t *testing.T) {
assert := assert.New(t)
for d, r := range _degreesToRadians {
assert.Equal(d, RadiansToDegrees(r))
}
}
func TestRadianAdd(t *testing.T) {
assert := assert.New(t)
assert.Equal(_pi, RadianAdd(_pi2, _pi2))
assert.Equal(_3pi2, RadianAdd(_pi2, _pi))
assert.Equal(_pi, RadianAdd(_pi, _2pi))
assert.Equal(_pi, RadianAdd(_pi, -_2pi))
}

View file

@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"io" "io"
"math"
"strings" "strings"
"golang.org/x/image/font" "golang.org/x/image/font"
@ -82,7 +83,24 @@ func (vr *vectorRenderer) QuadCurveTo(cx, cy, x, y int) {
} }
func (vr *vectorRenderer) ArcTo(cx, cy int, rx, ry, startAngle, delta float64) { func (vr *vectorRenderer) ArcTo(cx, cy int, rx, ry, startAngle, delta float64) {
vr.p = append(vr.p, fmt.Sprintf("A %d %d %0.2f 1 1 %d %d", int(rx), int(ry), delta, cx, cy)) startAngle = RadianAdd(startAngle, _pi2)
endAngle := RadianAdd(startAngle, delta)
startx := cx + int(rx*math.Sin(startAngle))
starty := cy - int(ry*math.Cos(startAngle))
if len(vr.p) > 0 {
vr.p = append(vr.p, fmt.Sprintf("L %d %d", startx, starty))
} else {
vr.p = append(vr.p, fmt.Sprintf("M %d %d", startx, starty))
}
endx := cx + int(rx*math.Sin(endAngle))
endy := cy - int(ry*math.Cos(endAngle))
dd := RadiansToDegrees(delta)
vr.p = append(vr.p, fmt.Sprintf("A %d %d %0.2f 0 1 %d %d", int(rx), int(ry), dd, endx, endy))
} }
// Close closes a shape. // Close closes a shape.