text rotation is sucky.

This commit is contained in:
Will Charczuk 2016-08-31 22:11:52 -07:00
parent 102f7a8aa3
commit b78f2327aa
8 changed files with 140 additions and 12 deletions

View File

@ -0,0 +1,48 @@
package main
import (
"net/http"
"github.com/wcharczuk/go-chart"
)
func drawChart(res http.ResponseWriter, req *http.Request) {
/*
In this example we set a rotation on the style for the custom ticks from the `custom_ticks` example.
*/
graph := chart.Chart{
YAxis: chart.YAxis{
Style: chart.Style{
Show: true,
TextRotationDegrees: 45.0,
},
Range: &chart.ContinuousRange{
Min: 0.0,
Max: 4.0,
},
Ticks: []chart.Tick{
{0.0, "0.00"},
{2.0, "2.00"},
{4.0, "4.00"},
{6.0, "6.00"},
{8.0, "Eight"},
{10.0, "Ten"},
},
},
Series: []chart.Series{
chart.ContinuousSeries{
XValues: []float64{1.0, 2.0, 3.0, 4.0, 5.0},
YValues: []float64{1.0, 2.0, 3.0, 4.0, 5.0},
},
},
}
res.Header().Set("Content-Type", "image/png")
graph.Render(chart.PNG, res)
}
func main() {
http.HandleFunc("/", drawChart)
http.ListenAndServe(":8080", nil)
}

14
box.go
View File

@ -219,3 +219,17 @@ func (b Box) OuterConstrain(bounds, other Box) Box {
} }
return newBox return newBox
} }
// Rotate rotates a box's corners by a given radian rotation angle.
func (b Box) Rotate(radians float64) Box {
cx, cy := b.Center()
ltx, lty := Math.RotateCoordinate(cx, cy, b.Left, b.Top, radians)
rbx, rby := Math.RotateCoordinate(cx, cy, b.Right, b.Bottom, radians)
return Box{
Top: lty,
Left: ltx,
Right: rbx,
Bottom: rby,
}
}

View File

@ -143,3 +143,19 @@ func TestBoxShift(t *testing.T) {
assert.Equal(11, shifted.Right) assert.Equal(11, shifted.Right)
assert.Equal(12, shifted.Bottom) assert.Equal(12, shifted.Bottom)
} }
func TestBoxRotate(t *testing.T) {
assert := assert.New(t)
b := Box{
Top: 5,
Left: 5,
Right: 20,
Bottom: 10,
}
rotated := b.Rotate(Math.DegreesToRadians(45))
assert.Equal(1, rotated.Top)
assert.Equal(4, rotated.Left)
assert.Equal(11, rotated.Right)
assert.Equal(15, rotated.Bottom)
}

17
math.go
View File

@ -214,9 +214,18 @@ func (m mathUtil) DegreesToCompass(deg float64) float64 {
} }
// CirclePoint returns the absolute position of a circle diameter point given // CirclePoint returns the absolute position of a circle diameter point given
// by the radius and the angle. // by the radius and the theta.
func (m mathUtil) CirclePoint(cx, cy int, radius, angleRadians float64) (x, y int) { func (m mathUtil) CirclePoint(cx, cy int, radius, thetaRadians float64) (x, y int) {
x = cx + int(radius*math.Sin(angleRadians)) x = cx + int(radius*math.Sin(thetaRadians))
y = cy - int(radius*math.Cos(angleRadians)) y = cy - int(radius*math.Cos(thetaRadians))
return
}
func (m mathUtil) RotateCoordinate(cx, cy, x, y int, thetaRadians float64) (rx, ry int) {
tempX, tempY := float64(x-cx), float64(y-cy)
rotatedX := int(math.Ceil(tempX*math.Cos(thetaRadians) - tempY*math.Sin(thetaRadians)))
rotatedY := int(math.Ceil(tempX*math.Sin(thetaRadians) + tempY*math.Cos(thetaRadians)))
rx = rotatedX + cy
ry = rotatedY + cy
return return
} }

View File

@ -160,3 +160,14 @@ func TestRadianAdd(t *testing.T) {
assert.Equal(_pi, Math.RadianAdd(_pi, _2pi)) assert.Equal(_pi, Math.RadianAdd(_pi, _2pi))
assert.Equal(_pi, Math.RadianAdd(_pi, -_2pi)) assert.Equal(_pi, Math.RadianAdd(_pi, -_2pi))
} }
func TestRotateCoordinate(t *testing.T) {
assert := assert.New(t)
cx, cy := 10, 10
x, y := 5, 5
rx, ry := Math.RotateCoordinate(cx, cy, x, y, Math.DegreesToRadians(45))
assert.Equal(10, rx)
assert.Equal(3, ry)
}

