Adding upstream version 2.1.2.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
c8c64afc61
commit
41a2f19f12
220 changed files with 19814 additions and 0 deletions
491
bar_chart.go
Normal file
491
bar_chart.go
Normal file
|
@ -0,0 +1,491 @@
|
|||
package chart
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
|
||||
"github.com/golang/freetype/truetype"
|
||||
)
|
||||
|
||||
// BarChart is a chart that draws bars on a range.
|
||||
type BarChart struct {
|
||||
Title string
|
||||
TitleStyle Style
|
||||
|
||||
ColorPalette ColorPalette
|
||||
|
||||
Width int
|
||||
Height int
|
||||
DPI float64
|
||||
|
||||
BarWidth int
|
||||
|
||||
Background Style
|
||||
Canvas Style
|
||||
|
||||
XAxis Style
|
||||
YAxis YAxis
|
||||
|
||||
BarSpacing int
|
||||
|
||||
UseBaseValue bool
|
||||
BaseValue float64
|
||||
|
||||
Font *truetype.Font
|
||||
defaultFont *truetype.Font
|
||||
|
||||
Bars []Value
|
||||
Elements []Renderable
|
||||
}
|
||||
|
||||
// GetDPI returns the dpi for the chart.
|
||||
func (bc BarChart) GetDPI() float64 {
|
||||
if bc.DPI == 0 {
|
||||
return DefaultDPI
|
||||
}
|
||||
return bc.DPI
|
||||
}
|
||||
|
||||
// GetFont returns the text font.
|
||||
func (bc BarChart) GetFont() *truetype.Font {
|
||||
if bc.Font == nil {
|
||||
return bc.defaultFont
|
||||
}
|
||||
return bc.Font
|
||||
}
|
||||
|
||||
// GetWidth returns the chart width or the default value.
|
||||
func (bc BarChart) GetWidth() int {
|
||||
if bc.Width == 0 {
|
||||
return DefaultChartWidth
|
||||
}
|
||||
return bc.Width
|
||||
}
|
||||
|
||||
// GetHeight returns the chart height or the default value.
|
||||
func (bc BarChart) GetHeight() int {
|
||||
if bc.Height == 0 {
|
||||
return DefaultChartHeight
|
||||
}
|
||||
return bc.Height
|
||||
}
|
||||
|
||||
// GetBarSpacing returns the spacing between bars.
|
||||
func (bc BarChart) GetBarSpacing() int {
|
||||
if bc.BarSpacing == 0 {
|
||||
return DefaultBarSpacing
|
||||
}
|
||||
return bc.BarSpacing
|
||||
}
|
||||
|
||||
// GetBarWidth returns the default bar width.
|
||||
func (bc BarChart) GetBarWidth() int {
|
||||
if bc.BarWidth == 0 {
|
||||
return DefaultBarWidth
|
||||
}
|
||||
return bc.BarWidth
|
||||
}
|
||||
|
||||
// Render renders the chart with the given renderer to the given io.Writer.
|
||||
func (bc BarChart) Render(rp RendererProvider, w io.Writer) error {
|
||||
if len(bc.Bars) == 0 {
|
||||
return errors.New("please provide at least one bar")
|
||||
}
|
||||
|
||||
r, err := rp(bc.GetWidth(), bc.GetHeight())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if bc.Font == nil {
|
||||
defaultFont, err := GetDefaultFont()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bc.defaultFont = defaultFont
|
||||
}
|
||||
r.SetDPI(bc.GetDPI())
|
||||
|
||||
bc.drawBackground(r)
|
||||
|
||||
var canvasBox Box
|
||||
var yt []Tick
|
||||
var yr Range
|
||||
var yf ValueFormatter
|
||||
|
||||
canvasBox = bc.getDefaultCanvasBox()
|
||||
yr = bc.getRanges()
|
||||
if yr.GetMax()-yr.GetMin() == 0 {
|
||||
return fmt.Errorf("invalid data range; cannot be zero")
|
||||
}
|
||||
yr = bc.setRangeDomains(canvasBox, yr)
|
||||
yf = bc.getValueFormatters()
|
||||
|
||||
if bc.hasAxes() {
|
||||
yt = bc.getAxesTicks(r, yr, yf)
|
||||
canvasBox = bc.getAdjustedCanvasBox(r, canvasBox, yr, yt)
|
||||
yr = bc.setRangeDomains(canvasBox, yr)
|
||||
}
|
||||
bc.drawCanvas(r, canvasBox)
|
||||
bc.drawBars(r, canvasBox, yr)
|
||||
bc.drawXAxis(r, canvasBox)
|
||||
bc.drawYAxis(r, canvasBox, yr, yt)
|
||||
|
||||
bc.drawTitle(r)
|
||||
for _, a := range bc.Elements {
|
||||
a(r, canvasBox, bc.styleDefaultsElements())
|
||||
}
|
||||
|
||||
return r.Save(w)
|
||||
}
|
||||
|
||||
func (bc BarChart) drawCanvas(r Renderer, canvasBox Box) {
|
||||
Draw.Box(r, canvasBox, bc.getCanvasStyle())
|
||||
}
|
||||
|
||||
func (bc BarChart) getRanges() Range {
|
||||
var yrange Range
|
||||
if bc.YAxis.Range != nil && !bc.YAxis.Range.IsZero() {
|
||||
yrange = bc.YAxis.Range
|
||||
} else {
|
||||
yrange = &ContinuousRange{}
|
||||
}
|
||||
|
||||
if !yrange.IsZero() {
|
||||
return yrange
|
||||
}
|
||||
|
||||
if len(bc.YAxis.Ticks) > 0 {
|
||||
tickMin, tickMax := math.MaxFloat64, -math.MaxFloat64
|
||||
for _, t := range bc.YAxis.Ticks {
|
||||
tickMin = math.Min(tickMin, t.Value)
|
||||
tickMax = math.Max(tickMax, t.Value)
|
||||
}
|
||||
yrange.SetMin(tickMin)
|
||||
yrange.SetMax(tickMax)
|
||||
return yrange
|
||||
}
|
||||
|
||||
min, max := math.MaxFloat64, -math.MaxFloat64
|
||||
for _, b := range bc.Bars {
|
||||
min = math.Min(b.Value, min)
|
||||
max = math.Max(b.Value, max)
|
||||
}
|
||||
|
||||
yrange.SetMin(min)
|
||||
yrange.SetMax(max)
|
||||
|
||||
return yrange
|
||||
}
|
||||
|
||||
func (bc BarChart) drawBackground(r Renderer) {
|
||||
Draw.Box(r, Box{
|
||||
Right: bc.GetWidth(),
|
||||
Bottom: bc.GetHeight(),
|
||||
}, bc.getBackgroundStyle())
|
||||
}
|
||||
|
||||
func (bc BarChart) drawBars(r Renderer, canvasBox Box, yr Range) {
|
||||
xoffset := canvasBox.Left
|
||||
|
||||
width, spacing, _ := bc.calculateScaledTotalWidth(canvasBox)
|
||||
bs2 := spacing >> 1
|
||||
|
||||
var barBox Box
|
||||
var bxl, bxr, by int
|
||||
for index, bar := range bc.Bars {
|
||||
bxl = xoffset + bs2
|
||||
bxr = bxl + width
|
||||
|
||||
by = canvasBox.Bottom - yr.Translate(bar.Value)
|
||||
|
||||
if bc.UseBaseValue {
|
||||
barBox = Box{
|
||||
Top: by,
|
||||
Left: bxl,
|
||||
Right: bxr,
|
||||
Bottom: canvasBox.Bottom - yr.Translate(bc.BaseValue),
|
||||
}
|
||||
} else {
|
||||
barBox = Box{
|
||||
Top: by,
|
||||
Left: bxl,
|
||||
Right: bxr,
|
||||
Bottom: canvasBox.Bottom,
|
||||
}
|
||||
}
|
||||
|
||||
Draw.Box(r, barBox, bar.Style.InheritFrom(bc.styleDefaultsBar(index)))
|
||||
|
||||
xoffset += width + spacing
|
||||
}
|
||||
}
|
||||
|
||||
func (bc BarChart) drawXAxis(r Renderer, canvasBox Box) {
|
||||
if !bc.XAxis.Hidden {
|
||||
axisStyle := bc.XAxis.InheritFrom(bc.styleDefaultsAxes())
|
||||
axisStyle.WriteToRenderer(r)
|
||||
|
||||
width, spacing, _ := bc.calculateScaledTotalWidth(canvasBox)
|
||||
|
||||
r.MoveTo(canvasBox.Left, canvasBox.Bottom)
|
||||
r.LineTo(canvasBox.Right, canvasBox.Bottom)
|
||||
r.Stroke()
|
||||
|
||||
r.MoveTo(canvasBox.Left, canvasBox.Bottom)
|
||||
r.LineTo(canvasBox.Left, canvasBox.Bottom+DefaultVerticalTickHeight)
|
||||
r.Stroke()
|
||||
|
||||
cursor := canvasBox.Left
|
||||
for index, bar := range bc.Bars {
|
||||
barLabelBox := Box{
|
||||
Top: canvasBox.Bottom + DefaultXAxisMargin,
|
||||
Left: cursor,
|
||||
Right: cursor + width + spacing,
|
||||
Bottom: bc.GetHeight(),
|
||||
}
|
||||
|
||||
if len(bar.Label) > 0 {
|
||||
Draw.TextWithin(r, bar.Label, barLabelBox, axisStyle)
|
||||
}
|
||||
|
||||
axisStyle.WriteToRenderer(r)
|
||||
if index < len(bc.Bars)-1 {
|
||||
r.MoveTo(barLabelBox.Right, canvasBox.Bottom)
|
||||
r.LineTo(barLabelBox.Right, canvasBox.Bottom+DefaultVerticalTickHeight)
|
||||
r.Stroke()
|
||||
}
|
||||
cursor += width + spacing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (bc BarChart) drawYAxis(r Renderer, canvasBox Box, yr Range, ticks []Tick) {
|
||||
if !bc.YAxis.Style.Hidden {
|
||||
bc.YAxis.Render(r, canvasBox, yr, bc.styleDefaultsAxes(), ticks)
|
||||
}
|
||||
}
|
||||
|
||||
func (bc BarChart) drawTitle(r Renderer) {
|
||||
if len(bc.Title) > 0 && !bc.TitleStyle.Hidden {
|
||||
r.SetFont(bc.TitleStyle.GetFont(bc.GetFont()))
|
||||
r.SetFontColor(bc.TitleStyle.GetFontColor(bc.GetColorPalette().TextColor()))
|
||||
titleFontSize := bc.TitleStyle.GetFontSize(bc.getTitleFontSize())
|
||||
r.SetFontSize(titleFontSize)
|
||||
|
||||
textBox := r.MeasureText(bc.Title)
|
||||
|
||||
textWidth := textBox.Width()
|
||||
textHeight := textBox.Height()
|
||||
|
||||
titleX := (bc.GetWidth() >> 1) - (textWidth >> 1)
|
||||
titleY := bc.TitleStyle.Padding.GetTop(DefaultTitleTop) + textHeight
|
||||
|
||||
r.Text(bc.Title, titleX, titleY)
|
||||
}
|
||||
}
|
||||
|
||||
func (bc BarChart) getCanvasStyle() Style {
|
||||
return bc.Canvas.InheritFrom(bc.styleDefaultsCanvas())
|
||||
}
|
||||
|
||||
func (bc BarChart) styleDefaultsCanvas() Style {
|
||||
return Style{
|
||||
FillColor: bc.GetColorPalette().CanvasColor(),
|
||||
StrokeColor: bc.GetColorPalette().CanvasStrokeColor(),
|
||||
StrokeWidth: DefaultCanvasStrokeWidth,
|
||||
}
|
||||
}
|
||||
|
||||
func (bc BarChart) hasAxes() bool {
|
||||
return !bc.YAxis.Style.Hidden
|
||||
}
|
||||
|
||||
func (bc BarChart) setRangeDomains(canvasBox Box, yr Range) Range {
|
||||
yr.SetDomain(canvasBox.Height())
|
||||
return yr
|
||||
}
|
||||
|
||||
func (bc BarChart) getDefaultCanvasBox() Box {
|
||||
return bc.box()
|
||||
}
|
||||
|
||||
func (bc BarChart) getValueFormatters() ValueFormatter {
|
||||
if bc.YAxis.ValueFormatter != nil {
|
||||
return bc.YAxis.ValueFormatter
|
||||
}
|
||||
return FloatValueFormatter
|
||||
}
|
||||
|
||||
func (bc BarChart) getAxesTicks(r Renderer, yr Range, yf ValueFormatter) (yticks []Tick) {
|
||||
if !bc.YAxis.Style.Hidden {
|
||||
yticks = bc.YAxis.GetTicks(r, yr, bc.styleDefaultsAxes(), yf)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (bc BarChart) calculateEffectiveBarSpacing(canvasBox Box) int {
|
||||
totalWithBaseSpacing := bc.calculateTotalBarWidth(bc.GetBarWidth(), bc.GetBarSpacing())
|
||||
if totalWithBaseSpacing > canvasBox.Width() {
|
||||
lessBarWidths := canvasBox.Width() - (len(bc.Bars) * bc.GetBarWidth())
|
||||
if lessBarWidths > 0 {
|
||||
return int(math.Ceil(float64(lessBarWidths) / float64(len(bc.Bars))))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
return bc.GetBarSpacing()
|
||||
}
|
||||
|
||||
func (bc BarChart) calculateEffectiveBarWidth(canvasBox Box, spacing int) int {
|
||||
totalWithBaseWidth := bc.calculateTotalBarWidth(bc.GetBarWidth(), spacing)
|
||||
if totalWithBaseWidth > canvasBox.Width() {
|
||||
totalLessBarSpacings := canvasBox.Width() - (len(bc.Bars) * spacing)
|
||||
if totalLessBarSpacings > 0 {
|
||||
return int(math.Ceil(float64(totalLessBarSpacings) / float64(len(bc.Bars))))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
return bc.GetBarWidth()
|
||||
}
|
||||
|
||||
func (bc BarChart) calculateTotalBarWidth(barWidth, spacing int) int {
|
||||
return len(bc.Bars) * (barWidth + spacing)
|
||||
}
|
||||
|
||||
func (bc BarChart) calculateScaledTotalWidth(canvasBox Box) (width, spacing, total int) {
|
||||
spacing = bc.calculateEffectiveBarSpacing(canvasBox)
|
||||
width = bc.calculateEffectiveBarWidth(canvasBox, spacing)
|
||||
total = bc.calculateTotalBarWidth(width, spacing)
|
||||
return
|
||||
}
|
||||
|
||||
func (bc BarChart) getAdjustedCanvasBox(r Renderer, canvasBox Box, yrange Range, yticks []Tick) Box {
|
||||
axesOuterBox := canvasBox.Clone()
|
||||
|
||||
_, _, totalWidth := bc.calculateScaledTotalWidth(canvasBox)
|
||||
|
||||
if !bc.XAxis.Hidden {
|
||||
xaxisHeight := DefaultVerticalTickHeight
|
||||
|
||||
axisStyle := bc.XAxis.InheritFrom(bc.styleDefaultsAxes())
|
||||
axisStyle.WriteToRenderer(r)
|
||||
|
||||
cursor := canvasBox.Left
|
||||
for _, bar := range bc.Bars {
|
||||
if len(bar.Label) > 0 {
|
||||
barLabelBox := Box{
|
||||
Top: canvasBox.Bottom + DefaultXAxisMargin,
|
||||
Left: cursor,
|
||||
Right: cursor + bc.GetBarWidth() + bc.GetBarSpacing(),
|
||||
Bottom: bc.GetHeight(),
|
||||
}
|
||||
lines := Text.WrapFit(r, bar.Label, barLabelBox.Width(), axisStyle)
|
||||
linesBox := Text.MeasureLines(r, lines, axisStyle)
|
||||
|
||||
xaxisHeight = MinInt(linesBox.Height()+(2*DefaultXAxisMargin), xaxisHeight)
|
||||
}
|
||||
}
|
||||
|
||||
xbox := Box{
|
||||
Top: canvasBox.Top,
|
||||
Left: canvasBox.Left,
|
||||
Right: canvasBox.Left + totalWidth,
|
||||
Bottom: bc.GetHeight() - xaxisHeight,
|
||||
}
|
||||
|
||||
axesOuterBox = axesOuterBox.Grow(xbox)
|
||||
}
|
||||
|
||||
if !bc.YAxis.Style.Hidden {
|
||||
axesBounds := bc.YAxis.Measure(r, canvasBox, yrange, bc.styleDefaultsAxes(), yticks)
|
||||
axesOuterBox = axesOuterBox.Grow(axesBounds)
|
||||
}
|
||||
|
||||
return canvasBox.OuterConstrain(bc.box(), axesOuterBox)
|
||||
}
|
||||
|
||||
// box returns the chart bounds as a box.
|
||||
func (bc BarChart) box() Box {
|
||||
dpr := bc.Background.Padding.GetRight(10)
|
||||
dpb := bc.Background.Padding.GetBottom(50)
|
||||
|
||||
return Box{
|
||||
Top: bc.Background.Padding.GetTop(20),
|
||||
Left: bc.Background.Padding.GetLeft(20),
|
||||
Right: bc.GetWidth() - dpr,
|
||||
Bottom: bc.GetHeight() - dpb,
|
||||
}
|
||||
}
|
||||
|
||||
func (bc BarChart) getBackgroundStyle() Style {
|
||||
return bc.Background.InheritFrom(bc.styleDefaultsBackground())
|
||||
}
|
||||
|
||||
func (bc BarChart) styleDefaultsBackground() Style {
|
||||
return Style{
|
||||
FillColor: bc.GetColorPalette().BackgroundColor(),
|
||||
StrokeColor: bc.GetColorPalette().BackgroundStrokeColor(),
|
||||
StrokeWidth: DefaultStrokeWidth,
|
||||
}
|
||||
}
|
||||
|
||||
func (bc BarChart) styleDefaultsBar(index int) Style {
|
||||
return Style{
|
||||
StrokeColor: bc.GetColorPalette().GetSeriesColor(index),
|
||||
StrokeWidth: 3.0,
|
||||
FillColor: bc.GetColorPalette().GetSeriesColor(index),
|
||||
}
|
||||
}
|
||||
|
||||
func (bc BarChart) styleDefaultsTitle() Style {
|
||||
return bc.TitleStyle.InheritFrom(Style{
|
||||
FontColor: bc.GetColorPalette().TextColor(),
|
||||
Font: bc.GetFont(),
|
||||
FontSize: bc.getTitleFontSize(),
|
||||
TextHorizontalAlign: TextHorizontalAlignCenter,
|
||||
TextVerticalAlign: TextVerticalAlignTop,
|
||||
TextWrap: TextWrapWord,
|
||||
})
|
||||
}
|
||||
|
||||
func (bc BarChart) getTitleFontSize() float64 {
|
||||
effectiveDimension := MinInt(bc.GetWidth(), bc.GetHeight())
|
||||
if effectiveDimension >= 2048 {
|
||||
return 48
|
||||
} else if effectiveDimension >= 1024 {
|
||||
return 24
|
||||
} else if effectiveDimension >= 512 {
|
||||
return 18
|
||||
} else if effectiveDimension >= 256 {
|
||||
return 12
|
||||
}
|
||||
return 10
|
||||
}
|
||||
|
||||
func (bc BarChart) styleDefaultsAxes() Style {
|
||||
return Style{
|
||||
StrokeColor: bc.GetColorPalette().AxisStrokeColor(),
|
||||
Font: bc.GetFont(),
|
||||
FontSize: DefaultAxisFontSize,
|
||||
FontColor: bc.GetColorPalette().TextColor(),
|
||||
TextHorizontalAlign: TextHorizontalAlignCenter,
|
||||
TextVerticalAlign: TextVerticalAlignTop,
|
||||
TextWrap: TextWrapWord,
|
||||
}
|
||||
}
|
||||
|
||||
func (bc BarChart) styleDefaultsElements() Style {
|
||||
return Style{
|
||||
Font: bc.GetFont(),
|
||||
}
|
||||
}
|
||||
|
||||
// GetColorPalette returns the color palette for the chart.
|
||||
func (bc BarChart) GetColorPalette() ColorPalette {
|
||||
if bc.ColorPalette != nil {
|
||||
return bc.ColorPalette
|
||||
}
|
||||
return AlternateColorPalette
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue