snapshot.

This commit is contained in:
Will Charczuk 2016-09-05 13:26:12 -07:00
parent b78f2327aa
commit 8f56e5939b
10 changed files with 105 additions and 44 deletions

View file

@ -14,13 +14,31 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
graph := chart.Chart{ graph := chart.Chart{
YAxis: chart.YAxis{ YAxis: chart.YAxis{
Style: chart.Style{ Style: chart.Style{
Show: true, Show: true,
TextRotationDegrees: 45.0,
}, },
Range: &chart.ContinuousRange{ Range: &chart.ContinuousRange{
Min: 0.0, Min: 0.0,
Max: 4.0, Max: 4.0,
}, },
TickStyle: chart.Style{
TextRotationDegrees: 45.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"},
},
},
XAxis: chart.XAxis{
Style: chart.Style{
Show: true,
},
TickStyle: chart.Style{
TextRotationDegrees: 45.0,
},
Ticks: []chart.Tick{ Ticks: []chart.Tick{
{0.0, "0.00"}, {0.0, "0.00"},
{2.0, "2.00"}, {2.0, "2.00"},
@ -39,6 +57,20 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
} }
res.Header().Set("Content-Type", "image/png") res.Header().Set("Content-Type", "image/png")
graph.Elements = []chart.Renderable{
func(r chart.Renderer, cb chart.Box, defaults chart.Style) {
b := chart.Box{50, 50, 90, 110}
chart.Draw.Box(r, b, chart.Style{
StrokeWidth: 2,
StrokeColor: chart.ColorBlue,
})
chart.Draw.Box(r, b.Rotate(chart.Math.DegreesToRadians(45)), chart.Style{
StrokeWidth: 2,
StrokeColor: chart.ColorRed,
})
},
}
graph.Render(chart.PNG, res) graph.Render(chart.PNG, res)
} }

6
box.go
View file

@ -76,8 +76,8 @@ func (b Box) Height() int {
// Center returns the center of the box // Center returns the center of the box
func (b Box) Center() (x, y int) { func (b Box) Center() (x, y int) {
w, h := b.Width(), b.Height() w2, h2 := b.Width()>>1, b.Height()>>1
return b.Left + w>>1, b.Top + h>>1 return b.Left + w2, b.Top + h2
} }
// Aspect returns the aspect ratio of the box. // Aspect returns the aspect ratio of the box.
@ -227,8 +227,8 @@ func (b Box) Rotate(radians float64) Box {
ltx, lty := Math.RotateCoordinate(cx, cy, b.Left, b.Top, radians) ltx, lty := Math.RotateCoordinate(cx, cy, b.Left, b.Top, radians)
rbx, rby := Math.RotateCoordinate(cx, cy, b.Right, b.Bottom, radians) rbx, rby := Math.RotateCoordinate(cx, cy, b.Right, b.Bottom, radians)
return Box{ return Box{
Top: lty,
Left: ltx, Left: ltx,
Top: lty,
Right: rbx, Right: rbx,
Bottom: rby, Bottom: rby,
} }

View file

@ -144,6 +144,20 @@ func TestBoxShift(t *testing.T) {
assert.Equal(12, shifted.Bottom) assert.Equal(12, shifted.Bottom)
} }
func TestBoxCenter(t *testing.T) {
assert := assert.New(t)
b := Box{
Top: 10,
Left: 10,
Right: 20,
Bottom: 30,
}
cx, cy := b.Center()
assert.Equal(15, cx)
assert.Equal(20, cy)
}
func TestBoxRotate(t *testing.T) { func TestBoxRotate(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
@ -156,6 +170,6 @@ func TestBoxRotate(t *testing.T) {
rotated := b.Rotate(Math.DegreesToRadians(45)) rotated := b.Rotate(Math.DegreesToRadians(45))
assert.Equal(1, rotated.Top) assert.Equal(1, rotated.Top)
assert.Equal(4, rotated.Left) assert.Equal(4, rotated.Left)
assert.Equal(11, rotated.Right) assert.Equal(10, rotated.Right)
assert.Equal(15, rotated.Bottom) assert.Equal(14, rotated.Bottom)
} }

View file

@ -223,9 +223,9 @@ func (m mathUtil) CirclePoint(cx, cy int, radius, thetaRadians float64) (x, y in
func (m mathUtil) RotateCoordinate(cx, cy, x, y int, thetaRadians float64) (rx, ry int) { func (m mathUtil) RotateCoordinate(cx, cy, x, y int, thetaRadians float64) (rx, ry int) {
tempX, tempY := float64(x-cx), float64(y-cy) tempX, tempY := float64(x-cx), float64(y-cy)
rotatedX := int(math.Ceil(tempX*math.Cos(thetaRadians) - tempY*math.Sin(thetaRadians))) rotatedX := tempX*math.Cos(thetaRadians) - tempY*math.Sin(thetaRadians)
rotatedY := int(math.Ceil(tempX*math.Sin(thetaRadians) + tempY*math.Cos(thetaRadians))) rotatedY := tempX*math.Sin(thetaRadians) + tempY*math.Cos(thetaRadians)
rx = rotatedX + cy rx = int(rotatedX) + cy
ry = rotatedY + cy ry = int(rotatedY) + cy
return return
} }

View file

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

View file

@ -28,7 +28,7 @@ type rasterRenderer struct {
i *image.RGBA i *image.RGBA
gc *drawing.RasterGraphicContext gc *drawing.RasterGraphicContext
rotateRadians float64 rotateRadians *float64
s Style s Style
} }
@ -183,34 +183,34 @@ func (rr *rasterRenderer) MeasureText(body string) Box {
Right: int(math.Ceil(r)), Right: int(math.Ceil(r)),
Bottom: int(math.Ceil(b)), Bottom: int(math.Ceil(b)),
} }
if rr.rotateRadians == 0 { if rr.rotateRadians == nil {
return textBox return textBox
} }
return textBox.Rotate(rr.rotateRadians) return textBox.Rotate(*rr.rotateRadians)
} }
// SetTextRotation sets a text rotation. // SetTextRotation sets a text rotation.
func (rr *rasterRenderer) SetTextRotation(radians float64) { func (rr *rasterRenderer) SetTextRotation(radians float64) {
rr.rotateRadians = radians rr.rotateRadians = &radians
} }
func (rr *rasterRenderer) getCoords(x, y int) (xf, yf int) { func (rr *rasterRenderer) getCoords(x, y int) (xf, yf int) {
if rr.rotateRadians == 0 { if rr.rotateRadians == nil {
xf = x xf = x
yf = y yf = y
return return
} }
rr.gc.Translate(float64(x), float64(y)) rr.gc.Translate(float64(x), float64(y))
rr.gc.Rotate(rr.rotateRadians) rr.gc.Rotate(*rr.rotateRadians)
return return
} }
// ClearTextRotation clears text rotation. // ClearTextRotation clears text rotation.
func (rr *rasterRenderer) ClearTextRotation() { func (rr *rasterRenderer) ClearTextRotation() {
rr.gc.SetMatrixTransform(drawing.NewIdentityMatrix()) rr.gc.SetMatrixTransform(drawing.NewIdentityMatrix())
rr.rotateRadians = 0 rr.rotateRadians = nil
} }
// Save implements the interface method. // Save implements the interface method.

View file

@ -33,7 +33,7 @@ type Style struct {
TextVerticalAlign TextVerticalAlign TextVerticalAlign TextVerticalAlign
TextWrap TextWrap TextWrap TextWrap
TextLineSpacing int TextLineSpacing int
TextRotationDegrees float64 TextRotationDegrees float64 //0 is unset or normal
} }
// IsZero returns if the object is set or not. // IsZero returns if the object is set or not.
@ -262,9 +262,8 @@ func (s Style) WriteToRenderer(r Renderer) {
r.SetFontColor(s.GetFontColor()) r.SetFontColor(s.GetFontColor())
r.SetFontSize(s.GetFontSize()) r.SetFontSize(s.GetFontSize())
if s.GetTextRotationDegrees() == 0 { r.ClearTextRotation()
r.ClearTextRotation() if s.GetTextRotationDegrees() != 0 {
} else {
r.SetTextRotation(Math.DegreesToRadians(s.GetTextRotationDegrees())) r.SetTextRotation(Math.DegreesToRadians(s.GetTextRotationDegrees()))
} }
} }

