examples, some fixes
This commit is contained in:
parent
2adc3c7fdd
commit
ac26f764eb
210
README.md
210
README.md
|
@ -17,178 +17,68 @@ To install `chart` run the following:
|
|||
|
||||
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
|
||||
// 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!
|
||||
```
|
||||
![](https://raw.githubusercontent.com/wcharczuk/go-chart/master/images/goog_ltm.png)
|
||||
|
||||
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:
|
||||
|
||||
```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`.
|
||||
Bollinger Bounds:
|
||||
|
||||
![](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
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ func (as AnnotationSeries) Measure(r Renderer, canvasBox Box, xrange, yrange Ran
|
|||
Right: 0,
|
||||
Bottom: 0,
|
||||
}
|
||||
if as.Style.Show {
|
||||
if as.Style.IsZero() || as.Style.Show {
|
||||
style := as.Style.WithDefaultsFrom(Style{
|
||||
Font: defaults.Font,
|
||||
FillColor: DefaultAnnotationFillColor,
|
||||
|
@ -63,7 +63,7 @@ func (as AnnotationSeries) Measure(r Renderer, canvasBox Box, xrange, yrange Ran
|
|||
|
||||
// Render draws the series.
|
||||
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{
|
||||
Font: defaults.Font,
|
||||
FontColor: DefaultTextColor,
|
||||
|
|
34
chart.go
34
chart.go
|
@ -133,6 +133,8 @@ func (c Chart) getRanges() (xrange, yrange, yrangeAlt Range) {
|
|||
var miny, maxy 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 {
|
||||
if s.GetStyle().IsZero() || s.GetStyle().Show {
|
||||
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.Max = c.XAxis.Range.Max
|
||||
} else {
|
||||
|
@ -184,7 +194,15 @@ func (c Chart) getRanges() (xrange, yrange, yrangeAlt Range) {
|
|||
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.Max = c.YAxis.Range.Max
|
||||
} else {
|
||||
|
@ -193,7 +211,15 @@ func (c Chart) getRanges() (xrange, yrange, yrangeAlt Range) {
|
|||
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.Max = c.YAxisSecondary.Range.Max
|
||||
} else {
|
||||
|
@ -348,7 +374,7 @@ func (c Chart) getAnnotationAdjustedCanvasBox(r Renderer, canvasBox Box, xr, yr,
|
|||
}
|
||||
|
||||
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) {
|
||||
|
|
|
@ -11,7 +11,7 @@ const (
|
|||
// DefaultChartHeight is the default chart height.
|
||||
DefaultChartHeight = 400
|
||||
// DefaultChartWidth is the default chart width.
|
||||
DefaultChartWidth = 200
|
||||
DefaultChartWidth = 1024
|
||||
// DefaultStrokeWidth is the default chart line/stroke width.
|
||||
DefaultStrokeWidth = 1.0
|
||||
// DefaultAxisLineWidth is the line width of the axis lines.
|
||||
|
|
|
@ -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.
|
||||
func DrawBox(r Renderer, b Box, s Style) {
|
||||
r.SetFillColor(s.GetFillColor())
|
||||
r.SetStrokeColor(s.GetStrokeColor(DefaultStrokeColor))
|
||||
r.SetStrokeColor(s.GetStrokeColor())
|
||||
r.SetStrokeWidth(s.GetStrokeWidth(DefaultStrokeWidth))
|
||||
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.
|
||||
func CreateLegend(c *Chart, style Style) Renderable {
|
||||
return func(r Renderer, cb Box, defaults Style) {
|
||||
workingStyle := style.WithDefaultsFrom(defaults.WithDefaultsFrom(Style{
|
||||
func CreateLegend(c *Chart, userDefaults ...Style) Renderable {
|
||||
return func(r Renderer, cb Box, chartDefaults Style) {
|
||||
legendDefaults := Style{
|
||||
FillColor: drawing.ColorWhite,
|
||||
FontColor: DefaultTextColor,
|
||||
FontSize: 8.0,
|
||||
StrokeColor: DefaultAxisColor,
|
||||
StrokeWidth: DefaultAxisLineWidth,
|
||||
}))
|
||||
}
|
||||
|
||||
var legendStyle Style
|
||||
if len(userDefaults) > 0 {
|
||||
legendStyle = userDefaults[0].WithDefaultsFrom(chartDefaults.WithDefaultsFrom(legendDefaults))
|
||||
} else {
|
||||
legendStyle = chartDefaults.WithDefaultsFrom(legendDefaults)
|
||||
}
|
||||
|
||||
// DEFAULTS
|
||||
legendPadding := 5
|
||||
|
@ -240,11 +247,11 @@ func CreateLegend(c *Chart, style Style) Renderable {
|
|||
|
||||
var labels []string
|
||||
var lines []Style
|
||||
for _, s := range c.Series {
|
||||
for index, s := range c.Series {
|
||||
if s.GetStyle().IsZero() || s.GetStyle().Show {
|
||||
if _, isAnnotationSeries := s.(AnnotationSeries); !isAnnotationSeries {
|
||||
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,
|
||||
}
|
||||
|
||||
r.SetFontColor(workingStyle.GetFontColor())
|
||||
r.SetFontSize(workingStyle.GetFontSize())
|
||||
r.SetFont(legendStyle.GetFont())
|
||||
r.SetFontColor(legendStyle.GetFontColor())
|
||||
r.SetFontSize(legendStyle.GetFontSize())
|
||||
|
||||
// measure
|
||||
for x := 0; x < len(labels); x++ {
|
||||
|
@ -273,7 +281,7 @@ func CreateLegend(c *Chart, style Style) Renderable {
|
|||
}
|
||||
|
||||
legend = legend.Grow(legendContent)
|
||||
DrawBox(r, legend, workingStyle)
|
||||
DrawBox(r, legend, legendStyle)
|
||||
|
||||
legendContent.Right = legend.Right - legendPadding
|
||||
legendContent.Bottom = legend.Bottom - legendPadding
|
||||
|
@ -283,6 +291,7 @@ func CreateLegend(c *Chart, style Style) Renderable {
|
|||
for x := 0; x < len(labels); x++ {
|
||||
if len(labels[x]) > 0 {
|
||||
tb := r.MeasureText(labels[x])
|
||||
|
||||
ycursor += tb.Height()
|
||||
|
||||
r.Text(labels[x], tx, ycursor)
|
||||
|
|
47
examples/annotations/main.go
Normal file
47
examples/annotations/main.go
Normal 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
42
examples/axes/main.go
Normal 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
42
examples/basic/main.go
Normal 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)
|
||||
}
|
44
examples/custom_formatters/main.go
Normal file
44
examples/custom_formatters/main.go
Normal 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)
|
||||
}
|
40
examples/custom_ranges/main.go
Normal file
40
examples/custom_ranges/main.go
Normal 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)
|
||||
}
|
41
examples/custom_styles/main.go
Normal file
41
examples/custom_styles/main.go
Normal 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)
|
||||
}
|
48
examples/custom_ticks/main.go
Normal file
48
examples/custom_ticks/main.go
Normal 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
39
examples/legend/main.go
Normal 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)
|
||||
}
|
42
examples/simple_moving_average/main.go
Normal file
42
examples/simple_moving_average/main.go
Normal 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
53
examples/twoaxis/main.go
Normal 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)
|
||||
}
|
|
@ -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())
|
||||
}
|
Loading…
Reference in New Issue
Block a user