From 98d3996b47e5b065036198d8d77b55fe786919ca Mon Sep 17 00:00:00 2001 From: Will Charczuk Date: Tue, 10 Jan 2017 13:50:17 -0800 Subject: [PATCH] descending --- _examples/descending/main.go | 55 ++++++++++++++++++++++++++++++++++++ continuous_range.go | 19 ++++++++++--- market_hours_range.go | 8 +++++- range.go | 2 ++ tick.go | 52 +++++++++++++++++++++++++++------- tick_test.go | 9 ++++-- 6 files changed, 127 insertions(+), 18 deletions(-) create mode 100644 _examples/descending/main.go diff --git a/_examples/descending/main.go b/_examples/descending/main.go new file mode 100644 index 0000000..add5d87 --- /dev/null +++ b/_examples/descending/main.go @@ -0,0 +1,55 @@ +package main + +import ( + "net/http" + + "github.com/wcharczuk/go-chart" +) + +func drawChart(res http.ResponseWriter, req *http.Request) { + + /* + The below will draw the same chart as the `basic` example, except with both the x and y axes turned on. + In this case, both the x and y axis ticks are generated automatically, the x and y ranges are established automatically, the canvas "box" is adjusted to fit the space the axes occupy so as not to clip. + */ + + graph := chart.Chart{ + Height: 500, + Width: 500, + XAxis: chart.XAxis{ + Style: chart.Style{ + Show: true, + }, + /*Range: &chart.ContinuousRange{ + Descending: true, + },*/ + }, + YAxis: chart.YAxis{ + Style: chart.Style{ + Show: true, + }, + Range: &chart.ContinuousRange{ + Descending: true, + }, + }, + Series: []chart.Series{ + chart.ContinuousSeries{ + Style: chart.Style{ + Show: true, + StrokeColor: chart.GetDefaultColor(0).WithAlpha(64), + FillColor: chart.GetDefaultColor(0).WithAlpha(64), + }, + XValues: []float64{1.0, 2.0, 3.0, 4.0, 5.0}, + YValues: []float64{1.0, 2.0, 3.0, 4.0, 5.0}, + }, + }, + } + + res.Header().Set("Content-Type", "image/png") + graph.Render(chart.PNG, res) +} + +func main() { + http.HandleFunc("/", drawChart) + http.ListenAndServe(":8080", nil) +} diff --git a/continuous_range.go b/continuous_range.go index 8a8e853..99fa939 100644 --- a/continuous_range.go +++ b/continuous_range.go @@ -7,9 +7,15 @@ import ( // ContinuousRange represents a boundary for a set of numbers. type ContinuousRange struct { - Min float64 - Max float64 - Domain int + Min float64 + Max float64 + Domain int + Descending bool +} + +// IsDescending returns if the range is descending. +func (r ContinuousRange) IsDescending() bool { + return r.Descending } // IsZero returns if the ContinuousRange has been set or not. @@ -56,12 +62,17 @@ func (r *ContinuousRange) SetDomain(domain int) { // String returns a simple string for the ContinuousRange. func (r ContinuousRange) String() string { - return fmt.Sprintf("ContinuousRange [%.2f,%.2f] => %f", r.Min, r.Max, r.Domain) + return fmt.Sprintf("ContinuousRange [%.2f,%.2f] => %d", r.Min, r.Max, r.Domain) } // Translate maps a given value into the ContinuousRange space. func (r ContinuousRange) Translate(value float64) int { normalized := value - r.Min ratio := normalized / r.GetDelta() + + if r.IsDescending() { + return r.Domain - int(math.Ceil(ratio*float64(r.Domain))) + } + return int(math.Ceil(ratio * float64(r.Domain))) } diff --git a/market_hours_range.go b/market_hours_range.go index 265cffa..27ff6db 100644 --- a/market_hours_range.go +++ b/market_hours_range.go @@ -18,7 +18,13 @@ type MarketHoursRange struct { ValueFormatter ValueFormatter - Domain int + Descending bool + Domain int +} + +// IsDescending returns if the range is descending. +func (mhr MarketHoursRange) IsDescending() bool { + return mhr.Descending } // GetTimezone returns the timezone for the market hours range. diff --git a/range.go b/range.go index a816fda..a7b22ab 100644 --- a/range.go +++ b/range.go @@ -36,6 +36,8 @@ type Range interface { GetDomain() int SetDomain(domain int) + IsDescending() bool + // Translate the range to the domain. Translate(value float64) int } diff --git a/tick.go b/tick.go index 138d15c..1b678ee 100644 --- a/tick.go +++ b/tick.go @@ -1,6 +1,10 @@ package chart -import "math" +import ( + "fmt" + "math" + "strings" +) // TicksProvider is a type that provides ticks. type TicksProvider interface { @@ -31,6 +35,15 @@ func (t Ticks) Less(i, j int) bool { return t[i].Value < t[j].Value } +// String returns a string representation of the set of ticks. +func (t Ticks) String() string { + var values []string + for i, tick := range t { + values = append(values, fmt.Sprintf("[%d: %s]", i, tick.Label)) + } + return strings.Join(values, ", ") +} + // GenerateContinuousTicks generates a set of ticks. func GenerateContinuousTicks(r Renderer, ra Range, isVertical bool, style Style, vf ValueFormatter) []Tick { if vf == nil { @@ -40,10 +53,17 @@ func GenerateContinuousTicks(r Renderer, ra Range, isVertical bool, style Style, var ticks []Tick min, max := ra.GetMin(), ra.GetMax() - ticks = append(ticks, Tick{ - Value: min, - Label: vf(min), - }) + if ra.IsDescending() { + ticks = append(ticks, Tick{ + Value: max, + Label: vf(max), + }) + } else { + ticks = append(ticks, Tick{ + Value: min, + Label: vf(min), + }) + } minLabel := vf(min) style.GetTextOptions().WriteToRenderer(r) @@ -67,17 +87,29 @@ func GenerateContinuousTicks(r Renderer, ra Range, isVertical bool, style Style, intermediateTickCount = Math.MinInt(intermediateTickCount, 1<<10) for x := 1; x < intermediateTickCount; x++ { - tickValue := min + Math.RoundUp(tickStep*float64(x), roundTo) + var tickValue float64 + if ra.IsDescending() { + tickValue = max - Math.RoundUp(tickStep*float64(x), roundTo) + } else { + tickValue = min + Math.RoundUp(tickStep*float64(x), roundTo) + } ticks = append(ticks, Tick{ Value: tickValue, Label: vf(tickValue), }) } - ticks = append(ticks, Tick{ - Value: max, - Label: vf(max), - }) + if ra.IsDescending() { + ticks = append(ticks, Tick{ + Value: min, + Label: vf(min), + }) + } else { + ticks = append(ticks, Tick{ + Value: max, + Label: vf(max), + }) + } return ticks } diff --git a/tick_test.go b/tick_test.go index 0ea3a88..7cea3c9 100644 --- a/tick_test.go +++ b/tick_test.go @@ -42,9 +42,10 @@ func TestGenerateContinuousTicksDescending(t *testing.T) { r.SetFont(f) ra := &ContinuousRange{ - Min: 10.0, - Max: 0.0, - Domain: 256, + Min: 0.0, + Max: 10.0, + Domain: 256, + Descending: true, } vf := FloatValueFormatter @@ -53,5 +54,7 @@ func TestGenerateContinuousTicksDescending(t *testing.T) { assert.NotEmpty(ticks) assert.Len(ticks, 11) assert.Equal(10.0, ticks[0].Value) + assert.Equal(9.0, ticks[1].Value) + assert.Equal(1.0, ticks[len(ticks)-2].Value) assert.Equal(0.0, ticks[len(ticks)-1].Value) }