View File

@ -177,12 +177,17 @@ func (rr *rasterRenderer) MeasureText(body string) Box {
t = 0 t = 0
} }
return Box{ textBox := Box{
Top: int(math.Ceil(t)), Top: int(math.Ceil(t)),
Left: int(math.Ceil(l)), Left: int(math.Ceil(l)),
Right: int(math.Ceil(r)), Right: int(math.Ceil(r)),
Bottom: int(math.Ceil(b)), Bottom: int(math.Ceil(b)),
} }
if rr.rotateRadians == 0 {
return textBox
}
return textBox.Rotate(rr.rotateRadians)
} }
// SetTextRotation sets a text rotation. // SetTextRotation sets a text rotation.

View File

@ -33,6 +33,7 @@ type Style struct {
TextVerticalAlign TextVerticalAlign TextVerticalAlign TextVerticalAlign
TextWrap TextWrap TextWrap TextWrap
TextLineSpacing int TextLineSpacing int
TextRotationDegrees float64
} }
// IsZero returns if the object is set or not. // IsZero returns if the object is set or not.
@ -241,6 +242,16 @@ func (s Style) GetTextLineSpacing(defaults ...int) int {
return s.TextLineSpacing return s.TextLineSpacing
} }
// GetTextRotationDegrees returns the text rotation in degrees.
func (s Style) GetTextRotationDegrees(defaults ...float64) float64 {
if s.TextRotationDegrees == 0 {
if len(defaults) > 0 {
return defaults[0]
}
}
return s.TextRotationDegrees
}
// WriteToRenderer passes the style's options to a renderer. // WriteToRenderer passes the style's options to a renderer.
func (s Style) WriteToRenderer(r Renderer) { func (s Style) WriteToRenderer(r Renderer) {
r.SetStrokeColor(s.GetStrokeColor()) r.SetStrokeColor(s.GetStrokeColor())
@ -250,6 +261,12 @@ func (s Style) WriteToRenderer(r Renderer) {
r.SetFont(s.GetFont()) r.SetFont(s.GetFont())
r.SetFontColor(s.GetFontColor()) r.SetFontColor(s.GetFontColor())
r.SetFontSize(s.GetFontSize()) r.SetFontSize(s.GetFontSize())
if s.GetTextRotationDegrees() == 0 {
r.ClearTextRotation()
} else {
r.SetTextRotation(Math.DegreesToRadians(s.GetTextRotationDegrees()))
}
} }
// WriteDrawingOptionsToRenderer passes just the drawing style options to a renderer. // WriteDrawingOptionsToRenderer passes just the drawing style options to a renderer.
@ -281,6 +298,7 @@ func (s Style) InheritFrom(defaults Style) (final Style) {
final.TextVerticalAlign = s.GetTextVerticalAlign(defaults.TextVerticalAlign) final.TextVerticalAlign = s.GetTextVerticalAlign(defaults.TextVerticalAlign)
final.TextWrap = s.GetTextWrap(defaults.TextWrap) final.TextWrap = s.GetTextWrap(defaults.TextWrap)
final.TextLineSpacing = s.GetTextLineSpacing(defaults.TextLineSpacing) final.TextLineSpacing = s.GetTextLineSpacing(defaults.TextLineSpacing)
final.TextRotationDegrees = s.GetTextRotationDegrees(defaults.TextRotationDegrees)
return return
} }
@ -320,5 +338,6 @@ func (s Style) GetTextOptions() Style {
TextVerticalAlign: s.TextVerticalAlign, TextVerticalAlign: s.TextVerticalAlign,
TextWrap: s.TextWrap, TextWrap: s.TextWrap,
TextLineSpacing: s.TextLineSpacing, TextLineSpacing: s.TextLineSpacing,
TextRotationDegrees: s.TextRotationDegrees,
} }
} }

View File

