This commit is contained in:
Will Charczuk 2017-05-17 16:01:23 -07:00
parent e3e851d2d1
commit de6df027fc
14 changed files with 116 additions and 85 deletions

View File

@ -261,7 +261,7 @@ func (bc BarChart) drawYAxis(r Renderer, canvasBox Box, yr Range, ticks []Tick)
r.Stroke()
var ty int
var tb Box
var tb Box2d
for _, t := range ticks {
ty = canvasBox.Bottom - yr.Translate(t.Value)
@ -272,7 +272,7 @@ func (bc BarChart) drawYAxis(r Renderer, canvasBox Box, yr Range, ticks []Tick)
axisStyle.GetTextOptions().WriteToRenderer(r)
tb = r.MeasureText(t.Label)
Draw.Text(r, t.Label, canvasBox.Right+DefaultYAxisMargin+5, ty+(tb.Height()>>1), axisStyle)
Draw.Text(r, t.Label, canvasBox.Right+DefaultYAxisMargin+5, ty+(int(tb.Height())>>1), axisStyle)
}
}
@ -369,7 +369,7 @@ func (bc BarChart) getAdjustedCanvasBox(r Renderer, canvasBox Box, yrange Range,
lines := Text.WrapFit(r, bar.Label, barLabelBox.Width(), axisStyle)
linesBox := Text.MeasureLines(r, lines, axisStyle)
xaxisHeight = util.Math.MinInt(linesBox.Height()+(2*DefaultXAxisMargin), xaxisHeight)
xaxisHeight = util.Math.MinInt(int(linesBox.Height())+(2*DefaultXAxisMargin), xaxisHeight)
}
}

View File