View file

@ -32,7 +32,6 @@ type vectorRenderer struct {
b *bytes.Buffer b *bytes.Buffer
c *canvas c *canvas
s *Style s *Style
r float64
p []string p []string
fc *font.Drawer fc *font.Drawer
} }
@ -168,18 +167,22 @@ func (vr *vectorRenderer) MeasureText(body string) (box Box) {
box.Right = w box.Right = w
box.Bottom = int(drawing.PointsToPixels(vr.dpi, vr.s.FontSize)) box.Bottom = int(drawing.PointsToPixels(vr.dpi, vr.s.FontSize))
if vr.c.textTheta == nil {
return
}
box = box.Rotate(*vr.c.textTheta)
} }
return return
} }
// SetTextRotation sets the text rotation. // SetTextRotation sets the text rotation.
func (vr *vectorRenderer) SetTextRotation(radians float64) { func (vr *vectorRenderer) SetTextRotation(radians float64) {
vr.c.r = radians vr.c.textTheta = &radians
} }
// ClearTextRotation clears the text rotation. // ClearTextRotation clears the text rotation.
func (vr *vectorRenderer) ClearTextRotation() { func (vr *vectorRenderer) ClearTextRotation() {
vr.c.r = 0 vr.c.textTheta = nil
} }
// Save saves the renderer's contents to a writer. // Save saves the renderer's contents to a writer.
@ -196,11 +199,11 @@ func newCanvas(w io.Writer) *canvas {
} }
type canvas struct { type canvas struct {
w io.Writer w io.Writer
dpi float64 dpi float64
r float64 textTheta *float64
width int width int
height int height int
} }
func (c *canvas) Start(width, height int) { func (c *canvas) Start(width, height int) {
@ -218,10 +221,10 @@ func (c *canvas) Path(d string, style Style) {
} }
func (c *canvas) Text(x, y int, body string, style Style) { func (c *canvas) Text(x, y int, body string, style Style) {
if c.r == 0 { if c.textTheta == nil {
c.w.Write([]byte(fmt.Sprintf(`<text x="%d" y="%d" style="%s">%s</text>`, x, y, c.styleAsSVG(style), body))) c.w.Write([]byte(fmt.Sprintf(`<text x="%d" y="%d" style="%s">%s</text>`, x, y, c.styleAsSVG(style), body)))
} else { } else {
transform := fmt.Sprintf(` transform="rotate(%0.2f,%d,%d)"`, Math.RadiansToDegrees(c.r), x, y) transform := fmt.Sprintf(` transform="rotate(%0.2f,%d,%d)"`, Math.RadiansToDegrees(*c.textTheta), x, y)
c.w.Write([]byte(fmt.Sprintf(`<text x="%d" y="%d" style="%s"%s>%s</text>`, x, y, c.styleAsSVG(style), transform, body))) c.w.Write([]byte(fmt.Sprintf(`<text x="%d" y="%d" style="%s"%s>%s</text>`, x, y, c.styleAsSVG(style), transform, body)))
} }
} }

View file

@ -7,13 +7,15 @@ import (
// XAxis represents the horizontal axis. // XAxis represents the horizontal axis.
type XAxis struct { type XAxis struct {
Name string Name string
NameStyle Style NameStyle Style
Style Style Style Style
ValueFormatter ValueFormatter ValueFormatter ValueFormatter
Range Range Range Range
Ticks []Tick
TickStyle Style
Ticks []Tick
TickPosition TickPosition TickPosition TickPosition
GridLines []GridLine GridLines []GridLine
@ -139,7 +141,7 @@ 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()
tickStyle.GetTextOptions().WriteToRenderer(r) xa.TickStyle.InheritFrom(xa.Style.InheritFrom(defaults)).WriteToRenderer(r)
tb := r.MeasureText(t.Label) tb := r.MeasureText(t.Label)
switch tp { switch tp {

View file

@ -22,8 +22,8 @@ type YAxis struct {
TickStyle Style TickStyle Style
Ticks []Tick Ticks []Tick
GridLines []GridLine
GridLines []GridLine
GridMajorStyle Style GridMajorStyle Style
GridMinorStyle Style GridMinorStyle Style
} }
@ -149,11 +149,10 @@ 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 {
ya.TickStyle.InheritFrom(ya.Style.InheritFrom(defaults)).WriteToRenderer(r)
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 {
@ -167,7 +166,8 @@ 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()
ya.Style.InheritFrom(defaults).WriteToRenderer(r)
r.MoveTo(lx, ly) r.MoveTo(lx, ly)
if ya.AxisType == YAxisPrimary { if ya.AxisType == YAxisPrimary {