@ -20,6 +20,7 @@ type YAxis struct {
ValueFormatter ValueFormatter ValueFormatter ValueFormatter
Range Range Range Range
TickStyle Style
Ticks []Tick Ticks []Tick
GridLines []GridLine GridLines []GridLine
@ -42,6 +43,11 @@ func (ya YAxis) GetStyle() Style {
return ya.Style return ya.Style
} }
// GetTickStyle returns the tick style.
func (ya YAxis) GetTickStyle() Style {
return ya.TickStyle
}
// GetTicks returns the ticks for a series. // GetTicks returns the ticks for a series.
// The coalesce priority is: // The coalesce priority is:
// - User Supplied Ticks (i.e. Ticks array on the axis itself). // - User Supplied Ticks (i.e. Ticks array on the axis itself).
@ -68,8 +74,6 @@ func (ya YAxis) GetGridLines(ticks []Tick) []GridLine {
// Measure returns the bounds of the axis. // Measure returns the bounds of the axis.
func (ya YAxis) Measure(r Renderer, canvasBox Box, ra Range, defaults Style, ticks []Tick) Box { func (ya YAxis) Measure(r Renderer, canvasBox Box, ra Range, defaults Style, ticks []Tick) Box {
ya.Style.InheritFrom(defaults).WriteToRenderer(r)
sort.Sort(Ticks(ticks)) sort.Sort(Ticks(ticks))
var tx int var tx int
@ -79,9 +83,12 @@ func (ya YAxis) Measure(r Renderer, canvasBox Box, ra Range, defaults Style, tic
tx = canvasBox.Left - DefaultYAxisMargin tx = canvasBox.Left - DefaultYAxisMargin
} }
ya.TickStyle.InheritFrom(ya.Style.InheritFrom(defaults)).WriteToRenderer(r)
var minx, maxx, miny, maxy = math.MaxInt32, 0, math.MaxInt32, 0 var minx, maxx, miny, maxy = math.MaxInt32, 0, math.MaxInt32, 0
var maxTextHeight int var maxTextHeight int
for _, t := range ticks { for _, t := range ticks {
v := t.Value v := t.Value
ly := canvasBox.Bottom - ra.Translate(v) ly := canvasBox.Bottom - ra.Translate(v)
@ -142,9 +149,11 @@ func (ya YAxis) Render(r Renderer, canvasBox Box, ra Range, defaults Style, tick
var maxTextWidth int var maxTextWidth int
for _, t := range ticks { for _, t := range ticks {
v := t.Value v := t.Value
ly := canvasBox.Bottom - ra.Translate(v) ly := canvasBox.Bottom - ra.Translate(v)
ya.TickStyle.InheritFrom(ya.Style.InheritFrom(defaults)).WriteToRenderer(r)
tb := r.MeasureText(t.Label) tb := r.MeasureText(t.Label)
if tb.Width() > maxTextWidth { if tb.Width() > maxTextWidth {
@ -158,6 +167,7 @@ func (ya YAxis) Render(r Renderer, canvasBox Box, ra Range, defaults Style, tick
} }
r.Text(t.Label, finalTextX, finalTextY) r.Text(t.Label, finalTextX, finalTextY)
r.ClearTextRotation()
r.MoveTo(lx, ly) r.MoveTo(lx, ly)
if ya.AxisType == YAxisPrimary { if ya.AxisType == YAxisPrimary {
@ -168,12 +178,9 @@ func (ya YAxis) Render(r Renderer, canvasBox Box, ra Range, defaults Style, tick
r.Stroke() r.Stroke()
} }
nameStyle := ya.NameStyle.InheritFrom(defaults) nameStyle := ya.NameStyle.InheritFrom(defaults.InheritFrom(Style{TextRotationDegrees: 90}))
if ya.NameStyle.Show && len(ya.Name) > 0 { if ya.NameStyle.Show && len(ya.Name) > 0 {
nameStyle.GetTextOptions().WriteToRenderer(r) nameStyle.GetTextOptions().WriteToRenderer(r)
r.SetTextRotation(Math.DegreesToRadians(90))
tb := r.MeasureText(ya.Name) tb := r.MeasureText(ya.Name)
var tx int var tx int
@ -186,7 +193,6 @@ func (ya YAxis) Render(r Renderer, canvasBox Box, ra Range, defaults Style, tick
ty := canvasBox.Bottom - (canvasBox.Height()>>1 + tb.Width()>>1) ty := canvasBox.Bottom - (canvasBox.Height()>>1 + tb.Width()>>1)
r.Text(ya.Name, tx, ty) r.Text(ya.Name, tx, ty)
r.ClearTextRotation()
} }
if ya.Zero.Style.Show { if ya.Zero.Style.Show {