bar charts!
This commit is contained in:
parent
818dd0113b
commit
7bb3fbf810
6 changed files with 539 additions and 19 deletions
464
bar_chart.go
Normal file
464
bar_chart.go
Normal file
|
@ -0,0 +1,464 @@
|
||||||
|
package chart
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"github.com/golang/freetype/truetype"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BarChart is a chart that draws bars on a range.
|
||||||
|
type BarChart struct {
|
||||||
|
Title string
|
||||||
|
TitleStyle Style
|
||||||
|
|
||||||
|
Width int
|
||||||
|
Height int
|
||||||
|
DPI float64
|
||||||
|
|
||||||
|
BarWidth int
|
||||||
|
|
||||||
|
Background Style
|
||||||
|
Canvas Style
|
||||||
|
|
||||||
|
XAxis Style
|
||||||
|
YAxis YAxis
|
||||||
|
|
||||||
|
BarSpacing int
|
||||||
|
|
||||||
|
Font *truetype.Font
|
||||||
|
defaultFont *truetype.Font
|
||||||
|
|
||||||
|
Bars []Value
|
||||||
|
Elements []Renderable
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDPI returns the dpi for the chart.
|
||||||
|
func (bc BarChart) GetDPI(defaults ...float64) float64 {
|
||||||
|
if bc.DPI == 0 {
|
||||||
|
if len(defaults) > 0 {
|
||||||
|
return defaults[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 DefaultChartWidth
|
||||||
|
}
|
||||||
|
return bc.Height
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBarSpacing returns the spacing between bars.
|
||||||
|
func (bc BarChart) GetBarSpacing() int {
|
||||||
|
if bc.BarSpacing == 0 {
|
||||||
|
return 100
|
||||||
|
}
|
||||||
|
return bc.BarSpacing
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBarWidth returns the default bar width.
|
||||||
|
func (bc BarChart) GetBarWidth() int {
|
||||||
|
if bc.BarWidth == 0 {
|
||||||
|
return 40
|
||||||
|
}
|
||||||
|
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(DefaultDPI))
|
||||||
|
|
||||||
|
bc.drawBackground(r)
|
||||||
|
|
||||||
|
var canvasBox Box
|
||||||
|
var yt []Tick
|
||||||
|
var yr Range
|
||||||
|
var yf ValueFormatter
|
||||||
|
|
||||||
|
canvasBox = bc.getDefaultCanvasBox()
|
||||||
|
yr = bc.getRanges()
|
||||||
|
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.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) 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)
|
||||||
|
|
||||||
|
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.Show {
|
||||||
|
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.Show {
|
||||||
|
axisStyle := bc.YAxis.Style.InheritFrom(bc.styleDefaultsAxes())
|
||||||
|
axisStyle.WriteToRenderer(r)
|
||||||
|
|
||||||
|
r.MoveTo(canvasBox.Right, canvasBox.Top)
|
||||||
|
r.LineTo(canvasBox.Right, canvasBox.Bottom)
|
||||||
|
r.Stroke()
|
||||||
|
|
||||||
|
r.MoveTo(canvasBox.Right, canvasBox.Bottom)
|
||||||
|
r.LineTo(canvasBox.Right+DefaultHorizontalTickWidth, canvasBox.Bottom)
|
||||||
|
r.Stroke()
|
||||||
|
|
||||||
|
var ty int
|
||||||
|
var tb Box
|
||||||
|
for _, t := range ticks {
|
||||||
|
ty = canvasBox.Bottom - yr.Translate(t.Value)
|
||||||
|
|
||||||
|
axisStyle.GetStrokeOptions().WriteToRenderer(r)
|
||||||
|
r.MoveTo(canvasBox.Right, ty)
|
||||||
|
r.LineTo(canvasBox.Right+DefaultHorizontalTickWidth, ty)
|
||||||
|
r.Stroke()
|
||||||
|
|
||||||
|
axisStyle.GetTextOptions().WriteToRenderer(r)
|
||||||
|
tb = r.MeasureText(t.Label)
|
||||||
|
Draw.Text(r, t.Label, canvasBox.Right+DefaultYAxisMargin+5, ty+(tb.Height()>>1), axisStyle)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bc BarChart) drawTitle(r Renderer) {
|
||||||
|
if len(bc.Title) > 0 && bc.TitleStyle.Show {
|
||||||
|
Draw.TextWithin(r, bc.Title, bc.box(), bc.styleDefaultsTitle())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bc BarChart) hasAxes() bool {
|
||||||
|
return bc.YAxis.Style.Show
|
||||||
|
}
|
||||||
|
|
||||||
|
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.Show {
|
||||||
|
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.Floor(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.Floor(float64(totalLessBarSpacings) / float64(len(bc.Bars))))
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return bc.GetBarWidth()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bc BarChart) calculateTotalBarWidth(barWidth, spacing int) int {
|
||||||
|
return len(bc.Bars) * (bc.GetBarWidth() + 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.Show {
|
||||||
|
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 = Math.MaxInt(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.Show {
|
||||||
|
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: 20,
|
||||||
|
Left: 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: DefaultBackgroundColor,
|
||||||
|
StrokeColor: DefaultBackgroundStrokeColor,
|
||||||
|
StrokeWidth: DefaultStrokeWidth,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bc BarChart) styleDefaultsBar(index int) Style {
|
||||||
|
return Style{
|
||||||
|
StrokeColor: GetAlternateColor(index),
|
||||||
|
StrokeWidth: 3.0,
|
||||||
|
FillColor: GetAlternateColor(index),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bc BarChart) styleDefaultsTitle() Style {
|
||||||
|
return bc.TitleStyle.InheritFrom(Style{
|
||||||
|
FontColor: DefaultTextColor,
|
||||||
|
Font: bc.GetFont(),
|
||||||
|
FontSize: bc.getTitleFontSize(),
|
||||||
|
TextHorizontalAlign: TextHorizontalAlignCenter,
|
||||||
|
TextVerticalAlign: TextVerticalAlignTop,
|
||||||
|
TextWrap: TextWrapWord,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bc BarChart) getTitleFontSize() float64 {
|
||||||
|
effectiveDimension := Math.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: DefaultAxisColor,
|
||||||
|
Font: bc.GetFont(),
|
||||||
|
FontSize: DefaultAxisFontSize,
|
||||||
|
FontColor: DefaultAxisColor,
|
||||||
|
TextHorizontalAlign: TextHorizontalAlignCenter,
|
||||||
|
TextVerticalAlign: TextVerticalAlignTop,
|
||||||
|
TextWrap: TextWrapWord,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bc BarChart) styleDefaultsElements() Style {
|
||||||
|
return Style{
|
||||||
|
Font: bc.GetFont(),
|
||||||
|
}
|
||||||
|
}
|
32
chart.go
32
chart.go
|
@ -133,9 +133,9 @@ func (c Chart) Render(rp RendererProvider, w io.Writer) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Chart) getRanges() (xrange, yrange, yrangeAlt Range) {
|
func (c Chart) getRanges() (xrange, yrange, yrangeAlt Range) {
|
||||||
var minx, maxx float64 = math.MaxFloat64, 0
|
var minx, maxx float64 = math.MaxFloat64, -math.MaxFloat64
|
||||||
var miny, maxy float64 = math.MaxFloat64, 0
|
var miny, maxy float64 = math.MaxFloat64, -math.MaxFloat64
|
||||||
var minya, maxya float64 = math.MaxFloat64, 0
|
var minya, maxya float64 = math.MaxFloat64, -math.MaxFloat64
|
||||||
|
|
||||||
seriesMappedToSecondaryAxis := false
|
seriesMappedToSecondaryAxis := false
|
||||||
|
|
||||||
|
@ -205,7 +205,7 @@ func (c Chart) getRanges() (xrange, yrange, yrangeAlt Range) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(c.XAxis.Ticks) > 0 {
|
if len(c.XAxis.Ticks) > 0 {
|
||||||
tickMin, tickMax := math.MaxFloat64, 0.0
|
tickMin, tickMax := math.MaxFloat64, -math.MaxFloat64
|
||||||
for _, t := range c.XAxis.Ticks {
|
for _, t := range c.XAxis.Ticks {
|
||||||
tickMin = math.Min(tickMin, t.Value)
|
tickMin = math.Min(tickMin, t.Value)
|
||||||
tickMax = math.Max(tickMax, t.Value)
|
tickMax = math.Max(tickMax, t.Value)
|
||||||
|
@ -218,7 +218,7 @@ func (c Chart) getRanges() (xrange, yrange, yrangeAlt Range) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(c.YAxis.Ticks) > 0 {
|
if len(c.YAxis.Ticks) > 0 {
|
||||||
tickMin, tickMax := math.MaxFloat64, 0.0
|
tickMin, tickMax := math.MaxFloat64, -math.MaxFloat64
|
||||||
for _, t := range c.YAxis.Ticks {
|
for _, t := range c.YAxis.Ticks {
|
||||||
tickMin = math.Min(tickMin, t.Value)
|
tickMin = math.Min(tickMin, t.Value)
|
||||||
tickMax = math.Max(tickMax, t.Value)
|
tickMax = math.Max(tickMax, t.Value)
|
||||||
|
@ -237,7 +237,7 @@ func (c Chart) getRanges() (xrange, yrange, yrangeAlt Range) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(c.YAxisSecondary.Ticks) > 0 {
|
if len(c.YAxisSecondary.Ticks) > 0 {
|
||||||
tickMin, tickMax := math.MaxFloat64, 0.0
|
tickMin, tickMax := math.MaxFloat64, -math.MaxFloat64
|
||||||
for _, t := range c.YAxis.Ticks {
|
for _, t := range c.YAxis.Ticks {
|
||||||
tickMin = math.Min(tickMin, t.Value)
|
tickMin = math.Min(tickMin, t.Value)
|
||||||
tickMax = math.Max(tickMax, t.Value)
|
tickMax = math.Max(tickMax, t.Value)
|
||||||
|
@ -309,13 +309,13 @@ func (c Chart) hasAxes() bool {
|
||||||
|
|
||||||
func (c Chart) getAxesTicks(r Renderer, xr, yr, yar Range, xf, yf, yfa ValueFormatter) (xticks, yticks, yticksAlt []Tick) {
|
func (c Chart) getAxesTicks(r Renderer, xr, yr, yar Range, xf, yf, yfa ValueFormatter) (xticks, yticks, yticksAlt []Tick) {
|
||||||
if c.XAxis.Style.Show {
|
if c.XAxis.Style.Show {
|
||||||
xticks = c.XAxis.GetTicks(r, xr, c.styleDefaultsAxis(), xf)
|
xticks = c.XAxis.GetTicks(r, xr, c.styleDefaultsAxes(), xf)
|
||||||
}
|
}
|
||||||
if c.YAxis.Style.Show {
|
if c.YAxis.Style.Show {
|
||||||
yticks = c.YAxis.GetTicks(r, yr, c.styleDefaultsAxis(), yf)
|
yticks = c.YAxis.GetTicks(r, yr, c.styleDefaultsAxes(), yf)
|
||||||
}
|
}
|
||||||
if c.YAxisSecondary.Style.Show {
|
if c.YAxisSecondary.Style.Show {
|
||||||
yticksAlt = c.YAxisSecondary.GetTicks(r, yar, c.styleDefaultsAxis(), yfa)
|
yticksAlt = c.YAxisSecondary.GetTicks(r, yar, c.styleDefaultsAxes(), yfa)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -323,15 +323,15 @@ 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 {
|
||||||
axesOuterBox := canvasBox.Clone()
|
axesOuterBox := canvasBox.Clone()
|
||||||
if c.XAxis.Style.Show {
|
if c.XAxis.Style.Show {
|
||||||
axesBounds := c.XAxis.Measure(r, canvasBox, xr, c.styleDefaultsAxis(), xticks)
|
axesBounds := c.XAxis.Measure(r, canvasBox, xr, c.styleDefaultsAxes(), xticks)
|
||||||
axesOuterBox = axesOuterBox.Grow(axesBounds)
|
axesOuterBox = axesOuterBox.Grow(axesBounds)
|
||||||
}
|
}
|
||||||
if c.YAxis.Style.Show {
|
if c.YAxis.Style.Show {
|
||||||
axesBounds := c.YAxis.Measure(r, canvasBox, yr, c.styleDefaultsAxis(), yticks)
|
axesBounds := c.YAxis.Measure(r, canvasBox, yr, c.styleDefaultsAxes(), yticks)
|
||||||
axesOuterBox = axesOuterBox.Grow(axesBounds)
|
axesOuterBox = axesOuterBox.Grow(axesBounds)
|
||||||
}
|
}
|
||||||
if c.YAxisSecondary.Style.Show {
|
if c.YAxisSecondary.Style.Show {
|
||||||
axesBounds := c.YAxisSecondary.Measure(r, canvasBox, yra, c.styleDefaultsAxis(), yticksAlt)
|
axesBounds := c.YAxisSecondary.Measure(r, canvasBox, yra, c.styleDefaultsAxes(), yticksAlt)
|
||||||
axesOuterBox = axesOuterBox.Grow(axesBounds)
|
axesOuterBox = axesOuterBox.Grow(axesBounds)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -407,13 +407,13 @@ func (c Chart) drawCanvas(r Renderer, canvasBox Box) {
|
||||||
|
|
||||||
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) {
|
||||||
if c.XAxis.Style.Show {
|
if c.XAxis.Style.Show {
|
||||||
c.XAxis.Render(r, canvasBox, xrange, c.styleDefaultsAxis(), xticks)
|
c.XAxis.Render(r, canvasBox, xrange, c.styleDefaultsAxes(), xticks)
|
||||||
}
|
}
|
||||||
if c.YAxis.Style.Show {
|
if c.YAxis.Style.Show {
|
||||||
c.YAxis.Render(r, canvasBox, yrange, c.styleDefaultsAxis(), yticks)
|
c.YAxis.Render(r, canvasBox, yrange, c.styleDefaultsAxes(), yticks)
|
||||||
}
|
}
|
||||||
if c.YAxisSecondary.Style.Show {
|
if c.YAxisSecondary.Style.Show {
|
||||||
c.YAxisSecondary.Render(r, canvasBox, yrangeAlt, c.styleDefaultsAxis(), yticksAlt)
|
c.YAxisSecondary.Render(r, canvasBox, yrangeAlt, c.styleDefaultsAxes(), yticksAlt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -472,7 +472,7 @@ func (c Chart) styleDefaultsSeries(seriesIndex int) Style {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Chart) styleDefaultsAxis() Style {
|
func (c Chart) styleDefaultsAxes() Style {
|
||||||
return Style{
|
return Style{
|
||||||
Font: c.GetFont(),
|
Font: c.GetFont(),
|
||||||
FontColor: DefaultAxisColor,
|
FontColor: DefaultAxisColor,
|
||||||
|
|
54
examples/bar_chart/main.go
Normal file
54
examples/bar_chart/main.go
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/wcharczuk/go-chart"
|
||||||
|
)
|
||||||
|
|
||||||
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
|
sbc := chart.BarChart{
|
||||||
|
Height: 512,
|
||||||
|
BarWidth: 60,
|
||||||
|
XAxis: chart.Style{
|
||||||
|
Show: true,
|
||||||
|
},
|
||||||
|
YAxis: chart.YAxis{
|
||||||
|
Style: chart.Style{
|
||||||
|
Show: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Bars: []chart.Value{
|
||||||
|
{Value: 5, Label: "Blue"},
|
||||||
|
{Value: 5, Label: "Green"},
|
||||||
|
{Value: 4, Label: "Gray"},
|
||||||
|
{Value: 3, Label: "Orange"},
|
||||||
|
{Value: 3, Label: "Test"},
|
||||||
|
{Value: 2, Label: "??"},
|
||||||
|
{Value: 1, Label: "!!"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Header().Set("Content-Type", "image/png")
|
||||||
|
err := sbc.Render(chart.PNG, res)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error rendering chart: %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func port() string {
|
||||||
|
if len(os.Getenv("PORT")) > 0 {
|
||||||
|
return os.Getenv("PORT")
|
||||||
|
}
|
||||||
|
return "8080"
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
listenPort := fmt.Sprintf(":%s", port())
|
||||||
|
fmt.Printf("Listening on %s\n", listenPort)
|
||||||
|
http.HandleFunc("/", drawChart)
|
||||||
|
log.Fatal(http.ListenAndServe(listenPort, nil))
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func drawChart(res http.ResponseWriter, req *http.Request) {
|
func drawChart(res http.ResponseWriter, req *http.Request) {
|
||||||
start := chart.Date.Date(2016, 6, 20, chart.Date.Eastern())
|
start := chart.Date.Date(2016, 7, 01, chart.Date.Eastern())
|
||||||
end := chart.Date.Date(2016, 07, 21, chart.Date.Eastern())
|
end := chart.Date.Date(2016, 07, 21, chart.Date.Eastern())
|
||||||
xv := chart.Sequence.MarketHours(start, end, chart.NYSEOpen, chart.NYSEClose, chart.Date.IsNYSEHoliday)
|
xv := chart.Sequence.MarketHours(start, end, chart.NYSEOpen, chart.NYSEClose, chart.Date.IsNYSEHoliday)
|
||||||
yv := chart.Sequence.RandomWithAverage(len(xv), 200, 10)
|
yv := chart.Sequence.RandomWithAverage(len(xv), 200, 10)
|
||||||
|
|
|
@ -151,9 +151,7 @@ func (s sequence) MarketDayMondayCloses(from, to time.Time, marketOpen, marketCl
|
||||||
if isValidTradingDay {
|
if isValidTradingDay {
|
||||||
times = append(times, cursor)
|
times = append(times, cursor)
|
||||||
}
|
}
|
||||||
println("advance to next monday", cursor.Format(DefaultDateFormat))
|
|
||||||
cursor = Date.NextDayOfWeek(cursor, time.Monday)
|
cursor = Date.NextDayOfWeek(cursor, time.Monday)
|
||||||
println(cursor.Format(DefaultDateFormat))
|
|
||||||
}
|
}
|
||||||
return times
|
return times
|
||||||
}
|
}
|
||||||
|
|
4
tick.go
4
tick.go
|
@ -33,6 +33,10 @@ func (t Ticks) Less(i, j int) bool {
|
||||||
|
|
||||||
// GenerateContinuousTicks generates a set of ticks.
|
// GenerateContinuousTicks generates a set of ticks.
|
||||||
func GenerateContinuousTicks(r Renderer, ra Range, isVertical bool, style Style, vf ValueFormatter) []Tick {
|
func GenerateContinuousTicks(r Renderer, ra Range, isVertical bool, style Style, vf ValueFormatter) []Tick {
|
||||||
|
if vf == nil {
|
||||||
|
panic("vf is nil; did you remember to set a default value formatter?")
|
||||||
|
}
|
||||||
|
|
||||||
var ticks []Tick
|
var ticks []Tick
|
||||||
min, max := ra.GetMin(), ra.GetMax()
|
min, max := ra.GetMin(), ra.GetMax()
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue