examples, some fixes

This commit is contained in:
Will Charczuk 2016-07-16 20:53:46 -07:00
parent 2adc3c7fdd
commit ac26f764eb
16 changed files with 540 additions and 432 deletions

210
README.md
View File

@ -17,178 +17,68 @@ To install `chart` run the following:
Most of the components are interchangeable so feel free to crib whatever you want. Most of the components are interchangeable so feel free to crib whatever you want.
# Usage # Ouptut Examples
![](https://raw.githubusercontent.com/wcharczuk/go-chart/master/images/goog_ltm.png) Spark Lines:
![](https://raw.githubusercontent.com/wcharczuk/go-chart/master/images/tvix_ltm.png)
The chart code to produce the above is as follows: Single axis:
```go ![](https://raw.githubusercontent.com/wcharczuk/go-chart/master/images/goog_ltm.png)
// note this assumes that xvalues and yvalues
// have been pulled from a pricing service.
graph := chart.Chart{
Width: 1024,
Height: 400,
YAxis: chart.YAxis {
Style: chart.Style{
Show: true,
},
},
XAxis: chart.XAxis {
Style: chart.Style{
Show: true,
},
},
Series: []chart.Series{
chart.TimeSeries{
XValues: xvalues,
YValues: yvalues,
Style: chart.Style {
FillColor: chart.DefaultSeriesStrokeColors[0].WithAlpha(64),
},
},
chart.AnnotationSeries{
Name: "Last Value",
Style: chart.Style{
Show: true,
StrokeColor: chart.DefaultSeriesStrokeColors[0],
},
Annotations: []chart.Annotation{
chart.Annotation{
X: chart.TimeToFloat64(xvalues[len(xvalues)-1]),
Y: yvalues[len(yvalues)-1],
Label: chart.FloatValueFormatter(yvalues[len(yvalues)-1]),
},
},
},
},
}
graph.Render(chart.PNG, buffer) //thats it!
```
The key areas to note are that we have to explicitly turn on two features, the axes and add the last value label annotation series. When calling `.Render(..)` we add a parameter, `chart.PNG` that tells the renderer to use a raster renderer. Another option is to use `chart.SVG` which will use the vector renderer and create an svg representation of the chart. Two axis:
# Alternate Usage ![](https://raw.githubusercontent.com/wcharczuk/go-chart/master/images/two_axis.png)
You can alternately leave a bunch of features turned off and constrain the proportions to something like a spark line: Simple Moving Average:
![](https://raw.githubusercontent.com/wcharczuk/go-chart/master/images/tvix_ltm.png) ![](https://raw.githubusercontent.com/wcharczuk/go-chart/master/images/ma_goog_ltm.png)
The code to produce the above would be: Bollinger Bounds:
```go
// note this assumes that xvalues and yvalues
// have been pulled from a pricing service.
graph := chart.Chart{
Width: 1024,
Height: 100,
Series: []chart.Series{
chart.TimeSeries{
XValues: xvalues,
YValues: yvalues,
},
},
}
graph.Render(chart.PNG, buffer)
```
# 2 Y-Axis Charts
![](https://raw.githubusercontent.com/wcharczuk/go-chart/master/images/two_axis.png)
It is also possible to draw series against 2 separate y-axis with their own ranges (usually good for comparison charts).
In order to map the series to an alternate axis make sure to set the `YAxis` property of the series to `YAxisSecondary`.
```go
graph := chart.Chart{
Title: stock.Name,
TitleStyle: chart.Style{
Show: false,
},
Width: width,
Height: height,
XAxis: chart.XAxis{
Style: chart.Style{
Show: true,
},
},
YAxis: chart.YAxis{
Style: chart.Style{
Show: true,
},
},
Series: []chart.Series{
chart.TimeSeries{
Name: "vea",
XValues: vx,
YValues: vy,
Style: chart.Style{
Show: true,
StrokeColor: chart.GetDefaultSeriesStrokeColor(0),
FillColor: chart.GetDefaultSeriesStrokeColor(0).WithAlpha(64),
},
},
chart.TimeSeries{
Name: "spy",
XValues: cx,
YValues: cy,
YAxis: chart.YAxisSecondary, // key (!)
Style: chart.Style{
Show: true,
StrokeColor: chart.GetDefaultSeriesStrokeColor(1),
FillColor: chart.GetDefaultSeriesStrokeColor(1).WithAlpha(64),
},
},
chart.AnnotationSeries{
Name: fmt.Sprintf("%s - Last Value", "vea"),
Style: chart.Style{
Show: true,
StrokeColor: chart.GetDefaultSeriesStrokeColor(0),
},
Annotations: []chart.Annotation{
chart.Annotation{
X: float64(vx[len(vx)-1].Unix()),
Y: vy[len(vy)-1],
Label: fmt.Sprintf("%s - %s", "vea", chart.FloatValueFormatter(vy[len(vy)-1])),
},
},
},
chart.AnnotationSeries{
Name: fmt.Sprintf("%s - Last Value", "goog"),
Style: chart.Style{
Show: true,
StrokeColor: chart.GetDefaultSeriesStrokeColor(1),
},
YAxis: chart.YAxisSecondary, // key (!)
Annotations: []chart.Annotation{
chart.Annotation{
X: float64(cx[len(cx)-1].Unix()),
Y: cy[len(cy)-1],
Label: fmt.Sprintf("%s - %s", "goog", chart.FloatValueFormatter(cy[len(cy)-1])),
},
},
},
},
}
graph.Render(chart.PNG, buffer)
```
# Moving Averages
You can now also graph a moving average of a series using a special `MovingAverageSeries` that takes an `InnerSeries` as a required argument.
![](https://raw.githubusercontent.com/wcharczuk/go-chart/master/images/ma_goog_ltm.png)
There is a helper method, `GetLastValue` on the `MovingAverageSeries` to aid in creating a last value annotation for the series.
# More Intense Technical Analysis
You can also have series that produce two values, i.e. a series that implements `BoundedValueProvider` and the `GetBoundedValue(int)(x,y1,y2 float64)` method. An example of a `BoundedValueProvider` is the included `BollingerBandsSeries`.
![](https://raw.githubusercontent.com/wcharczuk/go-chart/master/images/spy_ltm_bbs.png) ![](https://raw.githubusercontent.com/wcharczuk/go-chart/master/images/spy_ltm_bbs.png)
Like the `MovingAverageSeries` this series takes an `InnerSeries` argument as required, and defaults to 10 samples and a `K` value of 2.0 (or two standard deviations in either direction). # Code Examples
Actual chart configurations and examples can be found in the `./examples/` directory. They are web servers, so start them with `go run main.go` then access `http://localhost:8080` to see the output.
# Usage
Everything starts with the `chart.Chart` object. The bare minimum to draw a chart would be the following:
```golang
import (
...
"bytes"
...
"github.com/wcharczuk/go-chart" //exposes "chart"
)
graph := chart.Chart{
Series: []chart.Series{
chart.ContinuousSeries{
XValues: []float64{1.0, 2.0, 3.0, 4.0},
YValues: []float64{1.0, 2.0, 3.0, 4.0},
},
},
}
buffer := bytes.NewBuffer([]byte{})
err := graph.Render(chart.PNG, buffer)
```
Explanation of the above: A `chart` can have many `Series`, a `Series` is a collection of things that need to be drawn according to the X range and the Y range(s).
Here, we have a single series with x range values as float64s, rendered to a PNG. Note; we can pass any type of `io.Writer` into `Render(...)`, meaning that we can render the chart to a file or a resonse or anything else that implements `io.Writer`.
# API Overview
Everything on the `chart.Chart` object has defaults that can be overriden. Whenever a developer sets a property on the chart object, it is to be assumed that value will be used instead of the default. One complication here
is any object's root `chart.Style` object (i.e named `Style`) and the `Show` property specifically, if any other property is set and the `Show` property is unset, it is assumed to be it's default value of `False`.
The best way to see the api in action is to look at the examples in the `./examples/` directory.
# Design Philosophy # Design Philosophy

View File

@ -39,7 +39,7 @@ func (as AnnotationSeries) Measure(r Renderer, canvasBox Box, xrange, yrange Ran
Right: 0, Right: 0,
Bottom: 0, Bottom: 0,
} }
if as.Style.Show { if as.Style.IsZero() || as.Style.Show {
style := as.Style.WithDefaultsFrom(Style{ style := as.Style.WithDefaultsFrom(Style{
Font: defaults.Font, Font: defaults.Font,
FillColor: DefaultAnnotationFillColor, FillColor: DefaultAnnotationFillColor,
@ -63,7 +63,7 @@ func (as AnnotationSeries) Measure(r Renderer, canvasBox Box, xrange, yrange Ran
// Render draws the series. // Render draws the series.
func (as AnnotationSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) { func (as AnnotationSeries) Render(r Renderer, canvasBox Box, xrange, yrange Range, defaults Style) {
if as.Style.Show { if as.Style.IsZero() || as.Style.Show {
style := as.Style.WithDefaultsFrom(Style{ style := as.Style.WithDefaultsFrom(Style{
Font: defaults.Font, Font: defaults.Font,
FontColor: DefaultTextColor, FontColor: DefaultTextColor,

View File

@ -133,6 +133,8 @@ func (c Chart) getRanges() (xrange, yrange, yrangeAlt Range) {
var miny, maxy float64 = math.MaxFloat64, 0 var miny, maxy float64 = math.MaxFloat64, 0
var minya, maxya float64 = math.MaxFloat64, 0 var minya, maxya float64 = math.MaxFloat64, 0
// note: a possible future optimization is to not scan the series values if
// all axis are represented by either custom ticks or custom ranges.
for _, s := range c.Series { for _, s := range c.Series {
if s.GetStyle().IsZero() || s.GetStyle().Show { if s.GetStyle().IsZero() || s.GetStyle().Show {
seriesAxis := s.GetYAxis() seriesAxis := s.GetYAxis()
@ -176,7 +178,15 @@ func (c Chart) getRanges() (xrange, yrange, yrangeAlt Range) {
} }
} }
if !c.XAxis.Range.IsZero() { if len(c.XAxis.Ticks) > 0 {
tickMin, tickMax := math.MaxFloat64, 0.0
for _, t := range c.XAxis.Ticks {
tickMin = math.Min(tickMin, t.Value)
tickMax = math.Max(tickMax, t.Value)
}
xrange.Min = tickMin
xrange.Max = tickMax
} else if !c.XAxis.Range.IsZero() {
xrange.Min = c.XAxis.Range.Min xrange.Min = c.XAxis.Range.Min
xrange.Max = c.XAxis.Range.Max xrange.Max = c.XAxis.Range.Max
} else { } else {
@ -184,7 +194,15 @@ func (c Chart) getRanges() (xrange, yrange, yrangeAlt Range) {
xrange.Max = maxx xrange.Max = maxx
} }
if !c.YAxis.Range.IsZero() { if len(c.YAxis.Ticks) > 0 {
tickMin, tickMax := math.MaxFloat64, 0.0
for _, t := range c.YAxis.Ticks {
tickMin = math.Min(tickMin, t.Value)
tickMax = math.Max(tickMax, t.Value)
}
yrange.Min = tickMin
yrange.Max = tickMax
} else if !c.YAxis.Range.IsZero() {
yrange.Min = c.YAxis.Range.Min yrange.Min = c.YAxis.Range.Min
yrange.Max = c.YAxis.Range.Max yrange.Max = c.YAxis.Range.Max
} else { } else {
@ -193,7 +211,15 @@ func (c Chart) getRanges() (xrange, yrange, yrangeAlt Range) {
yrange.Min, yrange.Max = yrange.GetRoundedRangeBounds() yrange.Min, yrange.Max = yrange.GetRoundedRangeBounds()
} }
if !c.YAxisSecondary.Range.IsZero() { if len(c.YAxisSecondary.Ticks) > 0 {
tickMin, tickMax := math.MaxFloat64, 0.0
for _, t := range c.YAxis.Ticks {
tickMin = math.Min(tickMin, t.Value)
tickMax = math.Max(tickMax, t.Value)
}
yrangeAlt.Min = tickMin
yrangeAlt.Max = tickMax
} else if !c.YAxisSecondary.Range.IsZero() {
yrangeAlt.Min = c.YAxisSecondary.Range.Min yrangeAlt.Min = c.YAxisSecondary.Range.Min
yrangeAlt.Max = c.YAxisSecondary.Range.Max yrangeAlt.Max = c.YAxisSecondary.Range.Max
} else { } else {
@ -348,7 +374,7 @@ func (c Chart) getAnnotationAdjustedCanvasBox(r Renderer, canvasBox Box, xr, yr,
} }
func (c Chart) drawBackground(r Renderer) { func (c Chart) drawBackground(r Renderer) {
DrawBox(r, c.Box(), c.Canvas.WithDefaultsFrom(c.styleDefaultsBackground())) DrawBox(r, c.Box(), c.Background.WithDefaultsFrom(c.styleDefaultsBackground()))
} }
func (c Chart) drawCanvas(r Renderer, canvasBox Box) { func (c Chart) drawCanvas(r Renderer, canvasBox Box) {

View File

@ -11,7 +11,7 @@ const (
// DefaultChartHeight is the default chart height. // DefaultChartHeight is the default chart height.
DefaultChartHeight = 400 DefaultChartHeight = 400
// DefaultChartWidth is the default chart width. // DefaultChartWidth is the default chart width.
DefaultChartWidth = 200 DefaultChartWidth = 1024
// DefaultStrokeWidth is the default chart line/stroke width. // DefaultStrokeWidth is the default chart line/stroke width.
DefaultStrokeWidth = 1.0 DefaultStrokeWidth = 1.0
// DefaultAxisLineWidth is the line width of the axis lines. // DefaultAxisLineWidth is the line width of the axis lines.

View File

@ -185,7 +185,7 @@ func DrawAnnotation(r Renderer, canvasBox Box, s Style, lx, ly int, label string
// DrawBox draws a box with a given style. // DrawBox draws a box with a given style.
func DrawBox(r Renderer, b Box, s Style) { func DrawBox(r Renderer, b Box, s Style) {
r.SetFillColor(s.GetFillColor()) r.SetFillColor(s.GetFillColor())
r.SetStrokeColor(s.GetStrokeColor(DefaultStrokeColor)) r.SetStrokeColor(s.GetStrokeColor())
r.SetStrokeWidth(s.GetStrokeWidth(DefaultStrokeWidth)) r.SetStrokeWidth(s.GetStrokeWidth(DefaultStrokeWidth))
r.SetStrokeDashArray(s.GetStrokeDashArray()) r.SetStrokeDashArray(s.GetStrokeDashArray())
@ -223,15 +223,22 @@ func DrawTextCentered(r Renderer, text string, x, y int, s Style) {
} }
// CreateLegend returns a legend renderable function. // CreateLegend returns a legend renderable function.
func CreateLegend(c *Chart, style Style) Renderable { func CreateLegend(c *Chart, userDefaults ...Style) Renderable {
return func(r Renderer, cb Box, defaults Style) { return func(r Renderer, cb Box, chartDefaults Style) {
workingStyle := style.WithDefaultsFrom(defaults.WithDefaultsFrom(Style{ legendDefaults := Style{
FillColor: drawing.ColorWhite, FillColor: drawing.ColorWhite,
FontColor: DefaultTextColor, FontColor: DefaultTextColor,
FontSize: 8.0, FontSize: 8.0,
StrokeColor: DefaultAxisColor, StrokeColor: DefaultAxisColor,
StrokeWidth: DefaultAxisLineWidth, StrokeWidth: DefaultAxisLineWidth,
})) }
var legendStyle Style
if len(userDefaults) > 0 {
legendStyle = userDefaults[0].WithDefaultsFrom(chartDefaults.WithDefaultsFrom(legendDefaults))
} else {
legendStyle = chartDefaults.WithDefaultsFrom(legendDefaults)
}
// DEFAULTS // DEFAULTS
legendPadding := 5 legendPadding := 5
@ -240,11 +247,11 @@ func CreateLegend(c *Chart, style Style) Renderable {
var labels []string var labels []string
var lines []Style var lines []Style
for _, s := range c.Series { for index, s := range c.Series {
if s.GetStyle().IsZero() || s.GetStyle().Show { if s.GetStyle().IsZero() || s.GetStyle().Show {
if _, isAnnotationSeries := s.(AnnotationSeries); !isAnnotationSeries { if _, isAnnotationSeries := s.(AnnotationSeries); !isAnnotationSeries {
labels = append(labels, s.GetName()) labels = append(labels, s.GetName())
lines = append(lines, s.GetStyle()) lines = append(lines, s.GetStyle().WithDefaultsFrom(c.styleDefaultsSeries(index)))
} }
} }
} }
@ -259,8 +266,9 @@ func CreateLegend(c *Chart, style Style) Renderable {
Left: legend.Left + legendPadding, Left: legend.Left + legendPadding,
} }
r.SetFontColor(workingStyle.GetFontColor()) r.SetFont(legendStyle.GetFont())
r.SetFontSize(workingStyle.GetFontSize()) r.SetFontColor(legendStyle.GetFontColor())
r.SetFontSize(legendStyle.GetFontSize())
// measure // measure
for x := 0; x < len(labels); x++ { for x := 0; x < len(labels); x++ {
@ -273,7 +281,7 @@ func CreateLegend(c *Chart, style Style) Renderable {
} }
legend = legend.Grow(legendContent) legend = legend.Grow(legendContent)
DrawBox(r, legend, workingStyle) DrawBox(r, legend, legendStyle)
legendContent.Right = legend.Right - legendPadding legendContent.Right = legend.Right - legendPadding
legendContent.Bottom = legend.Bottom - legendPadding legendContent.Bottom = legend.Bottom - legendPadding
@ -283,6 +291,7 @@ func CreateLegend(c *Chart, style Style) Renderable {
for x := 0; x < len(labels); x++ { for x := 0; x < len(labels); x++ {
if len(labels[x]) > 0 { if len(labels[x]) > 0 {
tb := r.MeasureText(labels[x]) tb := r.MeasureText(labels[x])
ycursor += tb.Height() ycursor += tb.Height()
r.Text(labels[x], tx, ycursor) r.Text(labels[x], tx, ycursor)

View File

@ -0,0 +1,47 @@
package main
import (
"net/http"
"github.com/wcharczuk/go-chart"
)
func drawChart(res http.ResponseWriter, req *http.Request) {
/*
In this example we add an `Annotation` series, which is a special type of series that
draws annotation labels at given X and Y values (as translated by their respective ranges).
It is important to not that the chart automatically sizes the canvas box to fit the annotations,
As well as automatically assign a series color for the `Stroke` or border component of the series.
The annotation series is most often used by the original author to show the last value of another series, but
they can be used in other capacities as well.
*/
graph := chart.Chart{
Series: []chart.Series{
chart.ContinuousSeries{
XValues: []float64{1.0, 2.0, 3.0, 4.0, 5.0},
YValues: []float64{1.0, 2.0, 3.0, 4.0, 5.0},
},
chart.AnnotationSeries{
Annotations: []chart.Annotation{
{X: 1.0, Y: 1.0, Label: "One"},
{X: 2.0, Y: 2.0, Label: "Two"},
{X: 3.0, Y: 3.0, Label: "Three"},
{X: 4.0, Y: 4.0, Label: "Four"},
{X: 5.0, Y: 5.0, Label: "Five"},
},
},
},
}
res.Header().Set("Content-Type", "image/png")
graph.Render(chart.PNG, res)
}
func main() {
http.HandleFunc("/", drawChart)
http.ListenAndServe(":8080", nil)
}

42
examples/axes/main.go Normal file
View File

@ -0,0 +1,42 @@
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{
XAxis: chart.XAxis{
Style: chart.Style{
Show: true, //enables / displays the x-axis
},
},
YAxis: chart.YAxis{
Style: chart.Style{
Show: true, //enables / displays the y-axis
},
},
Series: []chart.Series{
chart.ContinuousSeries{
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)
}

42
examples/basic/main.go Normal file
View File

@ -0,0 +1,42 @@
package main
import (
"net/http"
"github.com/wcharczuk/go-chart"
)
func drawChart(res http.ResponseWriter, req *http.Request) {
graph := chart.Chart{
Series: []chart.Series{
chart.ContinuousSeries{
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 drawChartWide(res http.ResponseWriter, req *http.Request) {
graph := chart.Chart{
Width: 1920, //this overrides the default.
Series: []chart.Series{
chart.ContinuousSeries{
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.HandleFunc("/wide", drawChartWide)
http.ListenAndServe(":8080", nil)
}

View File

@ -0,0 +1,44 @@
package main
import (
"fmt"
"net/http"
"github.com/wcharczuk/go-chart"
)
func drawChart(res http.ResponseWriter, req *http.Request) {
/*
In this example we use a custom `ValueFormatter` for the y axis, letting us specify how to format text of the y-axis ticks.
You can also do this for the x-axis, or the secondary y-axis.
This example also shows what the chart looks like with the x-axis left off or not shown.
*/
graph := chart.Chart{
YAxis: chart.YAxis{
Style: chart.Style{
Show: true,
},
ValueFormatter: func(v interface{}) string {
if vf, isFloat := v.(float64); isFloat {
return fmt.Sprintf("%0.6f", vf)
}
return ""
},
},
Series: []chart.Series{
chart.ContinuousSeries{
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)
}

View File

@ -0,0 +1,40 @@
package main
import (
"net/http"
"github.com/wcharczuk/go-chart"
)
func drawChart(res http.ResponseWriter, req *http.Request) {
/*
In this example we set a custom range for the y-axis, overriding the automatic range generation.
Note: the chart will still generate the ticks automatically based on the custom range, so the intervals may be a bit weird.
*/
graph := chart.Chart{
YAxis: chart.YAxis{
Style: chart.Style{
Show: true,
},
Range: chart.Range{
Min: 0.0,
Max: 10.0,
},
},
Series: []chart.Series{
chart.ContinuousSeries{
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)
}

View File

@ -0,0 +1,41 @@
package main
import (
"net/http"
"github.com/wcharczuk/go-chart"
"github.com/wcharczuk/go-chart/drawing"
)
func drawChart(res http.ResponseWriter, req *http.Request) {
/*
In this example we set some custom colors for the series and the chart background and canvas.
*/
graph := chart.Chart{
Background: chart.Style{
FillColor: drawing.ColorBlue,
},
Canvas: chart.Style{
FillColor: drawing.ColorFromHex("efefef"),
},
Series: []chart.Series{
chart.ContinuousSeries{
Style: chart.Style{
Show: true, //note; if we set ANY other properties, we must set this to true.
StrokeColor: drawing.ColorRed, // will supercede defaults
FillColor: drawing.ColorRed.WithAlpha(64), // will supercede defaults
},
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)
}

View File

@ -0,0 +1,48 @@
package main
import (
"net/http"
"github.com/wcharczuk/go-chart"
)
func drawChart(res http.ResponseWriter, req *http.Request) {
/*
In this example we set a custom set of ticks to use for the y-axis. It can be (almost) whatever you want, including some custom labels for ticks.
Custom ticks will supercede a custom range, which will supercede automatic generation based on series values.
*/
graph := chart.Chart{
YAxis: chart.YAxis{
Style: chart.Style{
Show: true,
},
Range: chart.Range{
Min: 0.0,
Max: 4.0,
},
Ticks: []chart.Tick{
{0.0, "0.00"},
{2.0, "2.00"},
{4.0, "4.00"},
{6.0, "6.00"},
{8.0, "Eight"},
{10.0, "Ten"},
},
},
Series: []chart.Series{
chart.ContinuousSeries{
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)
}

39
examples/legend/main.go Normal file
View File

@ -0,0 +1,39 @@
package main
import (
"net/http"
"github.com/wcharczuk/go-chart"
)
func drawChart(res http.ResponseWriter, req *http.Request) {
/*
In this example we add a `Renderable` or a custom component to the `Elements` array.
In this specific case it is a pre-built renderable (`CreateLegend`) that draws a legend for the chart's series.
If you like, you can use `CreateLegend` as a template for writing your own renderable, or even your own legend.
*/
graph := chart.Chart{
Series: []chart.Series{
chart.ContinuousSeries{
Name: "A test series",
XValues: []float64{1.0, 2.0, 3.0, 4.0, 5.0},
YValues: []float64{1.0, 2.0, 3.0, 4.0, 5.0},
},
},
}
//note we have to do this as a separate step because we need a reference to graph
graph.Elements = []chart.Renderable{
chart.CreateLegend(&graph),
}
res.Header().Set("Content-Type", "image/png")
graph.Render(chart.PNG, res)
}
func main() {
http.HandleFunc("/", drawChart)
http.ListenAndServe(":8080", nil)
}

View File

@ -0,0 +1,42 @@
package main
import (
"net/http"
"github.com/wcharczuk/go-chart"
)
func drawChart(res http.ResponseWriter, req *http.Request) {
/*
In this example we add a new type of series, a `SimpleMovingAverageSeries` that takes another series as a required argument.
InnerSeries only needs to implement `ValueProvider`, so really you could chain `SimpleMovingAverageSeries` together if you wanted.
*/
mainSeries := chart.ContinuousSeries{
Name: "A test series",
XValues: chart.Seq(1.0, 100.0), //generates a []float64 from 1.0 to 100.0 in 1.0 step increments, or 100 elements.
YValues: chart.SeqRand(100, 100), //generates a []float64 randomly from 0 to 100 with 100 elements.
}
// note we create a SimpleMovingAverage series by assignin the inner series.
// we need to use a reference because `.Render()` needs to modify state within the series.
smaSeries := &chart.SimpleMovingAverageSeries{
InnerSeries: mainSeries,
} // we can optionally set the `WindowSize` property which alters how the moving average is calculated.
graph := chart.Chart{
Series: []chart.Series{
mainSeries,
smaSeries,
},
}
res.Header().Set("Content-Type", "image/png")
graph.Render(chart.PNG, res)
}
func main() {
http.HandleFunc("/", drawChart)
http.ListenAndServe(":8080", nil)
}

53
examples/twoaxis/main.go Normal file
View File

@ -0,0 +1,53 @@
package main
import (
"net/http"
"github.com/wcharczuk/go-chart"
)
func drawChart(res http.ResponseWriter, req *http.Request) {
/*
In this example we add a second series, and assign it to the secondary y axis, giving that series it's own range.
We also enable all of the axes by setting the `Show` propery of their respective styles to `true`.
*/
graph := chart.Chart{
XAxis: chart.XAxis{
Style: chart.Style{
Show: true, //enables / displays the x-axis
},
},
YAxis: chart.YAxis{
Style: chart.Style{
Show: true, //enables / displays the y-axis
},
},
YAxisSecondary: chart.YAxis{
Style: chart.Style{
Show: true, //enables / displays the secondary y-axis
},
},
Series: []chart.Series{
chart.ContinuousSeries{
XValues: []float64{1.0, 2.0, 3.0, 4.0, 5.0},
YValues: []float64{1.0, 2.0, 3.0, 4.0, 5.0},
},
chart.ContinuousSeries{
YAxis: chart.YAxisSecondary,
XValues: []float64{1.0, 2.0, 3.0, 4.0, 5.0},
YValues: []float64{50.0, 40.0, 30.0, 20.0, 10.0},
},
},
}
res.Header().Set("Content-Type", "image/png")
graph.Render(chart.PNG, res)
}
func main() {
http.HandleFunc("/", drawChart)
http.ListenAndServe(":8080", nil)
}

View File

@ -1,255 +0,0 @@
package main
import (
"bytes"
"fmt"
"log"
"math/rand"
"net/http"
"time"
"github.com/wcharczuk/go-chart"
"github.com/wcharczuk/go-chart/drawing"
"github.com/wcharczuk/go-web"
)
func chartHandler(rc *web.RequestContext) web.ControllerResult {
rnd := rand.New(rand.NewSource(0))
format, err := rc.RouteParameter("format")
if err != nil {
format = "png"
}
if format == "png" {
rc.Response.Header().Set("Content-Type", "image/png")
} else {
rc.Response.Header().Set("Content-Type", "image/svg+xml")
}
var s1x []time.Time
for x := 0; x < 100; x++ {
s1x = append([]time.Time{time.Now().AddDate(0, 0, -1*x)}, s1x...)
}
var s1y []float64
for x := 0; x < 100; x++ {
s1y = append(s1y, rnd.Float64()*1024)
}
s1 := chart.TimeSeries{
Name: "a",
XValues: s1x,
YValues: s1y,
Style: chart.Style{
Show: true,
//FillColor: chart.GetDefaultSeriesStrokeColor(0).WithAlpha(64),
},
}
s1lv := chart.AnnotationSeries{
Name: fmt.Sprintf("Last Value"),
Style: chart.Style{
Show: true,
StrokeColor: chart.GetDefaultSeriesStrokeColor(0),
},
Annotations: []chart.Annotation{
{
X: float64(s1x[len(s1x)-1].Unix()),
Y: s1y[len(s1y)-1],
Label: fmt.Sprintf("%s - %s", "test", chart.FloatValueFormatter(s1y[len(s1y)-1])),
},
},
}
s1bb := &chart.BollingerBandsSeries{
Name: "BBS",
Style: chart.Style{
Show: true,
StrokeColor: chart.DefaultAxisColor,
FillColor: chart.DefaultAxisColor.WithAlpha(64),
},
K: 2.0,
WindowSize: 10,
InnerSeries: s1,
}
s1sma := &chart.SimpleMovingAverageSeries{
Style: chart.Style{
Show: true,
StrokeColor: drawing.ColorRed,
StrokeDashArray: []float64{5.0, 5.0},
},
InnerSeries: s1,
}
s1ema := &chart.ExponentialMovingAverageSeries{
Style: chart.Style{
Show: true,
StrokeColor: drawing.ColorBlue,
StrokeDashArray: []float64{5.0, 5.0},
},
InnerSeries: s1,
}
c := chart.Chart{
Title: "A Test Chart",
TitleStyle: chart.Style{
Show: false,
},
Width: 1024,
Height: 400,
XAxis: chart.XAxis{
Style: chart.Style{
Show: true,
},
},
YAxis: chart.YAxis{
Style: chart.Style{
Show: true,
},
Zero: chart.GridLine{
Style: chart.Style{
Show: true,
StrokeWidth: 1.0,
},
},
GridMajorStyle: chart.Style{
Show: false,
},
GridMinorStyle: chart.Style{
Show: true,
},
},
Series: []chart.Series{
s1bb,
s1,
s1lv,
s1sma,
s1ema,
},
}
if format == "png" {
err = c.Render(chart.PNG, rc.Response)
} else {
err = c.Render(chart.SVG, rc.Response)
}
if err != nil {
return rc.API().InternalError(err)
}
rc.Response.WriteHeader(http.StatusOK)
return nil
}
func boxHandler(rc *web.RequestContext) web.ControllerResult {
r, err := chart.PNG(1024, 1024)
if err != nil {
rc.API().InternalError(err)
}
f, err := chart.GetDefaultFont()
if err != nil {
return rc.API().InternalError(err)
}
//1:1 128wx128h @ 64,64
a := chart.Box{Top: 64, Left: 64, Right: 192, Bottom: 192}
// 3:2 256x170 @ 16, 16
//b := chart.Box{Top: 16, Left: 16, Right: 256, Bottom: 170}
// 2:3 170x256 @ 16, 16
c := chart.Box{Top: 16, Left: 16, Right: 170, Bottom: 256}
//fitb := a.Fit(b)
fitc := a.Fit(c)
//growb := a.Grow(b)
//growc := a.Grow(c)
//grow := a.Grow(b).Grow(c)
conc := a.Constrain(c)
boxStyle := chart.Style{
StrokeColor: drawing.ColorBlack,
StrokeWidth: 1.0,
Font: f,
FontSize: 18.0,
}
computedBoxStyle := chart.Style{
StrokeColor: drawing.ColorRed,
StrokeWidth: 1.0,
Font: f,
FontSize: 18.0,
}
chart.DrawBox(r, a, boxStyle)
//chart.DrawBox(r, b, boxStyle)
chart.DrawBox(r, c, boxStyle)
//chart.DrawBox(r, fitb, computedBoxStyle)
chart.DrawBox(r, fitc, computedBoxStyle)
/*chart.DrawBox(r, growb, computedBoxStyle)
chart.DrawBox(r, growc, computedBoxStyle)
chart.DrawBox(r, grow, computedBoxStyle)*/
chart.DrawBox(r, conc, computedBoxStyle)
ax, ay := a.Center()
chart.DrawTextCentered(r, "a", ax, ay, boxStyle.WithDefaultsFrom(chart.Style{
FillColor: boxStyle.StrokeColor,
}))
/*bx, by := b.Center()
chart.DrawTextCentered(r, "b", bx, by, boxStyle.WithDefaultsFrom(chart.Style{
FillColor: boxStyle.StrokeColor,
}))*/
cx, cy := c.Center()
chart.DrawTextCentered(r, "c", cx, cy, boxStyle.WithDefaultsFrom(chart.Style{
FillColor: boxStyle.StrokeColor,
}))
/*fbx, fby := fitb.Center()
chart.DrawTextCentered(r, "a fit b", fbx, fby, computedBoxStyle.WithDefaultsFrom(chart.Style{
FillColor: computedBoxStyle.StrokeColor,
}))*/
fcx, fcy := fitc.Center()
chart.DrawTextCentered(r, "a fit c", fcx, fcy, computedBoxStyle.WithDefaultsFrom(chart.Style{
FillColor: computedBoxStyle.StrokeColor,
}))
/*gbx, gby := growb.Center()
chart.DrawTextCentered(r, "a grow b", gbx, gby, computedBoxStyle.WithDefaultsFrom(chart.Style{
FillColor: computedBoxStyle.StrokeColor,
}))
gcx, gcy := growc.Center()
chart.DrawTextCentered(r, "a grow c", gcx, gcy, computedBoxStyle.WithDefaultsFrom(chart.Style{
FillColor: computedBoxStyle.StrokeColor,
}))*/
ccx, ccy := conc.Center()
chart.DrawTextCentered(r, "a const c", ccx, ccy, computedBoxStyle.WithDefaultsFrom(chart.Style{
FillColor: computedBoxStyle.StrokeColor,
}))
rc.Response.Header().Set("Content-Type", "image/png")
buffer := bytes.NewBuffer([]byte{})
err = r.Save(buffer)
if err != nil {
return rc.API().InternalError(err)
}
return rc.Raw(buffer.Bytes())
}
func main() {
app := web.New()
app.SetName("Chart Test Server")
app.SetLogger(web.NewStandardOutputLogger())
app.GET("/", chartHandler)
app.GET("/format/:format", chartHandler)
app.GET("/favico.ico", func(rc *web.RequestContext) web.ControllerResult {
return rc.Raw([]byte{})
})
app.GET("/box", boxHandler)
log.Fatal(app.Start())
}