package chart import ( "fmt" util "github.com/wcharczuk/go-chart/util" ) var ( // BoxZero is a preset box that represents an intentional zero value. BoxZero = Box{IsSet: true} ) // NewBox returns a new (set) box. func NewBox(top, left, right, bottom int) Box { return Box{ IsSet: true, Top: top, Left: left, Right: right, Bottom: bottom, } } // Box represents the main 4 dimensions of a box. type Box struct { Top int Left int Right int Bottom int IsSet bool } // IsZero returns if the box is set or not. func (b Box) IsZero() bool { if b.IsSet { return false } return b.Top == 0 && b.Left == 0 && b.Right == 0 && b.Bottom == 0 } // String returns a string representation of the box. func (b Box) String() string { return fmt.Sprintf("box(%d,%d,%d,%d)", b.Top, b.Left, b.Right, b.Bottom) } // GetTop returns a coalesced value with a default. func (b Box) GetTop(defaults ...int) int { if !b.IsSet && b.Top == 0 { if len(defaults) > 0 { return defaults[0] } return 0 } return b.Top } // GetLeft returns a coalesced value with a default. func (b Box) GetLeft(defaults ...int) int { if !b.IsSet && b.Left == 0 { if len(defaults) > 0 { return defaults[0] } return 0 } return b.Left } // GetRight returns a coalesced value with a default. func (b Box) GetRight(defaults ...int) int { if !b.IsSet && b.Right == 0 { if len(defaults) > 0 { return defaults[0] } return 0 } return b.Right } // GetBottom returns a coalesced value with a default. func (b Box) GetBottom(defaults ...int) int { if !b.IsSet && b.Bottom == 0 { if len(defaults) > 0 { return defaults[0] } return 0 } return b.Bottom } // Width returns the width func (b Box) Width() int { return util.Math.AbsInt(b.Right - b.Left) } // Height returns the height func (b Box) Height() int { return util.Math.AbsInt(b.Bottom - b.Top) } // Center returns the center of the box func (b Box) Center() (x, y int) { w2, h2 := b.Width()>>1, b.Height()>>1 return b.Left + w2, b.Top + h2 } // 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{ IsSet: b.IsSet, 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: util.Math.MinInt(b.Top, other.Top), Left: util.Math.MinInt(b.Left, other.Left), Right: util.Math.MaxInt(b.Right, other.Right), Bottom: util.Math.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, } } // Corners returns the box as a set of corners. func (b Box) Corners() Box2d { return Box2d{ TopLeft: Point{float64(b.Left), float64(b.Top)}, TopRight: Point{float64(b.Right), float64(b.Top)}, BottomRight: Point{float64(b.Right), float64(b.Bottom)}, BottomLeft: Point{float64(b.Left), float64(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. 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() newBox.Top = util.Math.MaxInt(newBox.Top, other.Top) newBox.Left = util.Math.MaxInt(newBox.Left, other.Left) newBox.Right = util.Math.MinInt(newBox.Right, other.Right) newBox.Bottom = util.Math.MinInt(newBox.Bottom, other.Bottom) 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 }