text rotation is sucky.
This commit is contained in:
parent
102f7a8aa3
commit
b78f2327aa
8 changed files with 140 additions and 12 deletions
48
_examples/text_rotation/main.go
Normal file
48
_examples/text_rotation/main.go
Normal 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
14
box.go
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
16
box_test.go
16
box_test.go
|
@ -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
17
math.go
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
11
math_test.go
11
math_test.go
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
19
style.go
19
style.go
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
20
yaxis.go
20
yaxis.go
|
@ -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 {
|
||||||
|
|
Loading…
Reference in a new issue