Skip to content
This repository has been archived by the owner on Aug 23, 2024. It is now read-only.

Commit

Permalink
Merge pull request #13 from wcharczuk/text-rotation
Browse files Browse the repository at this point in the history
Adds `TextRotationDegrees`
  • Loading branch information
wcharczuk authored Oct 21, 2016
2 parents 78e6288 + 2e8d196 commit 27a5efd
Show file tree
Hide file tree
Showing 23 changed files with 546 additions and 114 deletions.
18 changes: 18 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch",
"type": "go",
"request": "launch",
"mode": "test",
"remotePath": "",
"port": 2345,
"host": "127.0.0.1",
"program": "${workspaceRoot}",
"env": {},
"args": [],
"showLog": true
}
]
}
43 changes: 29 additions & 14 deletions _examples/request_timings/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,33 @@ package main

import (
"net/http"
"strconv"
"strings"
"time"

util "github.com/blendlabs/go-util"
"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) {
var xvalues []time.Time
var yvalues []float64
util.ReadFileByLines("requests.csv", func(line string) {
chart.File.ReadByLines("requests.csv", func(line string) {
parts := strings.Split(line, ",")
year := util.ParseInt(parts[0])
month := util.ParseInt(parts[1])
day := util.ParseInt(parts[2])
hour := util.ParseInt(parts[3])
elapsedMillis := util.ParseFloat64(parts[4])
year := parseInt(parts[0])
month := parseInt(parts[1])
day := parseInt(parts[2])
hour := parseInt(parts[3])
elapsedMillis := parseFloat64(parts[4])
xvalues = append(xvalues, time.Date(year, time.Month(month), day, hour, 0, 0, 0, time.UTC))
yvalues = append(yvalues, elapsedMillis)
})
Expand All @@ -27,12 +37,12 @@ func readData() ([]time.Time, []float64) {

func releases() []chart.GridLine {
return []chart.GridLine{
{Value: chart.TimeToFloat64(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.TimeToFloat64(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.TimeToFloat64(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, 1, 9, 30, 0, 0, time.UTC))},
{Value: chart.Time.ToFloat64(time.Date(2016, 8, 2, 9, 30, 0, 0, time.UTC))},
{Value: chart.Time.ToFloat64(time.Date(2016, 8, 3, 9, 30, 0, 0, time.UTC))},
{Value: chart.Time.ToFloat64(time.Date(2016, 8, 4, 9, 30, 0, 0, time.UTC))},
{Value: chart.Time.ToFloat64(time.Date(2016, 8, 5, 9, 30, 0, 0, time.UTC))},
{Value: chart.Time.ToFloat64(time.Date(2016, 8, 6, 9, 30, 0, 0, time.UTC))},
}
}

Expand Down Expand Up @@ -81,9 +91,14 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
Name: "Elapsed Millis",
NameStyle: chart.StyleShow(),
Style: chart.StyleShow(),
TickStyle: chart.Style{
TextRotationDegrees: 45.0,
},
},
XAxis: chart.XAxis{
Style: chart.StyleShow(),
Style: chart.Style{
Show: true,
},
ValueFormatter: chart.TimeHourValueFormatter,
GridMajorStyle: chart.Style{
Show: true,
Expand Down
4 changes: 2 additions & 2 deletions _examples/stock_analysis/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
},
}

res.Header().Set("Content-Type", "image/svg+xml")
graph.Render(chart.SVG, res)
res.Header().Set("Content-Type", "image/png")
graph.Render(chart.PNG, res)
}

func xvalues() []time.Time {
Expand Down
53 changes: 53 additions & 0 deletions _examples/text_rotation/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package main

import (
"net/http"

"github.com/wcharczuk/go-chart"
"github.com/wcharczuk/go-chart/drawing"
)

func drawChart(res http.ResponseWriter, req *http.Request) {
f, _ := chart.GetDefaultFont()
r, _ := chart.PNG(1024, 1024)

chart.Draw.Text(r, "Test", 64, 64, chart.Style{
FontColor: drawing.ColorBlack,
FontSize: 18,
Font: f,
})

chart.Draw.Text(r, "Test", 64, 64, chart.Style{
FontColor: drawing.ColorBlack,
FontSize: 18,
Font: f,
TextRotationDegrees: 45.0,
})

tb := chart.Draw.MeasureText(r, "Test", chart.Style{
FontColor: drawing.ColorBlack,
FontSize: 18,
Font: f,
}).Shift(64, 64)

tbc := tb.Corners().Rotate(45)

chart.Draw.BoxCorners(r, tbc, chart.Style{
StrokeColor: drawing.ColorRed,
StrokeWidth: 2,
})

tbcb := tbc.Box()
chart.Draw.Box(r, tbcb, chart.Style{
StrokeColor: drawing.ColorBlue,
StrokeWidth: 2,
})

res.Header().Set("Content-Type", "image/png")
r.Save(res)
}

func main() {
http.HandleFunc("/", drawChart)
http.ListenAndServe(":8080", nil)
}
115 changes: 112 additions & 3 deletions box.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package chart

import "fmt"
import (
"fmt"
"math"
)

// Box represents the main 4 dimensions of a box.
type Box struct {
Expand Down Expand Up @@ -76,8 +79,8 @@ func (b Box) Height() int {

// Center returns the center of the box
func (b Box) Center() (x, y int) {
w, h := b.Width(), b.Height()
return b.Left + w>>1, b.Top + h>>1
w2, h2 := b.Width()>>1, b.Height()>>1
return b.Left + w2, b.Top + h2
}

// Aspect returns the aspect ratio of the box.
Expand Down Expand Up @@ -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 maintains the original aspect ratio of the `other` box,
// but constrains it to the bounds of the target box.
Expand Down Expand Up @@ -219,3 +232,99 @@ func (b Box) OuterConstrain(bounds, other Box) Box {
}
return newBox
}

// BoxCorners is a box with independent corners.
type BoxCorners struct {
TopLeft, TopRight, BottomRight, BottomLeft Point
}

// Box return the BoxCorners as a regular box.
func (bc BoxCorners) Box() Box {
return Box{
Top: Math.MinInt(bc.TopLeft.Y, bc.TopRight.Y),
Left: Math.MinInt(bc.TopLeft.X, bc.BottomLeft.X),
Right: Math.MaxInt(bc.TopRight.X, bc.BottomRight.X),
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)
}
43 changes: 43 additions & 0 deletions box_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,46 @@ func TestBoxShift(t *testing.T) {
assert.Equal(11, shifted.Right)
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 TestBoxCornersCenter(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},
}

cx, cy := bc.Center()
assert.Equal(10, cx)
assert.Equal(10, cy)
}

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())
}
6 changes: 3 additions & 3 deletions chart.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,12 +102,12 @@ func (c Chart) Render(rp RendererProvider, w io.Writer) error {

if c.hasAxes() {
xt, yt, yta = c.getAxesTicks(r, xr, yr, yra, xf, yf, yfa)
canvasBox = c.getAxisAdjustedCanvasBox(r, canvasBox, xr, yr, yra, xt, yt, yta)
canvasBox = c.getAxesAdjustedCanvasBox(r, canvasBox, xr, yr, yra, xt, yt, yta)
xr, yr, yra = c.setRangeDomains(canvasBox, xr, yr, yra)

// do a second pass in case things haven't settled yet.
xt, yt, yta = c.getAxesTicks(r, xr, yr, yra, xf, yf, yfa)
canvasBox = c.getAxisAdjustedCanvasBox(r, canvasBox, xr, yr, yra, xt, yt, yta)
canvasBox = c.getAxesAdjustedCanvasBox(r, canvasBox, xr, yr, yra, xt, yt, yta)
xr, yr, yra = c.setRangeDomains(canvasBox, xr, yr, yra)
}

Expand Down Expand Up @@ -320,7 +320,7 @@ func (c Chart) getAxesTicks(r Renderer, xr, yr, yar Range, xf, yf, yfa ValueForm
return
}

func (c Chart) getAxisAdjustedCanvasBox(r Renderer, canvasBox Box, xr, yr, yra Range, xticks, yticks, yticksAlt []Tick) Box {
func (c Chart) getAxesAdjustedCanvasBox(r Renderer, canvasBox Box, xr, yr, yra Range, xticks, yticks, yticksAlt []Tick) Box {
axesOuterBox := canvasBox.Clone()
if c.XAxis.Style.Show {
axesBounds := c.XAxis.Measure(r, canvasBox, xr, c.styleDefaultsAxes(), xticks)
Expand Down
Loading

0 comments on commit 27a5efd

Please sign in to comment.