vector renderer works
This commit is contained in:
parent
b600cb1994
commit
3d9cf0da0c
5 changed files with 129 additions and 16 deletions
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
13
pie_chart.go
13
pie_chart.go
|
@ -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
48
util.go
|
@ -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
|
||||||
|
}
|
||||||
|
|
57
util_test.go
57
util_test.go
|
@ -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))
|
||||||
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in a new issue