text rotation works, ish.

This commit is contained in:
Will Charczuk 2016-10-21 12:44:37 -07:00
parent 53280b9258
commit f800bc387b
15 changed files with 281 additions and 131 deletions

View File

@ -2,23 +2,33 @@ package main
import ( import (
"net/http" "net/http"
"strconv"
"strings" "strings"
"time" "time"
util "github.com/blendlabs/go-util"
"github.com/wcharczuk/go-chart" "github.com/wcharczuk/go-chart"
) )
func parseInt(str string) int {
v, _ := strconv.Atoi(str)
return v
}
func parseFloat64(str string) float64 {
v, _ := strconv.ParseFloat(str, 64)
return v
}
func readData() ([]time.Time, []float64) { func readData() ([]time.Time, []float64) {
var xvalues []time.Time var xvalues []time.Time
var yvalues []float64 var yvalues []float64
util.ReadFileByLines("requests.csv", func(line string) { chart.File.ReadByLines("requests.csv", func(line string) {
parts := strings.Split(line, ",") parts := strings.Split(line, ",")
year := util.String.ParseInt(parts[0]) year := parseInt(parts[0])
month := util.String.ParseInt(parts[1]) month := parseInt(parts[1])
day := util.String.ParseInt(parts[2]) day := parseInt(parts[2])
hour := util.String.ParseInt(parts[3]) hour := parseInt(parts[3])
elapsedMillis := util.String.ParseFloat64(parts[4]) elapsedMillis := parseFloat64(parts[4])
xvalues = append(xvalues, time.Date(year, time.Month(month), day, hour, 0, 0, 0, time.UTC)) xvalues = append(xvalues, time.Date(year, time.Month(month), day, hour, 0, 0, 0, time.UTC))
yvalues = append(yvalues, elapsedMillis) yvalues = append(yvalues, elapsedMillis)
}) })
@ -27,12 +37,12 @@ func readData() ([]time.Time, []float64) {
func releases() []chart.GridLine { func releases() []chart.GridLine {
return []chart.GridLine{ return []chart.GridLine{
{Value: chart.TimeToFloat64(time.Date(2016, 8, 1, 9, 30, 0, 0, time.UTC))}, {Value: chart.Time.ToFloat64(time.Date(2016, 8, 1, 9, 30, 0, 0, time.UTC))},
{Value: chart.TimeToFloat64(time.Date(2016, 8, 2, 9, 30, 0, 0, time.UTC))}, {Value: chart.Time.ToFloat64(time.Date(2016, 8, 2, 9, 30, 0, 0, time.UTC))},
{Value: chart.TimeToFloat64(time.Date(2016, 8, 3, 9, 30, 0, 0, time.UTC))}, {Value: chart.Time.ToFloat64(time.Date(2016, 8, 3, 9, 30, 0, 0, time.UTC))},
{Value: chart.TimeToFloat64(time.Date(2016, 8, 4, 9, 30, 0, 0, time.UTC))}, {Value: chart.Time.ToFloat64(time.Date(2016, 8, 4, 9, 30, 0, 0, time.UTC))},
{Value: chart.TimeToFloat64(time.Date(2016, 8, 5, 9, 30, 0, 0, time.UTC))}, {Value: chart.Time.ToFloat64(time.Date(2016, 8, 5, 9, 30, 0, 0, time.UTC))},
{Value: chart.TimeToFloat64(time.Date(2016, 8, 6, 9, 30, 0, 0, time.UTC))}, {Value: chart.Time.ToFloat64(time.Date(2016, 8, 6, 9, 30, 0, 0, time.UTC))},
} }
} }
@ -78,13 +88,19 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
Style: chart.StyleShow(), Style: chart.StyleShow(),
}, },
XAxis: chart.XAxis{ XAxis: chart.XAxis{
Style: chart.StyleShow(), Style: chart.Style{
Show: true,
},
ValueFormatter: chart.TimeHourValueFormatter, ValueFormatter: chart.TimeHourValueFormatter,
GridMajorStyle: chart.Style{ GridMajorStyle: chart.Style{
Show: true, Show: true,
StrokeColor: chart.ColorAlternateGray, StrokeColor: chart.ColorAlternateGray,
StrokeWidth: 1.0, StrokeWidth: 1.0,
}, },
TickPosition: chart.TickPositionBetweenTicks,
TickStyle: chart.Style{
TextRotationDegrees: 45,
},
GridLines: releases(), GridLines: releases(),
}, },
Series: []chart.Series{ Series: []chart.Series{

View File

@ -60,8 +60,8 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
}, },
} }
res.Header().Set("Content-Type", "image/svg+xml") res.Header().Set("Content-Type", "image/png")
graph.Render(chart.SVG, res) graph.Render(chart.PNG, res)
} }
func xvalues() []time.Time { func xvalues() []time.Time {

View File

@ -4,60 +4,47 @@ import (
"net/http" "net/http"
"github.com/wcharczuk/go-chart" "github.com/wcharczuk/go-chart"
"github.com/wcharczuk/go-chart/drawing"
) )
func drawChart(res http.ResponseWriter, req *http.Request) { func drawChart(res http.ResponseWriter, req *http.Request) {
/* f, _ := chart.GetDefaultFont()
In this example we set a rotation on the style for the custom ticks from the `custom_ticks` example. r, _ := chart.PNG(1024, 1024)
*/
graph := chart.Chart{ chart.Draw.Text(r, "Test", 64, 64, chart.Style{
YAxis: chart.YAxis{ FontColor: drawing.ColorBlack,
Style: chart.Style{ FontSize: 18,
Show: true, Font: f,
}, })
Range: &chart.ContinuousRange{
Min: 0.0, chart.Draw.Text(r, "Test", 64, 64, chart.Style{
Max: 4.0, FontColor: drawing.ColorBlack,
}, FontSize: 18,
TickStyle: chart.Style{ Font: f,
TextRotationDegrees: 45.0, TextRotationDegrees: 45.0,
}, })
Ticks: []chart.Tick{
{Value: 0.0, Label: "0.00"}, tb := chart.Draw.MeasureText(r, "Test", chart.Style{
{Value: 2.0, Label: "2.00"}, FontColor: drawing.ColorBlack,
{Value: 4.0, Label: "4.00"}, FontSize: 18,
{Value: 6.0, Label: "6.00"}, Font: f,
{Value: 8.0, Label: "Eight"}, }).Shift(64, 64)
{Value: 10.0, Label: "Ten"},
}, tbc := tb.Corners().Rotate(45)
},
XAxis: chart.XAxis{ chart.Draw.BoxCorners(r, tbc, chart.Style{
Style: chart.Style{ StrokeColor: drawing.ColorRed,
Show: true, StrokeWidth: 2,
}, })
TickStyle: chart.Style{
TextRotationDegrees: 45.0, tbcb := tbc.Box()
}, chart.Draw.Box(r, tbcb, chart.Style{
Ticks: []chart.Tick{ StrokeColor: drawing.ColorBlue,
{Value: 0.0, Label: "0.00"}, StrokeWidth: 2,
{Value: 2.0, Label: "2.00"}, })
{Value: 4.0, Label: "4.00"},
{Value: 6.0, Label: "6.00"},
{Value: 8.0, Label: "Eight"},
{Value: 10.0, Label: "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") res.Header().Set("Content-Type", "image/png")
graph.Render(chart.PNG, res) r.Save(res)
} }
func main() { func main() {

119
box.go
View File

@ -1,6 +1,9 @@
package chart package chart
import "fmt" import (
"fmt"
"math"
)
// Box represents the main 4 dimensions of a box. // Box represents the main 4 dimensions of a box.
type Box struct { type Box struct {
@ -139,6 +142,16 @@ func (b Box) Shift(x, y int) Box {
} }
} }
// Corners returns the box as a set of corners.
func (b Box) Corners() BoxCorners {
return BoxCorners{
TopLeft: Point{b.Left, b.Top},
TopRight: Point{b.Right, b.Top},
BottomRight: Point{b.Right, b.Bottom},
BottomLeft: Point{b.Left, b.Bottom},
}
}
// Fit is functionally the inverse of grow. // Fit is functionally the inverse of grow.
// Fit maintains the original aspect ratio of the `other` box, // Fit maintains the original aspect ratio of the `other` box,
// but constrains it to the bounds of the target box. // but constrains it to the bounds of the target box.
@ -220,20 +233,98 @@ func (b Box) OuterConstrain(bounds, other Box) Box {
return newBox return newBox
} }
// BoundedRotate rotates a box's corners by a given radian rotation angle // BoxCorners is a box with independent corners.
// and returns the maximum bounds or clipping rectangle. type BoxCorners struct {
func (b Box) BoundedRotate(radians float64) Box { TopLeft, TopRight, BottomRight, BottomLeft Point
cx, cy := b.Center() }
ltx, lty := Math.RotateCoordinate(cx, cy, b.Left, b.Top, radians)
lbx, lby := Math.RotateCoordinate(cx, cy, b.Left, b.Bottom, radians)
rtx, rty := Math.RotateCoordinate(cx, cy, b.Right, b.Top, radians)
rbx, rby := Math.RotateCoordinate(cx, cy, b.Right, b.Bottom, radians)
// Box return the BoxCorners as a regular box.
func (bc BoxCorners) Box() Box {
return Box{ return Box{
Left: Math.MinInt(ltx, lbx), Top: Math.MinInt(bc.TopLeft.Y, bc.TopRight.Y),
Top: Math.MinInt(lty, rty), Left: Math.MinInt(bc.TopLeft.X, bc.BottomLeft.X),
Right: Math.MaxInt(rtx, rbx), Right: Math.MaxInt(bc.TopRight.X, bc.BottomRight.X),
Bottom: Math.MaxInt(lby, rby), Bottom: Math.MaxInt(bc.BottomLeft.Y, bc.BottomRight.Y),
} }
} }
// Width returns the width
func (bc BoxCorners) Width() int {
minLeft := Math.MinInt(bc.TopLeft.X, bc.BottomLeft.X)
maxRight := Math.MaxInt(bc.TopRight.X, bc.BottomRight.X)
return maxRight - minLeft
}
// Height returns the height
func (bc BoxCorners) Height() int {
minTop := Math.MinInt(bc.TopLeft.Y, bc.TopRight.Y)
maxBottom := Math.MaxInt(bc.BottomLeft.Y, bc.BottomRight.Y)
return maxBottom - minTop
}
// Center returns the center of the box
func (bc BoxCorners) Center() (x, y int) {
left := Math.MeanInt(bc.TopLeft.X, bc.BottomLeft.X)
right := Math.MeanInt(bc.TopRight.X, bc.BottomRight.X)
x = ((right - left) >> 1) + left
top := Math.MeanInt(bc.TopLeft.Y, bc.TopRight.Y)
bottom := Math.MeanInt(bc.BottomLeft.Y, bc.BottomRight.Y)
y = ((bottom - top) >> 1) + top
return
}
// Rotate rotates the box.
func (bc BoxCorners) Rotate(thetaDegrees float64) BoxCorners {
cx, cy := bc.Center()
thetaRadians := Math.DegreesToRadians(thetaDegrees)
tlx, tly := Math.RotateCoordinate(cx, cy, bc.TopLeft.X, bc.TopLeft.Y, thetaRadians)
trx, try := Math.RotateCoordinate(cx, cy, bc.TopRight.X, bc.TopRight.Y, thetaRadians)
brx, bry := Math.RotateCoordinate(cx, cy, bc.BottomRight.X, bc.BottomRight.Y, thetaRadians)
blx, bly := Math.RotateCoordinate(cx, cy, bc.BottomLeft.X, bc.BottomLeft.Y, thetaRadians)
return BoxCorners{
TopLeft: Point{tlx, tly},
TopRight: Point{trx, try},
BottomRight: Point{brx, bry},
BottomLeft: Point{blx, bly},
}
}
// Equals returns if the box equals another box.
func (bc BoxCorners) Equals(other BoxCorners) bool {
return bc.TopLeft.Equals(other.TopLeft) &&
bc.TopRight.Equals(other.TopRight) &&
bc.BottomRight.Equals(other.BottomRight) &&
bc.BottomLeft.Equals(other.BottomLeft)
}
func (bc BoxCorners) String() string {
return fmt.Sprintf("BoxC{%s,%s,%s,%s}", bc.TopLeft.String(), bc.TopRight.String(), bc.BottomRight.String(), bc.BottomLeft.String())
}
// Point is an X,Y pair
type Point struct {
X, Y int
}
// DistanceTo calculates the distance to another point.
func (p Point) DistanceTo(other Point) float64 {
dx := math.Pow(float64(p.X-other.X), 2)
dy := math.Pow(float64(p.Y-other.Y), 2)
return math.Pow(dx+dy, 0.5)
}
// Equals returns if a point equals another point.
func (p Point) Equals(other Point) bool {
return p.X == other.X && p.Y == other.Y
}
// String returns a string representation of the point.
func (p Point) String() string {
return fmt.Sprintf("P{%d,%d}", p.X, p.Y)
}

View File

@ -158,18 +158,31 @@ func TestBoxCenter(t *testing.T) {
assert.Equal(20, cy) assert.Equal(20, cy)
} }
func TestBoxBoundedRotate(t *testing.T) { func TestBoxCornersCenter(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
b := Box{ bc := BoxCorners{
Top: 5, TopLeft: Point{5, 5},
Left: 5, TopRight: Point{15, 5},
Right: 20, BottomRight: Point{15, 15},
Bottom: 10, BottomLeft: Point{5, 15},
} }
rotated := b.BoundedRotate(Math.DegreesToRadians(45))
assert.Equal(1, rotated.Top) cx, cy := bc.Center()
assert.Equal(5, rotated.Left) assert.Equal(10, cx)
assert.Equal(19, rotated.Right) assert.Equal(10, cy)
assert.Equal(14, rotated.Bottom) }
func TestBoxCornersRotate(t *testing.T) {
assert := assert.New(t)
bc := BoxCorners{
TopLeft: Point{5, 5},
TopRight: Point{15, 5},
BottomRight: Point{15, 15},
BottomLeft: Point{5, 15},
}
rotated := bc.Rotate(45)
assert.True(rotated.TopLeft.Equals(Point{10, 3}), rotated.String())
} }

View File

@ -324,12 +324,10 @@ func (c Chart) getAxesAdjustedCanvasBox(r Renderer, canvasBox Box, xr, yr, yra R
axesOuterBox := canvasBox.Clone() axesOuterBox := canvasBox.Clone()
if c.XAxis.Style.Show { if c.XAxis.Style.Show {
axesBounds := c.XAxis.Measure(r, canvasBox, xr, c.styleDefaultsAxes(), xticks) axesBounds := c.XAxis.Measure(r, canvasBox, xr, c.styleDefaultsAxes(), xticks)
Draw.Box(r, axesBounds, Style{StrokeWidth: 2, StrokeColor: ColorRed})
axesOuterBox = axesOuterBox.Grow(axesBounds) axesOuterBox = axesOuterBox.Grow(axesBounds)
} }
if c.YAxis.Style.Show { if c.YAxis.Style.Show {
axesBounds := c.YAxis.Measure(r, canvasBox, yr, c.styleDefaultsAxes(), yticks) axesBounds := c.YAxis.Measure(r, canvasBox, yr, c.styleDefaultsAxes(), yticks)
Draw.Box(r, axesBounds, Style{StrokeWidth: 2, StrokeColor: ColorBlue})
axesOuterBox = axesOuterBox.Grow(axesBounds) axesOuterBox = axesOuterBox.Grow(axesBounds)
} }
if c.YAxisSecondary.Style.Show { if c.YAxisSecondary.Style.Show {

46
draw.go
View File

@ -140,6 +140,7 @@ func (d draw) HistogramSeries(r Renderer, canvasBox Box, xrange, yrange Range, s
// MeasureAnnotation measures how big an annotation would be. // MeasureAnnotation measures how big an annotation would be.
func (d draw) MeasureAnnotation(r Renderer, canvasBox Box, style Style, lx, ly int, label string) Box { func (d draw) MeasureAnnotation(r Renderer, canvasBox Box, style Style, lx, ly int, label string) Box {
style.WriteToRenderer(r) style.WriteToRenderer(r)
defer r.ResetStyle()
textBox := r.MeasureText(label) textBox := r.MeasureText(label)
textWidth := textBox.Width() textWidth := textBox.Width()
@ -168,6 +169,8 @@ func (d draw) MeasureAnnotation(r Renderer, canvasBox Box, style Style, lx, ly i
// Annotation draws an anotation with a renderer. // Annotation draws an anotation with a renderer.
func (d draw) Annotation(r Renderer, canvasBox Box, style Style, lx, ly int, label string) { func (d draw) Annotation(r Renderer, canvasBox Box, style Style, lx, ly int, label string) {
style.GetTextOptions().WriteToRenderer(r) style.GetTextOptions().WriteToRenderer(r)
defer r.ResetStyle()
textBox := r.MeasureText(label) textBox := r.MeasureText(label)
textWidth := textBox.Width() textWidth := textBox.Width()
halfTextHeight := textBox.Height() >> 1 halfTextHeight := textBox.Height() >> 1
@ -209,7 +212,8 @@ func (d draw) Annotation(r Renderer, canvasBox Box, style Style, lx, ly int, lab
// Box draws a box with a given style. // Box draws a box with a given style.
func (d draw) Box(r Renderer, b Box, s Style) { func (d draw) Box(r Renderer, b Box, s Style) {
s.WriteToRenderer(r) s.GetFillAndStrokeOptions().WriteToRenderer(r)
defer r.ResetStyle()
r.MoveTo(b.Left, b.Top) r.MoveTo(b.Left, b.Top)
r.LineTo(b.Right, b.Top) r.LineTo(b.Right, b.Top)
@ -219,18 +223,18 @@ func (d draw) Box(r Renderer, b Box, s Style) {
r.FillStroke() r.FillStroke()
} }
func (d draw) BoxRotated(r Renderer, b Box, thetaRadians float64, s Style) { func (d draw) BoxRotated(r Renderer, b Box, thetaDegrees float64, s Style) {
s.WriteToRenderer(r) d.BoxCorners(r, b.Corners().Rotate(thetaDegrees), s)
cx, cy := b.Center() }
ltx, lty := Math.RotateCoordinate(cx, cy, b.Left, b.Top, thetaRadians)
lbx, lby := Math.RotateCoordinate(cx, cy, b.Left, b.Bottom, thetaRadians)
rtx, rty := Math.RotateCoordinate(cx, cy, b.Right, b.Top, thetaRadians)
rbx, rby := Math.RotateCoordinate(cx, cy, b.Right, b.Bottom, thetaRadians)
r.MoveTo(ltx, lty) func (d draw) BoxCorners(r Renderer, bc BoxCorners, s Style) {
r.LineTo(rtx, rty) s.GetFillAndStrokeOptions().WriteToRenderer(r)
r.LineTo(rbx, rby) defer r.ResetStyle()
r.LineTo(lbx, lby)
r.MoveTo(bc.TopLeft.X, bc.TopLeft.Y)
r.LineTo(bc.TopRight.X, bc.TopRight.Y)
r.LineTo(bc.BottomRight.X, bc.BottomRight.Y)
r.LineTo(bc.BottomLeft.X, bc.BottomLeft.Y)
r.Close() r.Close()
r.FillStroke() r.FillStroke()
} }
@ -238,16 +242,26 @@ func (d draw) BoxRotated(r Renderer, b Box, thetaRadians float64, s Style) {
// DrawText draws text with a given style. // DrawText draws text with a given style.
func (d draw) Text(r Renderer, text string, x, y int, style Style) { func (d draw) Text(r Renderer, text string, x, y int, style Style) {
style.GetTextOptions().WriteToRenderer(r) style.GetTextOptions().WriteToRenderer(r)
defer r.ResetStyle()
r.Text(text, x, y) r.Text(text, x, y)
} }
func (d draw) MeasureText(r Renderer, text string, style Style) Box {
style.GetTextOptions().WriteToRenderer(r)
defer r.ResetStyle()
return r.MeasureText(text)
}
// TextWithin draws the text within a given box. // TextWithin draws the text within a given box.
func (d draw) TextWithin(r Renderer, text string, box Box, style Style) { func (d draw) TextWithin(r Renderer, text string, box Box, style Style) {
style.GetTextOptions().WriteToRenderer(r)
defer r.ResetStyle()
lines := Text.WrapFit(r, text, box.Width(), style) lines := Text.WrapFit(r, text, box.Width(), style)
linesBox := Text.MeasureLines(r, lines, style) linesBox := Text.MeasureLines(r, lines, style)
style.GetTextOptions().WriteToRenderer(r)
y := box.Top y := box.Top
switch style.GetTextVerticalAlign() { switch style.GetTextVerticalAlign() {
@ -268,7 +282,11 @@ func (d draw) TextWithin(r Renderer, text string, box Box, style Style) {
default: default:
tx = box.Left tx = box.Left
} }
if style.TextRotationDegrees == 0 {
ty = y + lineBox.Height() ty = y + lineBox.Height()
} else {
ty = y
}
d.Text(r, line, tx, ty, style) d.Text(r, line, tx, ty, style)
y += lineBox.Height() + style.GetTextLineSpacing() y += lineBox.Height() + style.GetTextLineSpacing()

Binary file not shown.

View File

@ -54,9 +54,7 @@ func Legend(c *Chart, userDefaults ...Style) Renderable {
Bottom: legend.Top + legendPadding.Top, Bottom: legend.Top + legendPadding.Top,
} }
r.SetFont(legendStyle.GetFont()) legendStyle.GetTextOptions().WriteToRenderer(r)
r.SetFontColor(legendStyle.GetFontColor())
r.SetFontSize(legendStyle.GetFontSize())
// measure // measure
labelCount := 0 labelCount := 0
@ -79,6 +77,8 @@ func Legend(c *Chart, userDefaults ...Style) Renderable {
Draw.Box(r, legend, legendStyle) Draw.Box(r, legend, legendStyle)
legendStyle.GetTextOptions().WriteToRenderer(r)
ycursor := legendContent.Top ycursor := legendContent.Top
tx := legendContent.Left tx := legendContent.Left
legendCount := 0 legendCount := 0

View File

@ -134,6 +134,16 @@ func (m mathUtil) AbsInt(value int) int {
return value return value
} }
// Mean returns the mean of a set of values
func (m mathUtil) Mean(values ...float64) float64 {
return m.Sum(values...) / float64(len(values))
}
// MeanInt returns the mean of a set of integer values.
func (m mathUtil) MeanInt(values ...int) int {
return m.SumInt(values...) / len(values)
}
// Sum sums a set of values. // Sum sums a set of values.
func (m mathUtil) Sum(values ...float64) float64 { func (m mathUtil) Sum(values ...float64) float64 {
var total float64 var total float64

View File

@ -33,6 +33,11 @@ type rasterRenderer struct {
s Style s Style
} }
func (rr *rasterRenderer) ResetStyle() {
rr.s = Style{Font: rr.s.Font}
rr.ClearTextRotation()
}
// GetDPI returns the dpi. // GetDPI returns the dpi.
func (rr *rasterRenderer) GetDPI() float64 { func (rr *rasterRenderer) GetDPI() float64 {
return rr.gc.GetDPI() return rr.gc.GetDPI()
@ -187,7 +192,7 @@ func (rr *rasterRenderer) MeasureText(body string) Box {
return textBox return textBox
} }
return textBox.BoundedRotate(*rr.rotateRadians) return textBox.Corners().Rotate(Math.RadiansToDegrees(*rr.rotateRadians)).Box()
} }
// SetTextRotation sets a text rotation. // SetTextRotation sets a text rotation.

View File

@ -9,6 +9,9 @@ import (
// Renderer represents the basic methods required to draw a chart. // Renderer represents the basic methods required to draw a chart.
type Renderer interface { type Renderer interface {
// ResetStyle should reset any style related settings on the renderer.
ResetStyle()
// GetDPI gets the DPI for the renderer. // GetDPI gets the DPI for the renderer.
GetDPI() float64 GetDPI() float64

View File

@ -36,6 +36,11 @@ type vectorRenderer struct {
fc *font.Drawer fc *font.Drawer
} }
func (vr *vectorRenderer) ResetStyle() {
vr.s = &Style{Font: vr.s.Font}
vr.fc = nil
}
// GetDPI returns the dpi. // GetDPI returns the dpi.
func (vr *vectorRenderer) GetDPI() float64 { func (vr *vectorRenderer) GetDPI() float64 {
return vr.dpi return vr.dpi
@ -170,7 +175,7 @@ func (vr *vectorRenderer) MeasureText(body string) (box Box) {
if vr.c.textTheta == nil { if vr.c.textTheta == nil {
return return
} }
box = box.BoundedRotate(*vr.c.textTheta) box = box.Corners().Rotate(Math.RadiansToDegrees(*vr.c.textTheta)).Box()
} }
return return
} }

View File

@ -70,20 +70,20 @@ func (xa XAxis) GetGridLines(ticks []Tick) []GridLine {
// Measure returns the bounds of the axis. // Measure returns the bounds of the axis.
func (xa XAxis) Measure(r Renderer, canvasBox Box, ra Range, defaults Style, ticks []Tick) Box { func (xa XAxis) Measure(r Renderer, canvasBox Box, ra Range, defaults Style, ticks []Tick) Box {
tickStyle := xa.Style.InheritFrom(defaults) tickStyle := xa.TickStyle.InheritFrom(xa.Style.InheritFrom(defaults))
sort.Sort(Ticks(ticks)) sort.Sort(Ticks(ticks))
tp := xa.GetTickPosition() tp := xa.GetTickPosition()
var ltx, rtx int
var tx, ty int
var left, right, bottom = math.MaxInt32, 0, 0 var left, right, bottom = math.MaxInt32, 0, 0
for index, t := range ticks { for index, t := range ticks {
v := t.Value v := t.Value
tickStyle.GetTextOptions().WriteToRenderer(r) tb := Draw.MeasureText(r, t.Label, tickStyle.GetTextOptions())
tb := r.MeasureText(t.Label)
var ltx, rtx int tx = canvasBox.Left + ra.Translate(v)
tx := canvasBox.Left + ra.Translate(v) ty = canvasBox.Bottom + DefaultXAxisMargin + tb.Height()
ty := canvasBox.Bottom + DefaultXAxisMargin + tb.Height()
switch tp { switch tp {
case TickPositionUnderTick, TickPositionUnset: case TickPositionUnderTick, TickPositionUnset:
ltx = tx - tb.Width()>>1 ltx = tx - tb.Width()>>1
@ -103,7 +103,7 @@ func (xa XAxis) Measure(r Renderer, canvasBox Box, ra Range, defaults Style, tic
} }
if xa.NameStyle.Show && len(xa.Name) > 0 { if xa.NameStyle.Show && len(xa.Name) > 0 {
tb := r.MeasureText(xa.Name) tb := Draw.MeasureText(r, xa.Name, xa.NameStyle.InheritFrom(defaults))
bottom += DefaultXAxisMargin + tb.Height() bottom += DefaultXAxisMargin + tb.Height()
} }
@ -117,7 +117,7 @@ func (xa XAxis) Measure(r Renderer, canvasBox Box, ra Range, defaults Style, tic
// Render renders the axis // Render renders the axis
func (xa XAxis) Render(r Renderer, canvasBox Box, ra Range, defaults Style, ticks []Tick) { func (xa XAxis) Render(r Renderer, canvasBox Box, ra Range, defaults Style, ticks []Tick) {
tickStyle := xa.Style.InheritFrom(defaults) tickStyle := xa.TickStyle.InheritFrom(xa.Style.InheritFrom(defaults))
tickStyle.GetStrokeOptions().WriteToRenderer(r) tickStyle.GetStrokeOptions().WriteToRenderer(r)
r.MoveTo(canvasBox.Left, canvasBox.Bottom) r.MoveTo(canvasBox.Left, canvasBox.Bottom)
@ -141,25 +141,30 @@ func (xa XAxis) Render(r Renderer, canvasBox Box, ra Range, defaults Style, tick
r.LineTo(tx, canvasBox.Bottom+DefaultVerticalTickHeight) r.LineTo(tx, canvasBox.Bottom+DefaultVerticalTickHeight)
r.Stroke() r.Stroke()
xa.TickStyle.InheritFrom(xa.Style.InheritFrom(defaults)).WriteToRenderer(r) tickWithAxisStyle := xa.TickStyle.InheritFrom(xa.Style.InheritFrom(defaults))
tb := r.MeasureText(t.Label) tb := Draw.MeasureText(r, t.Label, tickWithAxisStyle)
switch tp { switch tp {
case TickPositionUnderTick, TickPositionUnset: case TickPositionUnderTick, TickPositionUnset:
if tickStyle.TextRotationDegrees == 0 {
tx = tx - tb.Width()>>1
ty = canvasBox.Bottom + DefaultXAxisMargin + tb.Height() ty = canvasBox.Bottom + DefaultXAxisMargin + tb.Height()
r.Text(t.Label, tx-tb.Width()>>1, ty) } else {
ty = canvasBox.Bottom + (2 * DefaultXAxisMargin)
}
Draw.Text(r, t.Label, tx, ty, tickWithAxisStyle)
maxTextHeight = Math.MaxInt(maxTextHeight, tb.Height()) maxTextHeight = Math.MaxInt(maxTextHeight, tb.Height())
break break
case TickPositionBetweenTicks: case TickPositionBetweenTicks:
if index > 0 { if index > 0 {
llx := ra.Translate(ticks[index-1].Value) llx := ra.Translate(ticks[index-1].Value)
ltx := canvasBox.Left + llx ltx := canvasBox.Left + llx
finalTickStyle := tickStyle.InheritFrom(Style{TextHorizontalAlign: TextHorizontalAlignCenter}) finalTickStyle := tickWithAxisStyle.InheritFrom(Style{TextHorizontalAlign: TextHorizontalAlignCenter})
Draw.TextWithin(r, t.Label, Box{ Draw.TextWithin(r, t.Label, Box{
Left: ltx, Left: ltx,
Right: tx, Right: tx,
Top: canvasBox.Bottom + DefaultXAxisMargin, Top: canvasBox.Bottom + DefaultXAxisMargin,
Bottom: canvasBox.Bottom + DefaultXAxisMargin + tb.Height(), Bottom: canvasBox.Bottom + DefaultXAxisMargin,
}, finalTickStyle) }, finalTickStyle)
ftb := Text.MeasureLines(r, Text.WrapFit(r, t.Label, tx-ltx, finalTickStyle), finalTickStyle) ftb := Text.MeasureLines(r, Text.WrapFit(r, t.Label, tx-ltx, finalTickStyle), finalTickStyle)
@ -171,11 +176,10 @@ func (xa XAxis) Render(r Renderer, canvasBox Box, ra Range, defaults Style, tick
nameStyle := xa.NameStyle.InheritFrom(defaults) nameStyle := xa.NameStyle.InheritFrom(defaults)
if xa.NameStyle.Show && len(xa.Name) > 0 { if xa.NameStyle.Show && len(xa.Name) > 0 {
nameStyle.GetTextOptions().WriteToRenderer(r) tb := Draw.MeasureText(r, xa.Name, nameStyle)
tb := r.MeasureText(xa.Name)
tx := canvasBox.Right - (canvasBox.Width()>>1 + tb.Width()>>1) tx := canvasBox.Right - (canvasBox.Width()>>1 + tb.Width()>>1)
ty := canvasBox.Bottom + DefaultXAxisMargin + maxTextHeight + DefaultXAxisMargin + tb.Height() ty := canvasBox.Bottom + DefaultXAxisMargin + maxTextHeight + DefaultXAxisMargin + tb.Height()
r.Text(xa.Name, tx, ty) Draw.Text(r, xa.Name, tx, ty, nameStyle)
} }
if xa.GridMajorStyle.Show || xa.GridMinorStyle.Show { if xa.GridMajorStyle.Show || xa.GridMinorStyle.Show {

View File

@ -179,7 +179,7 @@ func (ya YAxis) Render(r Renderer, canvasBox Box, ra Range, defaults Style, tick
nameStyle := ya.NameStyle.InheritFrom(defaults.InheritFrom(Style{TextRotationDegrees: 90})) 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)
tb := r.MeasureText(ya.Name) tb := Draw.MeasureText(r, ya.Name, nameStyle)
var tx int var tx int
if ya.AxisType == YAxisPrimary { if ya.AxisType == YAxisPrimary {
@ -188,9 +188,9 @@ func (ya YAxis) Render(r Renderer, canvasBox Box, ra Range, defaults Style, tick
tx = canvasBox.Left - (DefaultYAxisMargin + int(sw) + maxTextWidth + DefaultYAxisMargin) tx = canvasBox.Left - (DefaultYAxisMargin + int(sw) + maxTextWidth + DefaultYAxisMargin)
} }
ty := canvasBox.Bottom - (canvasBox.Height()>>1 + tb.Width()>>1) ty := canvasBox.Top + (canvasBox.Height()>>1 + tb.Width()>>1)
r.Text(ya.Name, tx, ty) Draw.Text(r, ya.Name, tx, ty, nameStyle)
} }
if ya.Zero.Style.Show { if ya.Zero.Style.Show {