@ -26,13 +26,33 @@ func (bc Box2d) Points() []Point {
// Box return the Box2d as a regular box.
func (bc Box2d) Box() Box {
return Box{
Top: int(math.Min(bc.TopLeft.Y, bc.TopRight.Y)),
Left: int(math.Min(bc.TopLeft.X, bc.BottomLeft.X)),
Right: int(math.Max(bc.TopRight.X, bc.BottomRight.X)),
Bottom: int(math.Max(bc.BottomLeft.Y, bc.BottomRight.Y)),
Top: int(bc.Top()),
Left: int(bc.Left()),
Right: int(bc.Right()),
Bottom: int(bc.Bottom()),
}
}
// Top returns the top-most corner y value.
func (bc Box2d) Top() float64 {
return math.Min(bc.TopLeft.Y, bc.TopRight.Y)
}
// Left returns the left-most corner x value.
func (bc Box2d) Left() float64 {
return math.Min(bc.TopLeft.X, bc.BottomLeft.X)
}
// Right returns the right-most corner x value.
func (bc Box2d) Right() float64 {
return math.Max(bc.TopRight.X, bc.BottomRight.X)
}
// Bottom returns the bottom-most corner y value.
func (bc Box2d) Bottom() float64 {
return math.Max(bc.BottomLeft.Y, bc.BottomLeft.Y)
}
// Width returns the width
func (bc Box2d) Width() float64 {
minLeft := math.Min(bc.TopLeft.X, bc.BottomLeft.X)
@ -117,6 +137,17 @@ func (bc Box2d) Overlaps(other Box2d) bool {
return false
}
// Grow grows a box by a given set of dimensions.
func (bc Box2d) Grow(by Box) Box2d {
top, left, right, bottom := float64(by.Top), float64(by.Left), float64(by.Right), float64(by.Bottom)
return Box2d{
TopLeft: Point{X: bc.TopLeft.X - left, Y: bc.TopLeft.Y - top},
TopRight: Point{X: bc.TopRight.X + right, Y: bc.TopRight.Y - top},
BottomRight: Point{X: bc.BottomRight.X + right, Y: bc.BottomRight.Y + bottom},
BottomLeft: Point{X: bc.BottomLeft.X - left, Y: bc.BottomLeft.Y + bottom},
}
}
func (bc Box2d) String() string {
return fmt.Sprintf("Box2d{%s,%s,%s,%s}", bc.TopLeft.String(), bc.TopRight.String(), bc.BottomRight.String(), bc.BottomLeft.String())
}
@ -136,8 +167,8 @@ func (p Point) Shift(x, y float64) Point {
// 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)
dx := math.Pow(p.X-other.X, 2)
dy := math.Pow(p.Y-other.Y, 2)
return math.Pow(dx+dy, 0.5)
}
@ -148,5 +179,5 @@ func (p Point) Equals(other Point) bool {
// String returns a string representation of the point.
func (p Point) String() string {
return fmt.Sprintf("P{%.2f,%.2f}", p.X, p.Y)
return fmt.Sprintf("(%.2f,%.2f)", p.X, p.Y)
}

View File

@ -502,8 +502,8 @@ func (c Chart) drawTitle(r Renderer) {
textWidth := textBox.Width()
textHeight := textBox.Height()
titleX := (c.GetWidth() >> 1) - (textWidth >> 1)
titleY := c.TitleStyle.Padding.GetTop(DefaultTitleTop) + textHeight
titleX := (int(c.GetWidth()) >> 1) - (int(textWidth) >> 1)
titleY := c.TitleStyle.Padding.GetTop(DefaultTitleTop) + int(textHeight)
r.Text(c.Title, titleX, titleY)
}

22
draw.go
View File

@ -233,8 +233,8 @@ func (d draw) MeasureAnnotation(r Renderer, canvasBox Box, style Style, lx, ly i
defer r.ResetStyle()
textBox := r.MeasureText(label)
textWidth := textBox.Width()
textHeight := textBox.Height()
textWidth := int(textBox.Width())
textHeight := int(textBox.Height())
halfTextHeight := textHeight >> 1
pt := style.Padding.GetTop(DefaultAnnotationPadding.Top)
@ -262,8 +262,8 @@ func (d draw) Annotation(r Renderer, canvasBox Box, style Style, lx, ly int, lab
defer r.ResetStyle()
textBox := r.MeasureText(label)
textWidth := textBox.Width()
halfTextHeight := textBox.Height() >> 1
textWidth := int(textBox.Width())
halfTextHeight := int(textBox.Height()) >> 1
style.GetFillAndStrokeOptions().WriteToRenderer(r)
@ -337,7 +337,7 @@ func (d draw) Text(r Renderer, text string, x, y int, style Style) {
r.Text(text, x, y)
}
func (d draw) MeasureText(r Renderer, text string, style Style) Box {
func (d draw) MeasureText(r Renderer, text string, style Style) Box2d {
style.GetTextOptions().WriteToRenderer(r)
defer r.ResetStyle()
@ -356,9 +356,9 @@ func (d draw) TextWithin(r Renderer, text string, box Box, style Style) {
switch style.GetTextVerticalAlign() {
case TextVerticalAlignBottom, TextVerticalAlignBaseline: // i have to build better baseline handling into measure text
y = y - linesBox.Height()
y = y - int(linesBox.Height())
case TextVerticalAlignMiddle, TextVerticalAlignMiddleBaseline:
y = (y - linesBox.Height()) >> 1
y = (y - int(linesBox.Height())) >> 1
}
var tx, ty int
@ -366,19 +366,19 @@ func (d draw) TextWithin(r Renderer, text string, box Box, style Style) {
lineBox := r.MeasureText(line)
switch style.GetTextHorizontalAlign() {
case TextHorizontalAlignCenter:
tx = box.Left + ((box.Width() - lineBox.Width()) >> 1)
tx = box.Left + ((int(box.Width()) - int(lineBox.Width())) >> 1)
case TextHorizontalAlignRight:
tx = box.Right - lineBox.Width()
tx = box.Right - int(lineBox.Width())
default:
tx = box.Left
}
if style.TextRotationDegrees == 0 {
ty = y + lineBox.Height()
ty = y + int(lineBox.Height())
} else {
ty = y
}
r.Text(line, tx, ty)
y += lineBox.Height() + style.GetTextLineSpacing()
y += int(lineBox.Height()) + style.GetTextLineSpacing()
}
}

View File

@ -67,8 +67,8 @@ func Legend(c *Chart, userDefaults ...Style) Renderable {
if labelCount > 0 {
legendContent.Bottom += DefaultMinimumTickVerticalSpacing
}
legendContent.Bottom += tb.Height()
right := legendContent.Left + tb.Width() + lineTextGap + lineLengthMinimum
legendContent.Bottom += int(tb.Height())
right := legendContent.Left + int(tb.Width()) + lineTextGap + lineLengthMinimum
legendContent.Right = util.Math.MaxInt(legendContent.Right, right)
labelCount++
}
@ -95,12 +95,12 @@ func Legend(c *Chart, userDefaults ...Style) Renderable {
tb := r.MeasureText(label)
ty := ycursor + tb.Height()
ty := ycursor + int(tb.Height())
r.Text(label, tx, ty)
th2 := tb.Height() >> 1
th2 := int(tb.Height()) >> 1
lx := tx + tb.Width() + lineTextGap
lx := tx + int(tb.Width()) + lineTextGap
ly := ty - th2
lx2 := legendContent.Right - legendPadding.Right
@ -112,7 +112,7 @@ func Legend(c *Chart, userDefaults ...Style) Renderable {
r.LineTo(lx2, ly)
r.Stroke()
ycursor += tb.Height()
ycursor += int(tb.Height())
legendCount++
}
}
@ -160,12 +160,12 @@ func LegendThin(c *Chart, userDefaults ...Style) Renderable {
var textHeight int
var textWidth int
var textBox Box
var textBox Box2d
for x := 0; x < len(labels); x++ {
if len(labels[x]) > 0 {
textBox = r.MeasureText(labels[x])
textHeight = util.Math.MaxInt(textBox.Height(), textHeight)
textWidth = util.Math.MaxInt(textBox.Width(), textWidth)
textHeight = util.Math.MaxInt(int(textBox.Height()), textHeight)
textWidth = util.Math.MaxInt(int(textBox.Width()), textWidth)
}
}
@ -200,7 +200,7 @@ func LegendThin(c *Chart, userDefaults ...Style) Renderable {
textBox = r.MeasureText(label)
r.Text(label, tx, ty)
lx = tx + textBox.Width() + lineTextGap
lx = tx + int(textBox.Width()) + lineTextGap
ly = ty - th2
r.SetStrokeColor(lines[index].GetStrokeColor())
@ -211,7 +211,7 @@ func LegendThin(c *Chart, userDefaults ...Style) Renderable {
r.LineTo(lx+lineLengthMinimum, ly)
r.Stroke()
tx += textBox.Width() + DefaultMinimumTickHorizontalSpacing + lineTextGap + lineLengthMinimum
tx += int(textBox.Width()) + DefaultMinimumTickHorizontalSpacing + lineTextGap + lineLengthMinimum
}
}
}
@ -279,8 +279,8 @@ func LegendLeft(c *Chart, userDefaults ...Style) Renderable {
if labelCount > 0 {
legendContent.Bottom += DefaultMinimumTickVerticalSpacing
}
legendContent.Bottom += tb.Height()
right := legendContent.Left + tb.Width() + lineTextGap + lineLengthMinimum
legendContent.Bottom += int(tb.Height())
right := legendContent.Left + int(tb.Width()) + lineTextGap + lineLengthMinimum
legendContent.Right = util.Math.MaxInt(legendContent.Right, right)
labelCount++
}
@ -307,12 +307,12 @@ func LegendLeft(c *Chart, userDefaults ...Style) Renderable {
tb := r.MeasureText(label)
ty := ycursor + tb.Height()
ty := ycursor + int(tb.Height())
r.Text(label, tx, ty)
th2 := tb.Height() >> 1
th2 := int(tb.Height()) >> 1
lx := tx + tb.Width() + lineTextGap
lx := tx + int(tb.Width()) + lineTextGap
ly := ty - th2
lx2 := legendContent.Right - legendPadding.Right
@ -324,7 +324,7 @@ func LegendLeft(c *Chart, userDefaults ...Style) Renderable {
r.LineTo(lx2, ly)
r.Stroke()
ycursor += tb.Height()
ycursor += int(tb.Height())
legendCount++
}
}

View File

@ -155,7 +155,7 @@ func (mhr *MarketHoursRange) measureTimes(r Renderer, defaults Style, vf ValueFo
timeLabel := vf(t)
labelBox := r.MeasureText(timeLabel)
total += labelBox.Width()
total += int(labelBox.Width())
if index > 0 {
total += DefaultMinimumTickHorizontalSpacing
}

View File

@ -162,8 +162,8 @@ func (pc PieChart) drawSlices(r Renderer, canvasBox Box, values []Value) {
lx, ly = util.Math.CirclePoint(cx, cy, labelRadius, delta2)
tb := r.MeasureText(v.Label)
lx = lx - (tb.Width() >> 1)
ly = ly + (tb.Height() >> 1)
lx = lx - (int(tb.Width()) >> 1)
ly = ly + (int(tb.Height()) >> 1)
r.Text(v.Label, lx, ly)
}

View File

@ -155,13 +155,13 @@ func (rr *rasterRenderer) Text(body string, x, y int) {
}
// MeasureText returns the height and width in pixels of a string.
func (rr *rasterRenderer) MeasureText(body string) Box {
func (rr *rasterRenderer) MeasureText(body string) Box2d {
rr.gc.SetFont(rr.s.Font)
rr.gc.SetFontSize(rr.s.FontSize)
rr.gc.SetFillColor(rr.s.FontColor)
l, t, r, b, err := rr.gc.GetStringBounds(body)
if err != nil {
return Box{}
return Box2d{}
}
if l < 0 {
r = r - l // equivalent to r+(-1*l)
@ -189,10 +189,10 @@ func (rr *rasterRenderer) MeasureText(body string) Box {
Bottom: int(math.Ceil(b)),
}
if rr.rotateRadians == nil {
return textBox
return textBox.Corners()
}
return textBox.Corners().Rotate(util.Math.RadiansToDegrees(*rr.rotateRadians)).Box()
return textBox.Corners().Rotate(util.Math.RadiansToDegrees(*rr.rotateRadians))
}
// SetTextRotation sets a text rotation.

View File

@ -73,7 +73,7 @@ type Renderer interface {
Text(body string, x, y int)
// MeasureText measures text.
MeasureText(body string) Box
MeasureText(body string) Box2d
// SetTextRotatation sets a rotation for drawing elements.
SetTextRotation(radians float64)

View File

@ -214,7 +214,7 @@ func (sbc StackedBarChart) drawYAxis(r Renderer, canvasBox Box) {
text := fmt.Sprintf("%0.0f%%", t*100)
tb := r.MeasureText(text)
Draw.Text(r, text, canvasBox.Right+DefaultYAxisMargin+5, ty+(tb.Height()>>1), axisStyle)
Draw.Text(r, text, canvasBox.Right+DefaultYAxisMargin+5, ty+(int(tb.Height())>>1), axisStyle)
}
}
@ -254,7 +254,7 @@ func (sbc StackedBarChart) getAdjustedCanvasBox(r Renderer, canvasBox Box) Box {
lines := Text.WrapFit(r, bar.Name, barLabelBox.Width(), axisStyle)
linesBox := Text.MeasureLines(r, lines, axisStyle)
xaxisHeight = util.Math.MaxInt(linesBox.Height()+(2*DefaultXAxisMargin), xaxisHeight)
xaxisHeight = util.Math.MaxInt(int(linesBox.Height())+(2*DefaultXAxisMargin), xaxisHeight)
}
}
return Box{

16
text.go
View File

@ -85,7 +85,7 @@ func (t text) WrapFitWord(r Renderer, value string, width int, style Style) []st
var line string
var word string
var textBox Box
var textBox Box2d
for _, c := range value {
if c == rune('\n') { // commit the line to output
@ -97,7 +97,7 @@ func (t text) WrapFitWord(r Renderer, value string, width int, style Style) []st
textBox = r.MeasureText(line + word + string(c))
if textBox.Width() >= width {
if int(textBox.Width()) >= width {
output = append(output, t.Trim(line))
line = word
word = string(c)
@ -120,7 +120,7 @@ func (t text) WrapFitRune(r Renderer, value string, width int, style Style) []st
var output []string
var line string
var textBox Box
var textBox Box2d
for _, c := range value {
if c == rune('\n') {
output = append(output, line)
@ -130,7 +130,7 @@ func (t text) WrapFitRune(r Renderer, value string, width int, style Style) []st
textBox = r.MeasureText(line + string(c))
if textBox.Width() >= width {
if int(textBox.Width()) >= width {
output = append(output, line)
line = string(c)
continue
@ -144,18 +144,18 @@ func (t text) Trim(value string) string {
return strings.Trim(value, " \t\n\r")
}
func (t text) MeasureLines(r Renderer, lines []string, style Style) Box {
func (t text) MeasureLines(r Renderer, lines []string, style Style) Box2d {
style.WriteTextOptionsToRenderer(r)
var output Box
for index, line := range lines {
lineBox := r.MeasureText(line)
output.Right = util.Math.MaxInt(lineBox.Right, output.Right)
output.Bottom += lineBox.Height()
output.Right = util.Math.MaxInt(int(lineBox.Right()), output.Right)
output.Bottom += int(lineBox.Height())
if index < len(lines)-1 {
output.Bottom += +style.GetTextLineSpacing()
}
}
return output
return output.Corners()
}
func (t text) appendLast(lines []string, text string) []string {

View File

@ -7,11 +7,10 @@ import (
"math"
"strings"
"golang.org/x/image/font"
util "github.com/blendlabs/go-util"
"github.com/golang/freetype/truetype"
"github.com/wcharczuk/go-chart/drawing"
"github.com/wcharczuk/go-chart/util"
"golang.org/x/image/font"
)
// SVG returns a new png/raster renderer.
@ -162,7 +161,8 @@ func (vr *vectorRenderer) Text(body string, x, y int) {
}
// MeasureText uses the truetype font drawer to measure the width of text.
func (vr *vectorRenderer) MeasureText(body string) (box Box) {
func (vr *vectorRenderer) MeasureText(body string) Box2d {
var box Box
if vr.s.GetFont() != nil {
vr.fc = &font.Drawer{
Face: truetype.NewFace(vr.s.GetFont(), &truetype.Options{
@ -175,11 +175,11 @@ func (vr *vectorRenderer) MeasureText(body string) (box Box) {
box.Right = w
box.Bottom = int(drawing.PointsToPixels(vr.dpi, vr.s.FontSize))
if vr.c.textTheta == nil {
return
return box.Corners()
}
box = box.Corners().Rotate(util.Math.RadiansToDegrees(*vr.c.textTheta)).Box()
return box.Corners().Rotate(util.Math.RadiansToDegrees(*vr.c.textTheta))
}
return
return box.Corners()
}
// SetTextRotation sets the text rotation.

View File

@ -91,11 +91,11 @@ func (xa XAxis) Measure(r Renderer, canvasBox Box, ra Range, defaults Style, tic
tb := Draw.MeasureText(r, t.Label, tickStyle.GetTextOptions())
tx = canvasBox.Left + ra.Translate(v)
ty = canvasBox.Bottom + DefaultXAxisMargin + tb.Height()
ty = canvasBox.Bottom + DefaultXAxisMargin + int(tb.Height())
switch tp {
case TickPositionUnderTick, TickPositionUnset:
ltx = tx - tb.Width()>>1
rtx = tx + tb.Width()>>1
ltx = tx - int(tb.Width())>>1
rtx = tx + int(tb.Width())>>1
break
case TickPositionBetweenTicks:
if index > 0 {
@ -112,7 +112,7 @@ func (xa XAxis) Measure(r Renderer, canvasBox Box, ra Range, defaults Style, tic
if xa.NameStyle.Show && len(xa.Name) > 0 {
tb := Draw.MeasureText(r, xa.Name, xa.NameStyle.InheritFrom(defaults))
bottom += DefaultXAxisMargin + tb.Height()
bottom += DefaultXAxisMargin + int(tb.Height())
}
return Box{
@ -153,13 +153,13 @@ func (xa XAxis) Render(r Renderer, canvasBox Box, ra Range, defaults Style, tick
switch tp {
case TickPositionUnderTick, TickPositionUnset:
if tickStyle.TextRotationDegrees == 0 {
tx = tx - tb.Width()>>1
ty = canvasBox.Bottom + DefaultXAxisMargin + tb.Height()
tx = tx - int(tb.Width())>>1
ty = canvasBox.Bottom + DefaultXAxisMargin + int(tb.Height())
} else {
ty = canvasBox.Bottom + (1.5 * DefaultXAxisMargin)
}
Draw.Text(r, t.Label, tx, ty, tickWithAxisStyle)
maxTextHeight = util.Math.MaxInt(maxTextHeight, tb.Height())
maxTextHeight = util.Math.MaxInt(maxTextHeight, int(tb.Height()))
break
case TickPositionBetweenTicks:
if index > 0 {
@ -175,7 +175,7 @@ func (xa XAxis) Render(r Renderer, canvasBox Box, ra Range, defaults Style, tick
}, finalTickStyle)
ftb := Text.MeasureLines(r, Text.WrapFit(r, t.Label, tx-ltx, finalTickStyle), finalTickStyle)
maxTextHeight = util.Math.MaxInt(maxTextHeight, ftb.Height())
maxTextHeight = util.Math.MaxInt(maxTextHeight, int(ftb.Height()))
}
break
}
@ -184,8 +184,8 @@ func (xa XAxis) Render(r Renderer, canvasBox Box, ra Range, defaults Style, tick
nameStyle := xa.NameStyle.InheritFrom(defaults)
if xa.NameStyle.Show && len(xa.Name) > 0 {
tb := Draw.MeasureText(r, xa.Name, nameStyle)
tx := canvasBox.Right - (canvasBox.Width()>>1 + tb.Width()>>1)
ty := canvasBox.Bottom + DefaultXAxisMargin + maxTextHeight + DefaultXAxisMargin + tb.Height()
tx := canvasBox.Right - (canvasBox.Width()>>1 + int(tb.Width())>>1)
ty := canvasBox.Bottom + DefaultXAxisMargin + maxTextHeight + DefaultXAxisMargin + int(tb.Height())
Draw.Text(r, xa.Name, tx, ty, nameStyle)
}

View File

@ -99,17 +99,17 @@ func (ya YAxis) Measure(r Renderer, canvasBox Box, ra Range, defaults Style, tic
ly := canvasBox.Bottom - ra.Translate(v)
tb := r.MeasureText(t.Label)
tbh2 := tb.Height() >> 1
tbh2 := int(tb.Height()) >> 1
finalTextX := tx
if ya.AxisType == YAxisSecondary {
finalTextX = tx - tb.Width()
finalTextX = tx - int(tb.Width())
}
maxTextHeight = util.Math.MaxInt(tb.Height(), maxTextHeight)
maxTextHeight = util.Math.MaxInt(int(tb.Height()), maxTextHeight)
if ya.AxisType == YAxisPrimary {
minx = canvasBox.Right
maxx = util.Math.MaxInt(maxx, tx+tb.Width())
maxx = util.Math.MaxInt(maxx, tx+int(tb.Width()))
} else if ya.AxisType == YAxisSecondary {
minx = util.Math.MinInt(minx, finalTextX)
maxx = util.Math.MaxInt(maxx, tx)
@ -160,18 +160,18 @@ func (ya YAxis) Render(r Renderer, canvasBox Box, ra Range, defaults Style, tick
tb := Draw.MeasureText(r, t.Label, tickStyle)
if tb.Width() > maxTextWidth {
maxTextWidth = tb.Width()
if int(tb.Width()) > maxTextWidth {
maxTextWidth = int(tb.Width())
}
if ya.AxisType == YAxisSecondary {
finalTextX = tx - tb.Width()
finalTextX = tx - int(tb.Width())
} else {
finalTextX = tx
}
if tickStyle.TextRotationDegrees == 0 {
finalTextY = ly + tb.Height()>>1
finalTextY = ly + int(tb.Height())>>1
} else {
finalTextY = ly
}
@ -203,9 +203,9 @@ func (ya YAxis) Render(r Renderer, canvasBox Box, ra Range, defaults Style, tick
var ty int
if nameStyle.TextRotationDegrees == 0 {
ty = canvasBox.Top + (canvasBox.Height()>>1 - tb.Width()>>1)
ty = canvasBox.Top + (canvasBox.Height()>>1 - int(tb.Width())>>1)
} else {
ty = canvasBox.Top + (canvasBox.Height()>>1 - tb.Height()>>1)
ty = canvasBox.Top + (canvasBox.Height()>>1 - int(tb.Height())>>1)
}
Draw.Text(r, ya.Name, tx, ty, nameStyle)