text rotation is sucky.
This commit is contained in:
parent
102f7a8aa3
commit
b78f2327aa
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
|
||||
}
|
||||
|
||||
// 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(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
|
||||
// by the radius and the angle.
|
||||
func (m mathUtil) CirclePoint(cx, cy int, radius, angleRadians float64) (x, y int) {
|
||||
x = cx + int(radius*math.Sin(angleRadians))
|
||||
y = cy - int(radius*math.Cos(angleRadians))
|
||||
// by the radius and the theta.
|
||||
func (m mathUtil) CirclePoint(cx, cy int, radius, thetaRadians float64) (x, y int) {
|
||||
x = cx + int(radius*math.Sin(thetaRadians))
|
||||
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
|
||||
}
|
||||
|
|
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))
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
return Box{
|
||||
textBox := Box{
|
||||
Top: int(math.Ceil(t)),
|
||||
Left: int(math.Ceil(l)),
|
||||
Right: int(math.Ceil(r)),
|
||||
Bottom: int(math.Ceil(b)),
|
||||
}
|
||||
if rr.rotateRadians == 0 {
|
||||
return textBox
|
||||
}
|
||||
|
||||
return textBox.Rotate(rr.rotateRadians)
|
||||
}
|
||||
|
||||
// SetTextRotation sets a text rotation.
|
||||
|
|
19
style.go
19
style.go
|
@ -33,6 +33,7 @@ type Style struct {
|
|||
TextVerticalAlign TextVerticalAlign
|
||||
TextWrap TextWrap
|
||||
TextLineSpacing int
|
||||
TextRotationDegrees float64
|
||||
}
|
||||
|
||||
// IsZero returns if the object is set or not.
|
||||
|
@ -241,6 +242,16 @@ func (s Style) GetTextLineSpacing(defaults ...int) int {
|
|||
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.
|
||||
func (s Style) WriteToRenderer(r Renderer) {
|
||||
r.SetStrokeColor(s.GetStrokeColor())
|
||||
|
@ -250,6 +261,12 @@ func (s Style) WriteToRenderer(r Renderer) {
|
|||
r.SetFont(s.GetFont())
|
||||
r.SetFontColor(s.GetFontColor())
|
||||
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.
|
||||
|
@ -281,6 +298,7 @@ func (s Style) InheritFrom(defaults Style) (final Style) {
|
|||
final.TextVerticalAlign = s.GetTextVerticalAlign(defaults.TextVerticalAlign)
|
||||
final.TextWrap = s.GetTextWrap(defaults.TextWrap)
|
||||
final.TextLineSpacing = s.GetTextLineSpacing(defaults.TextLineSpacing)
|
||||
final.TextRotationDegrees = s.GetTextRotationDegrees(defaults.TextRotationDegrees)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -320,5 +338,6 @@ func (s Style) GetTextOptions() Style {
|
|||
TextVerticalAlign: s.TextVerticalAlign,
|
||||
TextWrap: s.TextWrap,
|
||||
TextLineSpacing: s.TextLineSpacing,
|
||||
TextRotationDegrees: s.TextRotationDegrees,
|
||||
}
|
||||
}
|
||||
|
|
20
yaxis.go
20
yaxis.go
|
@ -20,6 +20,7 @@ type YAxis struct {
|
|||
ValueFormatter ValueFormatter
|
||||
Range Range
|
||||
|
||||
TickStyle Style
|
||||
Ticks []Tick
|
||||
GridLines []GridLine
|
||||
|
||||
|
@ -42,6 +43,11 @@ func (ya YAxis) GetStyle() Style {
|
|||
return ya.Style
|
||||
}
|
||||
|
||||
// GetTickStyle returns the tick style.
|
||||
func (ya YAxis) GetTickStyle() Style {
|
||||
return ya.TickStyle
|
||||
}
|
||||
|
||||
// GetTicks returns the ticks for a series.
|
||||
// The coalesce priority is:
|
||||
// - 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.
|
||||
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))
|
||||
|
||||
var tx int
|
||||
|
@ -79,9 +83,12 @@ func (ya YAxis) Measure(r Renderer, canvasBox Box, ra Range, defaults Style, tic
|
|||
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 maxTextHeight int
|
||||
for _, t := range ticks {
|
||||
|
||||
v := t.Value
|
||||
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
|
||||
for _, t := range ticks {
|
||||
|
||||
v := t.Value
|
||||
ly := canvasBox.Bottom - ra.Translate(v)
|
||||
|
||||
ya.TickStyle.InheritFrom(ya.Style.InheritFrom(defaults)).WriteToRenderer(r)
|
||||
tb := r.MeasureText(t.Label)
|
||||
|
||||
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.ClearTextRotation()
|
||||
|
||||
r.MoveTo(lx, ly)
|
||||
if ya.AxisType == YAxisPrimary {
|
||||
|
@ -168,12 +178,9 @@ func (ya YAxis) Render(r Renderer, canvasBox Box, ra Range, defaults Style, tick
|
|||
r.Stroke()
|
||||
}
|
||||
|
||||
nameStyle := ya.NameStyle.InheritFrom(defaults)
|
||||
nameStyle := ya.NameStyle.InheritFrom(defaults.InheritFrom(Style{TextRotationDegrees: 90}))
|
||||
if ya.NameStyle.Show && len(ya.Name) > 0 {
|
||||
nameStyle.GetTextOptions().WriteToRenderer(r)
|
||||
|
||||
r.SetTextRotation(Math.DegreesToRadians(90))
|
||||
|
||||
tb := r.MeasureText(ya.Name)
|
||||
|
||||
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)
|
||||
|
||||
r.Text(ya.Name, tx, ty)
|
||||
r.ClearTextRotation()
|
||||
}
|
||||
|
||||
if ya.Zero.Style.Show {
|
||||
|
|
Loading…
Reference in New Issue
Block a user