you can now specify tick values and labels manually.

This commit is contained in:
Will Charczuk 2016-07-08 09:17:28 -07:00
parent 65c3f62ac5
commit afc220e25c
3 changed files with 55 additions and 49 deletions

View File

@ -172,7 +172,7 @@ func (c Chart) initRanges(canvasBox Box) (xrange Range, yrange Range) {
if xrange.Formatter == nil { if xrange.Formatter == nil {
xrange.Formatter = s.GetXFormatter() xrange.Formatter = s.GetXFormatter()
} }
if yrange.Format == nil { if yrange.Formatter == nil {
yrange.Formatter = s.GetYFormatter() yrange.Formatter = s.GetYFormatter()
} }
} }
@ -187,6 +187,9 @@ func (c Chart) initRanges(canvasBox Box) (xrange Range, yrange Range) {
if c.XRange.Formatter != nil { if c.XRange.Formatter != nil {
xrange.Formatter = c.XRange.Formatter xrange.Formatter = c.XRange.Formatter
} }
if c.XRange.Ticks != nil {
xrange.Ticks = c.XRange.Ticks
}
xrange.Domain = canvasBox.Width xrange.Domain = canvasBox.Width
if c.YRange.IsZero() { if c.YRange.IsZero() {
@ -199,6 +202,9 @@ func (c Chart) initRanges(canvasBox Box) (xrange Range, yrange Range) {
if c.YRange.Formatter != nil { if c.YRange.Formatter != nil {
yrange.Formatter = c.YRange.Formatter yrange.Formatter = c.YRange.Formatter
} }
if c.YRange.Ticks != nil {
yrange.Ticks = c.YRange.Ticks
}
yrange.Domain = canvasBox.Height yrange.Domain = canvasBox.Height
return return
@ -244,68 +250,69 @@ func (c Chart) drawAxes(r Renderer, canvasBox Box, xrange, yrange Range) {
} }
} }
func (c Chart) generateRangeTicks(r Range, tickCount int, offset float64) []Tick {
var ticks []Tick
rangeTicks := Slices(tickCount, r.Max-r.Min)
for _, rv := range rangeTicks {
ticks = append(ticks, Tick{
RangeValue: rv + offset,
Label: r.Format(rv + offset),
})
}
return ticks
}
func (c Chart) drawYAxisLabels(r Renderer, canvasBox Box, yrange Range) { func (c Chart) drawYAxisLabels(r Renderer, canvasBox Box, yrange Range) {
tickFontSize := c.Axes.GetFontSize(DefaultAxisFontSize) tickFontSize := c.Axes.GetFontSize(DefaultAxisFontSize)
asw := c.getAxisWidth()
tx := canvasBox.Right + DefaultFinalLabelDeltaWidth + asw
r.SetFontColor(c.Axes.GetFontColor(DefaultAxisColor)) r.SetFontColor(c.Axes.GetFontColor(DefaultAxisColor))
r.SetFontSize(tickFontSize) r.SetFontSize(tickFontSize)
minimumTickHeight := tickFontSize + DefaultMinimumTickVerticalSpacing ticks := yrange.Ticks
tickCount := int(math.Floor(float64(yrange.Domain) / float64(minimumTickHeight))) if ticks == nil {
minimumTickHeight := tickFontSize + DefaultMinimumTickVerticalSpacing
tickCount := int(math.Floor(float64(yrange.Domain) / float64(minimumTickHeight)))
if tickCount > DefaultMaxTickCount { if tickCount > DefaultMaxTickCount {
tickCount = DefaultMaxTickCount tickCount = DefaultMaxTickCount
}
ticks = c.generateRangeTicks(yrange, tickCount, yrange.Min)
} }
rangeTicks := Slices(tickCount, yrange.Max-yrange.Min) for _, t := range ticks {
domainTicks := Slices(tickCount, float64(yrange.Domain)) v := t.RangeValue
y := yrange.Translate(v)
asw := c.getAxisWidth() ty := int(y)
tx := canvasBox.Right + DefaultFinalLabelDeltaWidth + asw r.Text(t.Label, tx, ty)
count := len(rangeTicks)
if len(domainTicks) < count {
count = len(domainTicks) //guard against mismatched array sizes.
}
for index := 0; index < count; index++ {
v := rangeTicks[index] + yrange.Min
y := domainTicks[index]
ty := canvasBox.Bottom - int(y)
r.Text(yrange.Format(v), tx, ty)
} }
} }
func (c Chart) drawXAxisLabels(r Renderer, canvasBox Box, xrange Range) { func (c Chart) drawXAxisLabels(r Renderer, canvasBox Box, xrange Range) {
tickFontSize := c.Axes.GetFontSize(DefaultAxisFontSize) tickFontSize := c.Axes.GetFontSize(DefaultAxisFontSize)
ty := canvasBox.Bottom + DefaultXAxisMargin + int(tickFontSize)
r.SetFontColor(c.Axes.GetFontColor(DefaultAxisColor)) r.SetFontColor(c.Axes.GetFontColor(DefaultAxisColor))
r.SetFontSize(tickFontSize) r.SetFontSize(tickFontSize)
maxLabelWidth := 60 ticks := xrange.Ticks
if ticks == nil {
maxLabelWidth := 60
minimumTickWidth := maxLabelWidth + DefaultMinimumTickHorizontalSpacing
tickCount := int(math.Floor(float64(xrange.Domain) / float64(minimumTickWidth)))
minimumTickWidth := maxLabelWidth + DefaultMinimumTickHorizontalSpacing if tickCount > DefaultMaxTickCount {
tickCount := int(math.Floor(float64(xrange.Domain) / float64(minimumTickWidth))) tickCount = DefaultMaxTickCount
}
if tickCount > DefaultMaxTickCount { ticks = c.generateRangeTicks(xrange, tickCount, xrange.Min)
tickCount = DefaultMaxTickCount
} }
rangeTicks := Slices(tickCount, xrange.Max-xrange.Min) for _, t := range ticks {
domainTicks := Slices(tickCount, float64(xrange.Domain)) v := t.RangeValue
x := xrange.Translate(v)
ty := canvasBox.Bottom + DefaultXAxisMargin + int(tickFontSize)
count := len(rangeTicks)
if len(domainTicks) < count {
count = len(domainTicks) //guard against mismatched array sizes.
}
for index := 0; index < count; index++ {
v := rangeTicks[index] + xrange.Min
x := domainTicks[index]
tx := canvasBox.Left + int(x) tx := canvasBox.Left + int(x)
r.Text(xrange.Format(v), tx, ty) r.Text(t.Label, tx, ty)
} }
} }

View File

@ -7,11 +7,18 @@ import (
"github.com/blendlabs/go-util" "github.com/blendlabs/go-util"
) )
// Tick represents a label on an axis.
type Tick struct {
RangeValue float64
Label string
}
// Range represents a continuous range, // Range represents a continuous range,
type Range struct { type Range struct {
Min float64 Min float64
Max float64 Max float64
Domain int Domain int
Ticks []Tick
Formatter Formatter Formatter Formatter
} }

View File

@ -1,10 +1,8 @@
package main package main
import ( import (
"fmt"
"log" "log"
"github.com/blendlabs/go-util"
"github.com/wcharczuk/go-chart" "github.com/wcharczuk/go-chart"
"github.com/wcharczuk/go-web" "github.com/wcharczuk/go-web"
) )
@ -29,12 +27,6 @@ func main() {
YRange: chart.Range{ YRange: chart.Range{
Min: 0.0, Min: 0.0,
Max: 7.0, Max: 7.0,
Formatter: func(v interface{}) string {
if typed, isTyped := v.(float64); isTyped {
return fmt.Sprintf("%.4f", typed)
}
return util.StringEmpty
},
}, },
FinalValueLabel: chart.Style{ FinalValueLabel: chart.Style{
Show: true, Show: true,