slightly more rigorous bounds checking and auto-fit
This commit is contained in:
parent
bba75e5d4c
commit
28f01842de
13 changed files with 475 additions and 195 deletions
|
@ -45,8 +45,6 @@ func TestAnnotationSeriesMeasure(t *testing.T) {
|
||||||
Left: 5,
|
Left: 5,
|
||||||
Right: 105,
|
Right: 105,
|
||||||
Bottom: 105,
|
Bottom: 105,
|
||||||
Height: 100,
|
|
||||||
Width: 100,
|
|
||||||
}
|
}
|
||||||
sd := Style{
|
sd := Style{
|
||||||
FontSize: 10.0,
|
FontSize: 10.0,
|
||||||
|
@ -100,8 +98,6 @@ func TestAnnotationSeriesRender(t *testing.T) {
|
||||||
Left: 5,
|
Left: 5,
|
||||||
Right: 105,
|
Right: 105,
|
||||||
Bottom: 105,
|
Bottom: 105,
|
||||||
Height: 100,
|
|
||||||
Width: 100,
|
|
||||||
}
|
}
|
||||||
sd := Style{
|
sd := Style{
|
||||||
FontSize: 10.0,
|
FontSize: 10.0,
|
||||||
|
|
172
box.go
172
box.go
|
@ -8,9 +8,6 @@ type Box struct {
|
||||||
Left int
|
Left int
|
||||||
Right int
|
Right int
|
||||||
Bottom int
|
Bottom int
|
||||||
|
|
||||||
Height int
|
|
||||||
Width int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsZero returns if the box is set or not.
|
// IsZero returns if the box is set or not.
|
||||||
|
@ -66,3 +63,172 @@ func (b Box) GetBottom(defaults ...int) int {
|
||||||
}
|
}
|
||||||
return b.Bottom
|
return b.Bottom
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Width returns the width
|
||||||
|
func (b Box) Width() int {
|
||||||
|
return AbsInt(b.Right - b.Left)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Height returns the height
|
||||||
|
func (b Box) Height() int {
|
||||||
|
return AbsInt(b.Bottom - b.Top)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aspect returns the aspect ratio of the box.
|
||||||
|
func (b Box) Aspect() float64 {
|
||||||
|
return float64(b.Width()) / float64(b.Height())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone returns a new copy of the box.
|
||||||
|
func (b Box) Clone() Box {
|
||||||
|
return Box{
|
||||||
|
Top: b.Top,
|
||||||
|
Left: b.Left,
|
||||||
|
Right: b.Right,
|
||||||
|
Bottom: b.Bottom,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsBiggerThan returns if a box is bigger than another box.
|
||||||
|
func (b Box) IsBiggerThan(other Box) bool {
|
||||||
|
return b.Top < other.Top ||
|
||||||
|
b.Bottom > other.Bottom ||
|
||||||
|
b.Left < other.Left ||
|
||||||
|
b.Right > other.Right
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSmallerThan returns if a box is smaller than another box.
|
||||||
|
func (b Box) IsSmallerThan(other Box) bool {
|
||||||
|
return b.Top > other.Top &&
|
||||||
|
b.Bottom < other.Bottom &&
|
||||||
|
b.Left > other.Left &&
|
||||||
|
b.Right < other.Right
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equals returns if the box equals another box.
|
||||||
|
func (b Box) Equals(other Box) bool {
|
||||||
|
return b.Top == other.Top &&
|
||||||
|
b.Left == other.Left &&
|
||||||
|
b.Right == other.Right &&
|
||||||
|
b.Bottom == other.Bottom
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grow grows a box based on another box.
|
||||||
|
func (b Box) Grow(other Box) Box {
|
||||||
|
return Box{
|
||||||
|
Top: MinInt(b.Top, other.Top),
|
||||||
|
Left: MinInt(b.Left, other.Left),
|
||||||
|
Right: MaxInt(b.Right, other.Right),
|
||||||
|
Bottom: MaxInt(b.Bottom, other.Bottom),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shift pushes a box by x,y.
|
||||||
|
func (b Box) Shift(x, y int) Box {
|
||||||
|
return Box{
|
||||||
|
Top: b.Top + y,
|
||||||
|
Left: b.Left + x,
|
||||||
|
Right: b.Right + x,
|
||||||
|
Bottom: b.Bottom + y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
func (b Box) Fit(other Box) Box {
|
||||||
|
ba := b.Aspect()
|
||||||
|
oa := other.Aspect()
|
||||||
|
|
||||||
|
if oa == ba {
|
||||||
|
return b.Clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
bw, bh := float64(b.Width()), float64(b.Height())
|
||||||
|
bw2 := int(bw) >> 1
|
||||||
|
bh2 := int(bh) >> 1
|
||||||
|
if oa > ba { // ex. 16:9 vs. 4:3
|
||||||
|
var noh2 int
|
||||||
|
if oa > 1.0 {
|
||||||
|
noh2 = int(bw/oa) >> 1
|
||||||
|
} else {
|
||||||
|
noh2 = int(bh*oa) >> 1
|
||||||
|
}
|
||||||
|
return Box{
|
||||||
|
Top: (b.Top + bh2) - noh2,
|
||||||
|
Left: b.Left,
|
||||||
|
Right: b.Right,
|
||||||
|
Bottom: (b.Top + bh2) + noh2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var now2 int
|
||||||
|
if oa > 1.0 {
|
||||||
|
now2 = int(bh/oa) >> 1
|
||||||
|
} else {
|
||||||
|
now2 = int(bw*oa) >> 1
|
||||||
|
}
|
||||||
|
return Box{
|
||||||
|
Top: b.Top,
|
||||||
|
Left: (b.Left + bw2) - now2,
|
||||||
|
Right: (b.Left + bw2) + now2,
|
||||||
|
Bottom: b.Bottom,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constrain is similar to `Fit` except that it will work
|
||||||
|
// more literally like the opposite of grow.
|
||||||
|
func (b Box) Constrain(other Box) Box {
|
||||||
|
newBox := b.Clone()
|
||||||
|
if other.Top < b.Top {
|
||||||
|
delta := b.Top - other.Top
|
||||||
|
newBox.Top = other.Top + delta
|
||||||
|
}
|
||||||
|
|
||||||
|
if other.Left < b.Left {
|
||||||
|
delta := b.Left - other.Left
|
||||||
|
newBox.Left = other.Left + delta
|
||||||
|
}
|
||||||
|
|
||||||
|
if other.Right > b.Right {
|
||||||
|
delta := other.Right - b.Right
|
||||||
|
newBox.Right = other.Right - delta
|
||||||
|
}
|
||||||
|
|
||||||
|
if other.Bottom > b.Bottom {
|
||||||
|
delta := other.Bottom - b.Bottom
|
||||||
|
newBox.Bottom = other.Bottom - delta
|
||||||
|
}
|
||||||
|
return newBox
|
||||||
|
}
|
||||||
|
|
||||||
|
// OuterConstrain is similar to `Constraint` with the difference
|
||||||
|
// that it applies corrections
|
||||||
|
func (b Box) OuterConstrain(bounds, other Box) Box {
|
||||||
|
newBox := b.Clone()
|
||||||
|
if other.Top < bounds.Top {
|
||||||
|
delta := bounds.Top - other.Top
|
||||||
|
newBox.Top = b.Top + delta
|
||||||
|
}
|
||||||
|
|
||||||
|
if other.Left < bounds.Left {
|
||||||
|
delta := bounds.Left - other.Left
|
||||||
|
newBox.Left = b.Left + delta
|
||||||
|
}
|
||||||
|
|
||||||
|
if other.Right > bounds.Right {
|
||||||
|
delta := other.Right - bounds.Right
|
||||||
|
newBox.Right = b.Right - delta
|
||||||
|
}
|
||||||
|
|
||||||
|
if other.Bottom > bounds.Bottom {
|
||||||
|
delta := other.Bottom - bounds.Bottom
|
||||||
|
newBox.Bottom = b.Bottom - delta
|
||||||
|
}
|
||||||
|
return newBox
|
||||||
|
}
|
||||||
|
|
87
box_test.go
Normal file
87
box_test.go
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
package chart
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/blendlabs/go-assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBoxClone(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
a := Box{Top: 5, Left: 5, Right: 15, Bottom: 15}
|
||||||
|
b := a.Clone()
|
||||||
|
assert.True(a.Equals(b))
|
||||||
|
assert.True(b.Equals(a))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoxEquals(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
a := Box{Top: 5, Left: 5, Right: 15, Bottom: 15}
|
||||||
|
b := Box{Top: 10, Left: 10, Right: 30, Bottom: 30}
|
||||||
|
c := Box{Top: 5, Left: 5, Right: 15, Bottom: 15}
|
||||||
|
assert.True(a.Equals(a))
|
||||||
|
assert.True(a.Equals(c))
|
||||||
|
assert.True(c.Equals(a))
|
||||||
|
assert.False(a.Equals(b))
|
||||||
|
assert.False(c.Equals(b))
|
||||||
|
assert.False(b.Equals(a))
|
||||||
|
assert.False(b.Equals(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoxIsBiggerThan(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
a := Box{Top: 5, Left: 5, Right: 25, Bottom: 25}
|
||||||
|
b := Box{Top: 10, Left: 10, Right: 20, Bottom: 20} // only half bigger
|
||||||
|
c := Box{Top: 1, Left: 1, Right: 30, Bottom: 30} //bigger
|
||||||
|
assert.True(a.IsBiggerThan(b))
|
||||||
|
assert.False(a.IsBiggerThan(c))
|
||||||
|
assert.True(c.IsBiggerThan(a))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoxIsSmallerThan(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
a := Box{Top: 5, Left: 5, Right: 25, Bottom: 25}
|
||||||
|
b := Box{Top: 10, Left: 10, Right: 20, Bottom: 20} // only half bigger
|
||||||
|
c := Box{Top: 1, Left: 1, Right: 30, Bottom: 30} //bigger
|
||||||
|
assert.False(a.IsSmallerThan(b))
|
||||||
|
assert.True(a.IsSmallerThan(c))
|
||||||
|
assert.False(c.IsSmallerThan(a))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoxGrow(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
a := Box{Top: 1, Left: 2, Right: 15, Bottom: 15}
|
||||||
|
b := Box{Top: 4, Left: 5, Right: 30, Bottom: 35}
|
||||||
|
c := a.Grow(b)
|
||||||
|
assert.False(c.Equals(b))
|
||||||
|
assert.False(c.Equals(a))
|
||||||
|
assert.Equal(1, c.Top)
|
||||||
|
assert.Equal(2, c.Left)
|
||||||
|
assert.Equal(30, c.Right)
|
||||||
|
assert.Equal(35, c.Bottom)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoxFit(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
a := Box{Top: 64, Left: 64, Right: 192, Bottom: 192}
|
||||||
|
b := Box{Top: 16, Left: 16, Right: 256, Bottom: 170}
|
||||||
|
c := Box{Top: 16, Left: 16, Right: 170, Bottom: 256}
|
||||||
|
|
||||||
|
fab := a.Fit(b)
|
||||||
|
assert.Equal(a.Left, fab.Left)
|
||||||
|
assert.Equal(a.Right, fab.Right)
|
||||||
|
assert.True(fab.Top < fab.Bottom)
|
||||||
|
assert.True(fab.Left < fab.Right)
|
||||||
|
assert.True(math.Abs(b.Aspect()-fab.Aspect()) < 0.02)
|
||||||
|
|
||||||
|
fac := a.Fit(c)
|
||||||
|
assert.Equal(a.Top, fac.Top)
|
||||||
|
assert.Equal(a.Bottom, fac.Bottom)
|
||||||
|
assert.True(math.Abs(c.Aspect()-fac.Aspect()) < 0.02)
|
||||||
|
}
|
217
chart.go
217
chart.go
|
@ -99,13 +99,14 @@ func (c Chart) Render(rp RendererProvider, w io.Writer) error {
|
||||||
c.drawSeries(r, canvasBox, xr, yr, yra, series, index)
|
c.drawSeries(r, canvasBox, xr, yr, yra, series, index)
|
||||||
}
|
}
|
||||||
c.drawTitle(r)
|
c.drawTitle(r)
|
||||||
|
|
||||||
return r.Save(w)
|
return r.Save(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Chart) getRanges() (xrange, yrange, yrangeAlt Range) {
|
func (c Chart) getRanges() (xrange, yrange, yrangeAlt Range) {
|
||||||
var globalMinX, globalMaxX float64 = math.MaxFloat64, 0
|
var minx, maxx float64 = math.MaxFloat64, 0
|
||||||
var globalMinY, globalMaxY float64 = math.MaxFloat64, 0
|
var miny, maxy float64 = math.MaxFloat64, 0
|
||||||
var globalMinYA, globalMaxYA float64 = math.MaxFloat64, 0
|
var minya, maxya float64 = math.MaxFloat64, 0
|
||||||
|
|
||||||
for _, s := range c.Series {
|
for _, s := range c.Series {
|
||||||
seriesAxis := s.GetYAxis()
|
seriesAxis := s.GetYAxis()
|
||||||
|
@ -113,26 +114,16 @@ func (c Chart) getRanges() (xrange, yrange, yrangeAlt Range) {
|
||||||
seriesLength := vp.Len()
|
seriesLength := vp.Len()
|
||||||
for index := 0; index < seriesLength; index++ {
|
for index := 0; index < seriesLength; index++ {
|
||||||
vx, vy := vp.GetValue(index)
|
vx, vy := vp.GetValue(index)
|
||||||
if globalMinX > vx {
|
|
||||||
globalMinX = vx
|
minx = math.Min(minx, vx)
|
||||||
}
|
maxx = math.Max(maxx, vx)
|
||||||
if globalMaxX < vx {
|
|
||||||
globalMaxX = vx
|
|
||||||
}
|
|
||||||
if seriesAxis == YAxisPrimary {
|
if seriesAxis == YAxisPrimary {
|
||||||
if globalMinY > vy {
|
miny = math.Min(miny, vy)
|
||||||
globalMinY = vy
|
maxy = math.Max(maxy, vy)
|
||||||
}
|
|
||||||
if globalMaxY < vy {
|
|
||||||
globalMaxY = vy
|
|
||||||
}
|
|
||||||
} else if seriesAxis == YAxisSecondary {
|
} else if seriesAxis == YAxisSecondary {
|
||||||
if globalMinYA > vy {
|
minya = math.Min(minya, vy)
|
||||||
globalMinYA = vy
|
maxya = math.Max(maxya, vy)
|
||||||
}
|
|
||||||
if globalMaxYA < vy {
|
|
||||||
globalMaxYA = vy
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -142,17 +133,16 @@ func (c Chart) getRanges() (xrange, yrange, yrangeAlt Range) {
|
||||||
xrange.Min = c.XAxis.Range.Min
|
xrange.Min = c.XAxis.Range.Min
|
||||||
xrange.Max = c.XAxis.Range.Max
|
xrange.Max = c.XAxis.Range.Max
|
||||||
} else {
|
} else {
|
||||||
xrange.Min = globalMinX
|
xrange.Min = minx
|
||||||
xrange.Max = globalMaxX
|
xrange.Max = maxx
|
||||||
//xrange.Min, xrange.Max = xrange.GetRoundedRangeBounds()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !c.YAxis.Range.IsZero() {
|
if !c.YAxis.Range.IsZero() {
|
||||||
yrange.Min = c.YAxis.Range.Min
|
yrange.Min = c.YAxis.Range.Min
|
||||||
yrange.Max = c.YAxis.Range.Max
|
yrange.Max = c.YAxis.Range.Max
|
||||||
} else {
|
} else {
|
||||||
yrange.Min = globalMinY
|
yrange.Min = miny
|
||||||
yrange.Max = globalMaxY
|
yrange.Max = maxy
|
||||||
yrange.Min, yrange.Max = yrange.GetRoundedRangeBounds()
|
yrange.Min, yrange.Max = yrange.GetRoundedRangeBounds()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,8 +150,8 @@ func (c Chart) getRanges() (xrange, yrange, yrangeAlt Range) {
|
||||||
yrangeAlt.Min = c.YAxisSecondary.Range.Min
|
yrangeAlt.Min = c.YAxisSecondary.Range.Min
|
||||||
yrangeAlt.Max = c.YAxisSecondary.Range.Max
|
yrangeAlt.Max = c.YAxisSecondary.Range.Max
|
||||||
} else {
|
} else {
|
||||||
yrangeAlt.Min = globalMinYA
|
yrangeAlt.Min = minya
|
||||||
yrangeAlt.Max = globalMaxYA
|
yrangeAlt.Max = maxya
|
||||||
yrangeAlt.Min, yrangeAlt.Max = yrangeAlt.GetRoundedRangeBounds()
|
yrangeAlt.Min, yrangeAlt.Max = yrangeAlt.GetRoundedRangeBounds()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,19 +159,17 @@ func (c Chart) getRanges() (xrange, yrange, yrangeAlt Range) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Chart) getDefaultCanvasBox() Box {
|
func (c Chart) getDefaultCanvasBox() Box {
|
||||||
|
dpt := c.Background.Padding.GetTop(DefaultBackgroundPadding.Top)
|
||||||
dpl := c.Background.Padding.GetLeft(DefaultBackgroundPadding.Left)
|
dpl := c.Background.Padding.GetLeft(DefaultBackgroundPadding.Left)
|
||||||
dpr := c.Background.Padding.GetRight(DefaultBackgroundPadding.Right)
|
dpr := c.Background.Padding.GetRight(DefaultBackgroundPadding.Right)
|
||||||
dpb := c.Background.Padding.GetBottom(DefaultBackgroundPadding.Bottom)
|
dpb := c.Background.Padding.GetBottom(DefaultBackgroundPadding.Bottom)
|
||||||
|
|
||||||
cb := Box{
|
return Box{
|
||||||
Top: c.Background.Padding.GetTop(DefaultBackgroundPadding.Top),
|
Top: dpt,
|
||||||
Left: dpl,
|
Left: dpl,
|
||||||
Right: c.Width - dpr,
|
Right: c.Width - dpr,
|
||||||
Bottom: c.Height - dpb,
|
Bottom: c.Height - dpb,
|
||||||
}
|
}
|
||||||
cb.Height = cb.Bottom - cb.Top
|
|
||||||
cb.Width = cb.Right - cb.Left
|
|
||||||
return cb
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Chart) getValueFormatters() (x, y, ya ValueFormatter) {
|
func (c Chart) getValueFormatters() (x, y, ya ValueFormatter) {
|
||||||
|
@ -227,71 +215,30 @@ func (c Chart) getAxesTicks(r Renderer, xr, yr, yar Range, xf, yf, yfa ValueForm
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Chart) getAxisAdjustedCanvasBox(r Renderer, canvasBox Box, xr, yr, yra Range, xticks, yticks, yticksAlt []Tick) Box {
|
func (c Chart) getAxisAdjustedCanvasBox(r Renderer, canvasBox Box, xr, yr, yra Range, xticks, yticks, yticksAlt []Tick) Box {
|
||||||
axesMinX, axesMaxX, axesMinY, axesMaxY := math.MaxInt32, 0, math.MaxInt32, 0
|
axesOuterBox := canvasBox.Clone()
|
||||||
if c.XAxis.Style.Show {
|
if c.XAxis.Style.Show {
|
||||||
axesBounds := c.XAxis.Measure(r, canvasBox, xr, xticks)
|
axesBounds := c.XAxis.Measure(r, canvasBox, xr, xticks)
|
||||||
axesMinY = MinInt(axesMinX, axesBounds.Top)
|
axesOuterBox = axesOuterBox.Grow(axesBounds)
|
||||||
axesMinX = MinInt(axesMinY, axesBounds.Left)
|
|
||||||
axesMaxX = MaxInt(axesMaxX, axesBounds.Right)
|
|
||||||
axesMaxY = MaxInt(axesMaxY, axesBounds.Bottom)
|
|
||||||
}
|
}
|
||||||
if c.YAxis.Style.Show {
|
if c.YAxis.Style.Show {
|
||||||
axesBounds := c.YAxis.Measure(r, canvasBox, yr, yticks)
|
axesBounds := c.YAxis.Measure(r, canvasBox, yr, yticks)
|
||||||
axesMinY = MinInt(axesMinX, axesBounds.Top)
|
axesOuterBox = axesOuterBox.Grow(axesBounds)
|
||||||
axesMinX = MinInt(axesMinY, axesBounds.Left)
|
|
||||||
axesMaxX = MaxInt(axesMaxX, axesBounds.Right)
|
|
||||||
axesMaxY = MaxInt(axesMaxY, axesBounds.Bottom)
|
|
||||||
}
|
}
|
||||||
if c.YAxisSecondary.Style.Show {
|
if c.YAxisSecondary.Style.Show {
|
||||||
axesBounds := c.YAxisSecondary.Measure(r, canvasBox, yra, yticksAlt)
|
axesBounds := c.YAxisSecondary.Measure(r, canvasBox, yra, yticksAlt)
|
||||||
axesMinY = MinInt(axesMinX, axesBounds.Top)
|
axesOuterBox = axesOuterBox.Grow(axesBounds)
|
||||||
axesMinX = MinInt(axesMinY, axesBounds.Left)
|
|
||||||
axesMaxX = MaxInt(axesMaxX, axesBounds.Right)
|
|
||||||
axesMaxY = MaxInt(axesMaxY, axesBounds.Bottom)
|
|
||||||
}
|
|
||||||
newBox := Box{
|
|
||||||
Top: canvasBox.Top,
|
|
||||||
Left: canvasBox.Left,
|
|
||||||
Right: canvasBox.Right,
|
|
||||||
Bottom: canvasBox.Bottom,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if axesMinY < 0 {
|
return canvasBox.OuterConstrain(c.asBox(), axesOuterBox)
|
||||||
// figure out how much top padding to add
|
|
||||||
delta := -1 * axesMinY
|
|
||||||
newBox.Top = canvasBox.Top + delta
|
|
||||||
}
|
|
||||||
|
|
||||||
if axesMinX < 0 {
|
|
||||||
// figure out how much left padding to add
|
|
||||||
delta := -1 * axesMinX
|
|
||||||
newBox.Left = canvasBox.Left + delta
|
|
||||||
}
|
|
||||||
|
|
||||||
if axesMaxX > c.Width {
|
|
||||||
// figure out how much right padding to add
|
|
||||||
delta := axesMaxX - c.Width
|
|
||||||
newBox.Right = canvasBox.Right - delta
|
|
||||||
}
|
|
||||||
|
|
||||||
if axesMaxY > c.Height {
|
|
||||||
//figure out how much bottom padding to add
|
|
||||||
delta := axesMaxY - c.Height
|
|
||||||
newBox.Bottom = canvasBox.Bottom - delta
|
|
||||||
}
|
|
||||||
|
|
||||||
newBox.Height = newBox.Bottom - newBox.Top
|
|
||||||
newBox.Width = newBox.Right - newBox.Left
|
|
||||||
return newBox
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Chart) setRangeDomains(canvasBox Box, xr, yr, yra Range) (xr2, yr2, yra2 Range) {
|
func (c Chart) setRangeDomains(canvasBox Box, xr, yr, yra Range) (xr2, yr2, yra2 Range) {
|
||||||
xr2.Min, xr2.Max = xr.Min, xr.Max
|
xr2.Min, xr2.Max = xr.Min, xr.Max
|
||||||
xr2.Domain = canvasBox.Width
|
xr2.Domain = canvasBox.Width()
|
||||||
yr2.Min, yr2.Max = yr.Min, yr.Max
|
yr2.Min, yr2.Max = yr.Min, yr.Max
|
||||||
yr2.Domain = canvasBox.Height
|
yr2.Domain = canvasBox.Height()
|
||||||
yra2.Min, yra2.Max = yra.Min, yra.Max
|
yra2.Min, yra2.Max = yra.Min, yra.Max
|
||||||
yra2.Domain = canvasBox.Height
|
yra2.Domain = canvasBox.Height()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -307,11 +254,11 @@ func (c Chart) hasAnnotationSeries() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Chart) getAnnotationAdjustedCanvasBox(r Renderer, canvasBox Box, xr, yr, yra Range, xf, yf, yfa ValueFormatter) Box {
|
func (c Chart) getAnnotationAdjustedCanvasBox(r Renderer, canvasBox Box, xr, yr, yra Range, xf, yf, yfa ValueFormatter) Box {
|
||||||
annotationMinX, annotationMaxX, annotationMinY, annotationMaxY := math.MaxInt32, 0, math.MaxInt32, 0
|
annotationSeriesBox := canvasBox.Clone()
|
||||||
for seriesIndex, s := range c.Series {
|
for seriesIndex, s := range c.Series {
|
||||||
if as, isAnnotationSeries := s.(AnnotationSeries); isAnnotationSeries {
|
if as, isAnnotationSeries := s.(AnnotationSeries); isAnnotationSeries {
|
||||||
if as.Style.Show {
|
if as.Style.Show {
|
||||||
style := c.getSeriesStyleDefaults(seriesIndex)
|
style := c.seriesStyleDefaults(seriesIndex)
|
||||||
var annotationBounds Box
|
var annotationBounds Box
|
||||||
if as.YAxis == YAxisPrimary {
|
if as.YAxis == YAxisPrimary {
|
||||||
annotationBounds = as.Measure(r, canvasBox, xr, yr, style)
|
annotationBounds = as.Measure(r, canvasBox, xr, yr, style)
|
||||||
|
@ -319,74 +266,28 @@ func (c Chart) getAnnotationAdjustedCanvasBox(r Renderer, canvasBox Box, xr, yr,
|
||||||
annotationBounds = as.Measure(r, canvasBox, xr, yra, style)
|
annotationBounds = as.Measure(r, canvasBox, xr, yra, style)
|
||||||
}
|
}
|
||||||
|
|
||||||
annotationMinY = MinInt(annotationMinY, annotationBounds.Top)
|
annotationSeriesBox = annotationSeriesBox.Grow(annotationBounds)
|
||||||
annotationMinX = MinInt(annotationMinX, annotationBounds.Left)
|
|
||||||
annotationMaxX = MaxInt(annotationMaxX, annotationBounds.Right)
|
|
||||||
annotationMaxY = MaxInt(annotationMaxY, annotationBounds.Bottom)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
newBox := Box{
|
return canvasBox.OuterConstrain(c.asBox(), annotationSeriesBox)
|
||||||
Top: canvasBox.Top,
|
|
||||||
Left: canvasBox.Left,
|
|
||||||
Right: canvasBox.Right,
|
|
||||||
Bottom: canvasBox.Bottom,
|
|
||||||
}
|
|
||||||
if annotationMinY < 0 {
|
|
||||||
// figure out how much top padding to add
|
|
||||||
delta := -1 * annotationMinY
|
|
||||||
newBox.Top = canvasBox.Top + delta
|
|
||||||
}
|
|
||||||
|
|
||||||
if annotationMinX < 0 {
|
|
||||||
// figure out how much left padding to add
|
|
||||||
delta := -1 * annotationMinX
|
|
||||||
newBox.Left = canvasBox.Left + delta
|
|
||||||
}
|
|
||||||
|
|
||||||
if annotationMaxX > c.Width {
|
|
||||||
// figure out how much right padding to add
|
|
||||||
delta := annotationMaxX - c.Width
|
|
||||||
newBox.Right = canvasBox.Right - delta
|
|
||||||
}
|
|
||||||
|
|
||||||
if annotationMaxY > c.Height {
|
|
||||||
//figure out how much bottom padding to add
|
|
||||||
delta := annotationMaxY - c.Height
|
|
||||||
newBox.Bottom = canvasBox.Bottom - delta
|
|
||||||
}
|
|
||||||
|
|
||||||
newBox.Height = newBox.Bottom - newBox.Top
|
|
||||||
newBox.Width = newBox.Right - newBox.Left
|
|
||||||
|
|
||||||
return newBox
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Chart) drawBackground(r Renderer) {
|
func (c Chart) drawBackground(r Renderer) {
|
||||||
r.SetFillColor(c.Background.GetFillColor(DefaultBackgroundColor))
|
DrawBox(r, Box{Right: c.Width, Bottom: c.Height}, c.Canvas.WithDefaultsFrom(Style{
|
||||||
r.SetStrokeColor(c.Background.GetStrokeColor(DefaultBackgroundStrokeColor))
|
FillColor: DefaultBackgroundColor,
|
||||||
r.SetStrokeWidth(c.Background.GetStrokeWidth(DefaultStrokeWidth))
|
StrokeColor: DefaultBackgroundStrokeColor,
|
||||||
r.MoveTo(0, 0)
|
StrokeWidth: DefaultStrokeWidth,
|
||||||
r.LineTo(c.Width, 0)
|
}))
|
||||||
r.LineTo(c.Width, c.Height)
|
|
||||||
r.LineTo(0, c.Height)
|
|
||||||
r.LineTo(0, 0)
|
|
||||||
r.Close()
|
|
||||||
r.FillStroke()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Chart) drawCanvas(r Renderer, canvasBox Box) {
|
func (c Chart) drawCanvas(r Renderer, canvasBox Box) {
|
||||||
r.SetFillColor(c.Canvas.GetFillColor(DefaultCanvasColor))
|
DrawBox(r, canvasBox, c.Canvas.WithDefaultsFrom(Style{
|
||||||
r.SetStrokeColor(c.Canvas.GetStrokeColor(DefaultCanvasStrokColor))
|
FillColor: DefaultCanvasColor,
|
||||||
r.SetStrokeWidth(c.Canvas.GetStrokeWidth(DefaultStrokeWidth))
|
StrokeColor: DefaultCanvasStrokeColor,
|
||||||
r.MoveTo(canvasBox.Left, canvasBox.Top)
|
StrokeWidth: DefaultStrokeWidth,
|
||||||
r.LineTo(canvasBox.Right, canvasBox.Top)
|
}))
|
||||||
r.LineTo(canvasBox.Right, canvasBox.Bottom)
|
|
||||||
r.LineTo(canvasBox.Left, canvasBox.Bottom)
|
|
||||||
r.LineTo(canvasBox.Left, canvasBox.Top)
|
|
||||||
r.Close()
|
|
||||||
r.FillStroke()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Chart) drawAxes(r Renderer, canvasBox Box, xrange, yrange, yrangeAlt Range, xticks, yticks, yticksAlt []Tick) {
|
func (c Chart) drawAxes(r Renderer, canvasBox Box, xrange, yrange, yrangeAlt Range, xticks, yticks, yticksAlt []Tick) {
|
||||||
|
@ -401,21 +302,11 @@ func (c Chart) drawAxes(r Renderer, canvasBox Box, xrange, yrange, yrangeAlt Ran
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Chart) getSeriesStyleDefaults(seriesIndex int) Style {
|
|
||||||
strokeColor := GetDefaultSeriesStrokeColor(seriesIndex)
|
|
||||||
return Style{
|
|
||||||
StrokeColor: strokeColor,
|
|
||||||
StrokeWidth: DefaultStrokeWidth,
|
|
||||||
Font: c.Font,
|
|
||||||
FontSize: DefaultFontSize,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c Chart) drawSeries(r Renderer, canvasBox Box, xrange, yrange, yrangeAlt Range, s Series, seriesIndex int) {
|
func (c Chart) drawSeries(r Renderer, canvasBox Box, xrange, yrange, yrangeAlt Range, s Series, seriesIndex int) {
|
||||||
if s.GetYAxis() == YAxisPrimary {
|
if s.GetYAxis() == YAxisPrimary {
|
||||||
s.Render(r, canvasBox, xrange, yrange, c.getSeriesStyleDefaults(seriesIndex))
|
s.Render(r, canvasBox, xrange, yrange, c.seriesStyleDefaults(seriesIndex))
|
||||||
} else if s.GetYAxis() == YAxisSecondary {
|
} else if s.GetYAxis() == YAxisSecondary {
|
||||||
s.Render(r, canvasBox, xrange, yrangeAlt, c.getSeriesStyleDefaults(seriesIndex))
|
s.Render(r, canvasBox, xrange, yrangeAlt, c.seriesStyleDefaults(seriesIndex))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -428,8 +319,8 @@ func (c Chart) drawTitle(r Renderer) {
|
||||||
|
|
||||||
textBox := r.MeasureText(c.Title)
|
textBox := r.MeasureText(c.Title)
|
||||||
|
|
||||||
textWidth := textBox.Width
|
textWidth := textBox.Width()
|
||||||
textHeight := textBox.Height
|
textHeight := textBox.Height()
|
||||||
|
|
||||||
titleX := (c.Width >> 1) - (textWidth >> 1)
|
titleX := (c.Width >> 1) - (textWidth >> 1)
|
||||||
titleY := c.TitleStyle.Padding.GetTop(DefaultTitleTop) + textHeight
|
titleY := c.TitleStyle.Padding.GetTop(DefaultTitleTop) + textHeight
|
||||||
|
@ -437,3 +328,17 @@ func (c Chart) drawTitle(r Renderer) {
|
||||||
r.Text(c.Title, titleX, titleY)
|
r.Text(c.Title, titleX, titleY)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c Chart) seriesStyleDefaults(seriesIndex int) Style {
|
||||||
|
strokeColor := GetDefaultSeriesStrokeColor(seriesIndex)
|
||||||
|
return Style{
|
||||||
|
StrokeColor: strokeColor,
|
||||||
|
StrokeWidth: DefaultStrokeWidth,
|
||||||
|
Font: c.Font,
|
||||||
|
FontSize: DefaultFontSize,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Chart) asBox() Box {
|
||||||
|
return Box{Right: c.Width, Bottom: c.Height}
|
||||||
|
}
|
||||||
|
|
|
@ -67,7 +67,7 @@ var (
|
||||||
DefaultCanvasColor = drawing.Color{R: 255, G: 255, B: 255, A: 255}
|
DefaultCanvasColor = drawing.Color{R: 255, G: 255, B: 255, A: 255}
|
||||||
// DefaultCanvasStrokColor is the default chart canvas stroke color.
|
// DefaultCanvasStrokColor is the default chart canvas stroke color.
|
||||||
// It is equivalent to css color:white.
|
// It is equivalent to css color:white.
|
||||||
DefaultCanvasStrokColor = drawing.Color{R: 255, G: 255, B: 255, A: 255}
|
DefaultCanvasStrokeColor = drawing.Color{R: 255, G: 255, B: 255, A: 255}
|
||||||
// DefaultTextColor is the default chart text color.
|
// DefaultTextColor is the default chart text color.
|
||||||
// It is equivalent to #333333.
|
// It is equivalent to #333333.
|
||||||
DefaultTextColor = drawing.Color{R: 51, G: 51, B: 51, A: 255}
|
DefaultTextColor = drawing.Color{R: 51, G: 51, B: 51, A: 255}
|
||||||
|
|
|
@ -51,7 +51,9 @@ func MeasureAnnotation(r Renderer, canvasBox Box, s Style, lx, ly int, label str
|
||||||
r.SetFont(s.GetFont())
|
r.SetFont(s.GetFont())
|
||||||
r.SetFontSize(s.GetFontSize(DefaultAnnotationFontSize))
|
r.SetFontSize(s.GetFontSize(DefaultAnnotationFontSize))
|
||||||
textBox := r.MeasureText(label)
|
textBox := r.MeasureText(label)
|
||||||
halfTextHeight := textBox.Height >> 1
|
textWidth := textBox.Width()
|
||||||
|
textHeight := textBox.Height()
|
||||||
|
halfTextHeight := textHeight >> 1
|
||||||
|
|
||||||
pt := s.Padding.GetTop(DefaultAnnotationPadding.Top)
|
pt := s.Padding.GetTop(DefaultAnnotationPadding.Top)
|
||||||
pl := s.Padding.GetLeft(DefaultAnnotationPadding.Left)
|
pl := s.Padding.GetLeft(DefaultAnnotationPadding.Left)
|
||||||
|
@ -61,7 +63,7 @@ func MeasureAnnotation(r Renderer, canvasBox Box, s Style, lx, ly int, label str
|
||||||
strokeWidth := s.GetStrokeWidth()
|
strokeWidth := s.GetStrokeWidth()
|
||||||
|
|
||||||
top := ly - (pt + halfTextHeight)
|
top := ly - (pt + halfTextHeight)
|
||||||
right := lx + pl + pr + textBox.Width + DefaultAnnotationDeltaWidth + int(strokeWidth)
|
right := lx + pl + pr + textWidth + DefaultAnnotationDeltaWidth + int(strokeWidth)
|
||||||
bottom := ly + (pb + halfTextHeight)
|
bottom := ly + (pb + halfTextHeight)
|
||||||
|
|
||||||
return Box{
|
return Box{
|
||||||
|
@ -69,8 +71,6 @@ func MeasureAnnotation(r Renderer, canvasBox Box, s Style, lx, ly int, label str
|
||||||
Left: lx,
|
Left: lx,
|
||||||
Right: right,
|
Right: right,
|
||||||
Bottom: bottom,
|
Bottom: bottom,
|
||||||
Width: right - lx,
|
|
||||||
Height: bottom - top,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,7 +79,8 @@ func DrawAnnotation(r Renderer, canvasBox Box, s Style, lx, ly int, label string
|
||||||
r.SetFont(s.GetFont())
|
r.SetFont(s.GetFont())
|
||||||
r.SetFontSize(s.GetFontSize(DefaultAnnotationFontSize))
|
r.SetFontSize(s.GetFontSize(DefaultAnnotationFontSize))
|
||||||
textBox := r.MeasureText(label)
|
textBox := r.MeasureText(label)
|
||||||
halfTextHeight := textBox.Height >> 1
|
textWidth := textBox.Width()
|
||||||
|
halfTextHeight := textBox.Height() >> 1
|
||||||
|
|
||||||
pt := s.Padding.GetTop(DefaultAnnotationPadding.Top)
|
pt := s.Padding.GetTop(DefaultAnnotationPadding.Top)
|
||||||
pl := s.Padding.GetLeft(DefaultAnnotationPadding.Left)
|
pl := s.Padding.GetLeft(DefaultAnnotationPadding.Left)
|
||||||
|
@ -92,10 +93,10 @@ func DrawAnnotation(r Renderer, canvasBox Box, s Style, lx, ly int, label string
|
||||||
ltx := lx + DefaultAnnotationDeltaWidth
|
ltx := lx + DefaultAnnotationDeltaWidth
|
||||||
lty := ly - (pt + halfTextHeight)
|
lty := ly - (pt + halfTextHeight)
|
||||||
|
|
||||||
rtx := lx + pl + pr + textBox.Width + DefaultAnnotationDeltaWidth
|
rtx := lx + pl + pr + textWidth + DefaultAnnotationDeltaWidth
|
||||||
rty := ly - (pt + halfTextHeight)
|
rty := ly - (pt + halfTextHeight)
|
||||||
|
|
||||||
rbx := lx + pl + pr + textBox.Width + DefaultAnnotationDeltaWidth
|
rbx := lx + pl + pr + textWidth + DefaultAnnotationDeltaWidth
|
||||||
rby := ly + (pb + halfTextHeight)
|
rby := ly + (pb + halfTextHeight)
|
||||||
|
|
||||||
lbx := lx + DefaultAnnotationDeltaWidth
|
lbx := lx + DefaultAnnotationDeltaWidth
|
||||||
|
@ -131,3 +132,27 @@ func DrawBox(r Renderer, b Box, s Style) {
|
||||||
r.LineTo(b.Left, b.Top)
|
r.LineTo(b.Left, b.Top)
|
||||||
r.FillStroke()
|
r.FillStroke()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DrawText draws text with a given style.
|
||||||
|
func DrawText(r Renderer, text string, x, y int, s Style) {
|
||||||
|
r.SetFillColor(s.GetFillColor())
|
||||||
|
r.SetStrokeColor(s.GetStrokeColor())
|
||||||
|
r.SetStrokeWidth(s.GetStrokeWidth())
|
||||||
|
r.SetFont(s.GetFont())
|
||||||
|
r.SetFontSize(s.GetFontSize())
|
||||||
|
r.Text(text, x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DrawTextCentered draws text with a given style centered.
|
||||||
|
func DrawTextCentered(r Renderer, text string, x, y int, s Style) {
|
||||||
|
r.SetFillColor(s.GetFillColor())
|
||||||
|
r.SetStrokeColor(s.GetStrokeColor())
|
||||||
|
r.SetStrokeWidth(s.GetStrokeWidth())
|
||||||
|
r.SetFont(s.GetFont())
|
||||||
|
r.SetFontSize(s.GetFontSize())
|
||||||
|
|
||||||
|
tb := r.MeasureText(text)
|
||||||
|
tx := x - (tb.Width() >> 1)
|
||||||
|
ty := y - (tb.Height() >> 1)
|
||||||
|
r.Text(text, tx, ty)
|
||||||
|
}
|
||||||
|
|
|
@ -162,8 +162,6 @@ func (rr *rasterRenderer) MeasureText(body string) Box {
|
||||||
Left: int(math.Ceil(l)),
|
Left: int(math.Ceil(l)),
|
||||||
Right: int(math.Ceil(r)),
|
Right: int(math.Ceil(r)),
|
||||||
Bottom: int(math.Ceil(b)),
|
Bottom: int(math.Ceil(b)),
|
||||||
Width: int(math.Ceil(r - l)),
|
|
||||||
Height: int(math.Ceil(b - t)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
@ -8,6 +9,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/wcharczuk/go-chart"
|
"github.com/wcharczuk/go-chart"
|
||||||
|
"github.com/wcharczuk/go-chart/drawing"
|
||||||
"github.com/wcharczuk/go-web"
|
"github.com/wcharczuk/go-web"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -36,7 +38,7 @@ func chartHandler(rc *web.RequestContext) web.ControllerResult {
|
||||||
c := chart.Chart{
|
c := chart.Chart{
|
||||||
Title: "A Test Chart",
|
Title: "A Test Chart",
|
||||||
TitleStyle: chart.Style{
|
TitleStyle: chart.Style{
|
||||||
Show: true,
|
Show: false,
|
||||||
},
|
},
|
||||||
Width: 1024,
|
Width: 1024,
|
||||||
Height: 400,
|
Height: 400,
|
||||||
|
@ -91,6 +93,104 @@ func chartHandler(rc *web.RequestContext) web.ControllerResult {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func boxHandler(rc *web.RequestContext) web.ControllerResult {
|
||||||
|
r, err := chart.PNG(1024, 1024)
|
||||||
|
if err != nil {
|
||||||
|
rc.API().InternalError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := chart.GetDefaultFont()
|
||||||
|
if err != nil {
|
||||||
|
return rc.API().InternalError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
//1:1 128wx128h @ 64,64
|
||||||
|
a := chart.Box{Top: 64, Left: 64, Right: 192, Bottom: 192}
|
||||||
|
|
||||||
|
// 3:2 256x170 @ 16, 16
|
||||||
|
//b := chart.Box{Top: 16, Left: 16, Right: 256, Bottom: 170}
|
||||||
|
|
||||||
|
// 2:3 170x256 @ 16, 16
|
||||||
|
c := chart.Box{Top: 16, Left: 16, Right: 170, Bottom: 256}
|
||||||
|
|
||||||
|
//fitb := a.Fit(b)
|
||||||
|
fitc := a.Fit(c)
|
||||||
|
//growb := a.Grow(b)
|
||||||
|
//growc := a.Grow(c)
|
||||||
|
//grow := a.Grow(b).Grow(c)
|
||||||
|
|
||||||
|
conc := a.Constrain(c)
|
||||||
|
|
||||||
|
boxStyle := chart.Style{
|
||||||
|
StrokeColor: drawing.ColorBlack,
|
||||||
|
StrokeWidth: 1.0,
|
||||||
|
Font: f,
|
||||||
|
FontSize: 18.0,
|
||||||
|
}
|
||||||
|
|
||||||
|
computedBoxStyle := chart.Style{
|
||||||
|
StrokeColor: drawing.ColorRed,
|
||||||
|
StrokeWidth: 1.0,
|
||||||
|
Font: f,
|
||||||
|
FontSize: 18.0,
|
||||||
|
}
|
||||||
|
|
||||||
|
chart.DrawBox(r, a, boxStyle)
|
||||||
|
//chart.DrawBox(r, b, boxStyle)
|
||||||
|
chart.DrawBox(r, c, boxStyle)
|
||||||
|
//chart.DrawBox(r, fitb, computedBoxStyle)
|
||||||
|
chart.DrawBox(r, fitc, computedBoxStyle)
|
||||||
|
/*chart.DrawBox(r, growb, computedBoxStyle)
|
||||||
|
chart.DrawBox(r, growc, computedBoxStyle)
|
||||||
|
chart.DrawBox(r, grow, computedBoxStyle)*/
|
||||||
|
chart.DrawBox(r, conc, computedBoxStyle)
|
||||||
|
|
||||||
|
ax, ay := a.Center()
|
||||||
|
chart.DrawTextCentered(r, "a", ax, ay, boxStyle.WithDefaultsFrom(chart.Style{
|
||||||
|
FillColor: boxStyle.StrokeColor,
|
||||||
|
}))
|
||||||
|
|
||||||
|
/*bx, by := b.Center()
|
||||||
|
chart.DrawTextCentered(r, "b", bx, by, boxStyle.WithDefaultsFrom(chart.Style{
|
||||||
|
FillColor: boxStyle.StrokeColor,
|
||||||
|
}))*/
|
||||||
|
|
||||||
|
cx, cy := c.Center()
|
||||||
|
chart.DrawTextCentered(r, "c", cx, cy, boxStyle.WithDefaultsFrom(chart.Style{
|
||||||
|
FillColor: boxStyle.StrokeColor,
|
||||||
|
}))
|
||||||
|
|
||||||
|
/*fbx, fby := fitb.Center()
|
||||||
|
chart.DrawTextCentered(r, "a fit b", fbx, fby, computedBoxStyle.WithDefaultsFrom(chart.Style{
|
||||||
|
FillColor: computedBoxStyle.StrokeColor,
|
||||||
|
}))*/
|
||||||
|
|
||||||
|
fcx, fcy := fitc.Center()
|
||||||
|
chart.DrawTextCentered(r, "a fit c", fcx, fcy, computedBoxStyle.WithDefaultsFrom(chart.Style{
|
||||||
|
FillColor: computedBoxStyle.StrokeColor,
|
||||||
|
}))
|
||||||
|
|
||||||
|
/*gbx, gby := growb.Center()
|
||||||
|
chart.DrawTextCentered(r, "a grow b", gbx, gby, computedBoxStyle.WithDefaultsFrom(chart.Style{
|
||||||
|
FillColor: computedBoxStyle.StrokeColor,
|
||||||
|
}))
|
||||||
|
|
||||||
|
gcx, gcy := growc.Center()
|
||||||
|
chart.DrawTextCentered(r, "a grow c", gcx, gcy, computedBoxStyle.WithDefaultsFrom(chart.Style{
|
||||||
|
FillColor: computedBoxStyle.StrokeColor,
|
||||||
|
}))*/
|
||||||
|
|
||||||
|
ccx, ccy := conc.Center()
|
||||||
|
chart.DrawTextCentered(r, "a const c", ccx, ccy, computedBoxStyle.WithDefaultsFrom(chart.Style{
|
||||||
|
FillColor: computedBoxStyle.StrokeColor,
|
||||||
|
}))
|
||||||
|
|
||||||
|
rc.Response.Header().Set("Content-Type", "image/png")
|
||||||
|
buffer := bytes.NewBuffer([]byte{})
|
||||||
|
err = r.Save(buffer)
|
||||||
|
return rc.Raw(buffer.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
app := web.New()
|
app := web.New()
|
||||||
app.SetName("Chart Test Server")
|
app.SetName("Chart Test Server")
|
||||||
|
@ -100,5 +200,6 @@ func main() {
|
||||||
app.GET("/favico.ico", func(rc *web.RequestContext) web.ControllerResult {
|
app.GET("/favico.ico", func(rc *web.RequestContext) web.ControllerResult {
|
||||||
return rc.Raw([]byte{})
|
return rc.Raw([]byte{})
|
||||||
})
|
})
|
||||||
|
app.GET("/box", boxHandler)
|
||||||
log.Fatal(app.Start())
|
log.Fatal(app.Start())
|
||||||
}
|
}
|
||||||
|
|
8
util.go
8
util.go
|
@ -114,3 +114,11 @@ func MaxInt(values ...int) int {
|
||||||
}
|
}
|
||||||
return max
|
return max
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AbsInt returns the absolute value of an integer.
|
||||||
|
func AbsInt(value int) int {
|
||||||
|
if value < 0 {
|
||||||
|
return -value
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
|
@ -153,8 +153,6 @@ 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))
|
||||||
box.Width = w
|
|
||||||
box.Height = int(drawing.PointsToPixels(vr.dpi, vr.s.FontSize))
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,6 @@ func TestVectorRendererMeasureText(t *testing.T) {
|
||||||
vr.SetFontSize(12.0)
|
vr.SetFontSize(12.0)
|
||||||
|
|
||||||
tb := vr.MeasureText("Ljp")
|
tb := vr.MeasureText("Ljp")
|
||||||
assert.Equal(21, tb.Width)
|
assert.Equal(21, tb.Width())
|
||||||
assert.Equal(15, tb.Height)
|
assert.Equal(15, tb.Height())
|
||||||
}
|
}
|
||||||
|
|
14
xaxis.go
14
xaxis.go
|
@ -56,7 +56,7 @@ func (xa XAxis) getTickCount(r Renderer, ra Range, vf ValueFormatter) int {
|
||||||
ll = ln
|
ll = ln
|
||||||
}
|
}
|
||||||
llb := r.MeasureText(ll)
|
llb := r.MeasureText(ll)
|
||||||
textWidth := llb.Width
|
textWidth := llb.Width()
|
||||||
width := textWidth + DefaultMinimumTickHorizontalSpacing
|
width := textWidth + DefaultMinimumTickHorizontalSpacing
|
||||||
count := int(math.Ceil(float64(ra.Domain) / float64(width)))
|
count := int(math.Ceil(float64(ra.Domain) / float64(width)))
|
||||||
return count
|
return count
|
||||||
|
@ -77,11 +77,11 @@ func (xa XAxis) Measure(r Renderer, canvasBox Box, ra Range, ticks []Tick) Box {
|
||||||
tb := r.MeasureText(t.Label)
|
tb := r.MeasureText(t.Label)
|
||||||
|
|
||||||
tx := canvasBox.Left + lx
|
tx := canvasBox.Left + lx
|
||||||
ty := canvasBox.Bottom + DefaultXAxisMargin + tb.Height
|
ty := canvasBox.Bottom + DefaultXAxisMargin + tb.Height()
|
||||||
|
|
||||||
top = MinInt(top, canvasBox.Bottom)
|
top = MinInt(top, canvasBox.Bottom)
|
||||||
left = MinInt(left, tx-(tb.Width>>1))
|
left = MinInt(left, tx-(tb.Width()>>1))
|
||||||
right = MaxInt(right, tx+(tb.Width>>1))
|
right = MaxInt(right, tx+(tb.Width()>>1))
|
||||||
bottom = MaxInt(bottom, ty)
|
bottom = MaxInt(bottom, ty)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,8 +90,6 @@ func (xa XAxis) Measure(r Renderer, canvasBox Box, ra Range, ticks []Tick) Box {
|
||||||
Left: left,
|
Left: left,
|
||||||
Right: right,
|
Right: right,
|
||||||
Bottom: bottom,
|
Bottom: bottom,
|
||||||
Width: right - left,
|
|
||||||
Height: bottom - top,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,8 +114,8 @@ func (xa XAxis) Render(r Renderer, canvasBox Box, ra Range, ticks []Tick) {
|
||||||
lx := ra.Translate(v)
|
lx := ra.Translate(v)
|
||||||
tb := r.MeasureText(t.Label)
|
tb := r.MeasureText(t.Label)
|
||||||
tx := canvasBox.Left + lx
|
tx := canvasBox.Left + lx
|
||||||
ty := canvasBox.Bottom + DefaultXAxisMargin + tb.Height
|
ty := canvasBox.Bottom + DefaultXAxisMargin + tb.Height()
|
||||||
r.Text(t.Label, tx-tb.Width>>1, ty)
|
r.Text(t.Label, tx-tb.Width()>>1, ty)
|
||||||
|
|
||||||
r.MoveTo(tx, canvasBox.Bottom)
|
r.MoveTo(tx, canvasBox.Bottom)
|
||||||
r.LineTo(tx, canvasBox.Bottom+DefaultVerticalTickHeight)
|
r.LineTo(tx, canvasBox.Bottom+DefaultVerticalTickHeight)
|
||||||
|
|
16
yaxis.go
16
yaxis.go
|
@ -59,7 +59,7 @@ func (ya YAxis) getTickCount(r Renderer, ra Range, vf ValueFormatter) int {
|
||||||
//given the domain, figure out how many ticks we can draw ...
|
//given the domain, figure out how many ticks we can draw ...
|
||||||
label := vf(ra.Min)
|
label := vf(ra.Min)
|
||||||
tb := r.MeasureText(label)
|
tb := r.MeasureText(label)
|
||||||
return int(math.Ceil(float64(ra.Domain) / float64(tb.Height+DefaultMinimumTickVerticalSpacing)))
|
return int(math.Ceil(float64(ra.Domain) / float64(tb.Height()+DefaultMinimumTickVerticalSpacing)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Measure returns the bounds of the axis.
|
// Measure returns the bounds of the axis.
|
||||||
|
@ -85,18 +85,18 @@ func (ya YAxis) Measure(r Renderer, canvasBox Box, ra Range, ticks []Tick) Box {
|
||||||
tb := r.MeasureText(t.Label)
|
tb := r.MeasureText(t.Label)
|
||||||
finalTextX := tx
|
finalTextX := tx
|
||||||
if ya.AxisType == YAxisSecondary {
|
if ya.AxisType == YAxisSecondary {
|
||||||
finalTextX = tx - tb.Width
|
finalTextX = tx - tb.Width()
|
||||||
}
|
}
|
||||||
|
|
||||||
if ya.AxisType == YAxisPrimary {
|
if ya.AxisType == YAxisPrimary {
|
||||||
minx = canvasBox.Right
|
minx = canvasBox.Right
|
||||||
maxx = MaxInt(maxx, tx+tb.Width)
|
maxx = MaxInt(maxx, tx+tb.Width())
|
||||||
} else if ya.AxisType == YAxisSecondary {
|
} else if ya.AxisType == YAxisSecondary {
|
||||||
minx = MinInt(minx, finalTextX)
|
minx = MinInt(minx, finalTextX)
|
||||||
maxx = MaxInt(maxx, tx)
|
maxx = MaxInt(maxx, tx)
|
||||||
}
|
}
|
||||||
miny = MinInt(miny, ly-tb.Height>>1)
|
miny = MinInt(miny, ly-tb.Height()>>1)
|
||||||
maxy = MaxInt(maxy, ly+tb.Height>>1)
|
maxy = MaxInt(maxy, ly+tb.Height()>>1)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Box{
|
return Box{
|
||||||
|
@ -104,8 +104,6 @@ func (ya YAxis) Measure(r Renderer, canvasBox Box, ra Range, ticks []Tick) Box {
|
||||||
Left: minx,
|
Left: minx,
|
||||||
Right: maxx,
|
Right: maxx,
|
||||||
Bottom: maxy,
|
Bottom: maxy,
|
||||||
Width: maxx - minx,
|
|
||||||
Height: maxy - miny,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,9 +140,9 @@ func (ya YAxis) Render(r Renderer, canvasBox Box, ra Range, ticks []Tick) {
|
||||||
tb := r.MeasureText(t.Label)
|
tb := r.MeasureText(t.Label)
|
||||||
|
|
||||||
finalTextX := tx
|
finalTextX := tx
|
||||||
finalTextY := ly + tb.Height>>1
|
finalTextY := ly + tb.Height()>>1
|
||||||
if ya.AxisType == YAxisSecondary {
|
if ya.AxisType == YAxisSecondary {
|
||||||
finalTextX = tx - tb.Width
|
finalTextX = tx - tb.Width()
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Text(t.Label, finalTextX, finalTextY)
|
r.Text(t.Label, finalTextX, finalTextY)
|
||||||
|
|
Loading…
Reference in a new issue