diff --git a/examples/annotations/main.go b/_examples/annotations/main.go
similarity index 100%
rename from examples/annotations/main.go
rename to _examples/annotations/main.go
diff --git a/examples/axes/main.go b/_examples/axes/main.go
similarity index 100%
rename from examples/axes/main.go
rename to _examples/axes/main.go
diff --git a/examples/bar_chart/main.go b/_examples/bar_chart/main.go
similarity index 100%
rename from examples/bar_chart/main.go
rename to _examples/bar_chart/main.go
diff --git a/examples/basic/main.go b/_examples/basic/main.go
similarity index 100%
rename from examples/basic/main.go
rename to _examples/basic/main.go
diff --git a/examples/custom_formatters/main.go b/_examples/custom_formatters/main.go
similarity index 100%
rename from examples/custom_formatters/main.go
rename to _examples/custom_formatters/main.go
diff --git a/examples/custom_padding/main.go b/_examples/custom_padding/main.go
similarity index 100%
rename from examples/custom_padding/main.go
rename to _examples/custom_padding/main.go
diff --git a/examples/custom_ranges/main.go b/_examples/custom_ranges/main.go
similarity index 100%
rename from examples/custom_ranges/main.go
rename to _examples/custom_ranges/main.go
diff --git a/examples/custom_styles/main.go b/_examples/custom_styles/main.go
similarity index 100%
rename from examples/custom_styles/main.go
rename to _examples/custom_styles/main.go
diff --git a/examples/custom_ticks/main.go b/_examples/custom_ticks/main.go
similarity index 100%
rename from examples/custom_ticks/main.go
rename to _examples/custom_ticks/main.go
diff --git a/examples/legend/main.go b/_examples/legend/main.go
similarity index 100%
rename from examples/legend/main.go
rename to _examples/legend/main.go
diff --git a/examples/linear_regression/main.go b/_examples/linear_regression/main.go
similarity index 100%
rename from examples/linear_regression/main.go
rename to _examples/linear_regression/main.go
diff --git a/examples/market_hours/main.go b/_examples/market_hours/main.go
similarity index 100%
rename from examples/market_hours/main.go
rename to _examples/market_hours/main.go
diff --git a/examples/min_max/main.go b/_examples/min_max/main.go
similarity index 80%
rename from examples/min_max/main.go
rename to _examples/min_max/main.go
index 056b1fd..2376efe 100644
--- a/examples/min_max/main.go
+++ b/_examples/min_max/main.go
@@ -32,8 +32,12 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
}
graph := chart.Chart{
+ Width: 1920,
+ Height: 1080,
YAxis: chart.YAxis{
- Style: chart.StyleShow(),
+ Name: "Random Values",
+ NameStyle: chart.StyleShow(),
+ Style: chart.StyleShow(),
Range: &chart.ContinuousRange{
Min: 25,
Max: 175,
@@ -51,8 +55,10 @@ func drawChart(res http.ResponseWriter, req *http.Request) {
},
}
- res.Header().Set("Content-Type", "image/png")
- graph.Render(chart.PNG, res)
+ graph.Elements = []chart.Renderable{chart.Legend(&graph)}
+
+ res.Header().Set("Content-Type", "image/svg+xml")
+ graph.Render(chart.SVG, res)
}
func main() {
diff --git a/examples/pie_chart/main.go b/_examples/pie_chart/main.go
similarity index 100%
rename from examples/pie_chart/main.go
rename to _examples/pie_chart/main.go
diff --git a/examples/response_times/main.go b/_examples/response_times/main.go
similarity index 100%
rename from examples/response_times/main.go
rename to _examples/response_times/main.go
diff --git a/examples/simple_moving_average/main.go b/_examples/simple_moving_average/main.go
similarity index 100%
rename from examples/simple_moving_average/main.go
rename to _examples/simple_moving_average/main.go
diff --git a/examples/stacked_bar/main.go b/_examples/stacked_bar/main.go
similarity index 100%
rename from examples/stacked_bar/main.go
rename to _examples/stacked_bar/main.go
diff --git a/examples/stock_analysis/main.go b/_examples/stock_analysis/main.go
similarity index 100%
rename from examples/stock_analysis/main.go
rename to _examples/stock_analysis/main.go
diff --git a/examples/timeseries/main.go b/_examples/timeseries/main.go
similarity index 100%
rename from examples/timeseries/main.go
rename to _examples/timeseries/main.go
diff --git a/examples/twoaxis/main.go b/_examples/twoaxis/main.go
similarity index 100%
rename from examples/twoaxis/main.go
rename to _examples/twoaxis/main.go
diff --git a/images/bar_chart.png b/_images/bar_chart.png
similarity index 100%
rename from images/bar_chart.png
rename to _images/bar_chart.png
diff --git a/images/goog_ltm.png b/_images/goog_ltm.png
similarity index 100%
rename from images/goog_ltm.png
rename to _images/goog_ltm.png
diff --git a/images/ma_goog_ltm.png b/_images/ma_goog_ltm.png
similarity index 100%
rename from images/ma_goog_ltm.png
rename to _images/ma_goog_ltm.png
diff --git a/images/pie_chart.png b/_images/pie_chart.png
similarity index 100%
rename from images/pie_chart.png
rename to _images/pie_chart.png
diff --git a/images/spy_ltm_bbs.png b/_images/spy_ltm_bbs.png
similarity index 100%
rename from images/spy_ltm_bbs.png
rename to _images/spy_ltm_bbs.png
diff --git a/images/stacked_bar.png b/_images/stacked_bar.png
similarity index 100%
rename from images/stacked_bar.png
rename to _images/stacked_bar.png
diff --git a/images/tvix_ltm.png b/_images/tvix_ltm.png
similarity index 100%
rename from images/tvix_ltm.png
rename to _images/tvix_ltm.png
diff --git a/images/two_axis.png b/_images/two_axis.png
similarity index 100%
rename from images/two_axis.png
rename to _images/two_axis.png
diff --git a/drawing/matrix.go b/drawing/matrix.go
index 32e0ef4..8bd2049 100644
--- a/drawing/matrix.go
+++ b/drawing/matrix.go
@@ -161,10 +161,10 @@ func (tr *Matrix) Translate(tx, ty float64) {
tr[5] = ty*tr[3] + tx*tr[1] + tr[5]
}
-// Rotate adds a rotation to the matrix. angle is in radian
-func (tr *Matrix) Rotate(angle float64) {
- c := math.Cos(angle)
- s := math.Sin(angle)
+// Rotate adds a rotation to the matrix.
+func (tr *Matrix) Rotate(radians float64) {
+ c := math.Cos(radians)
+ s := math.Sin(radians)
t0 := c*tr[0] + s*tr[2]
t1 := s*tr[3] + c*tr[1]
t2 := c*tr[2] - s*tr[0]
diff --git a/drawing/text.go b/drawing/text.go
index e1b40f2..d7ff5c2 100644
--- a/drawing/text.go
+++ b/drawing/text.go
@@ -22,14 +22,10 @@ func DrawContour(path PathBuilder, ps []truetype.Point, dx, dy float64) {
} else {
path.QuadCurveTo(q0X+dx, q0Y+dy, qX+dx, qY+dy)
}
- } else {
- if on0 {
- // No-op.
- } else {
- midX := (q0X + qX) / 2
- midY := (q0Y + qY) / 2
- path.QuadCurveTo(q0X+dx, q0Y+dy, midX+dx, midY+dy)
- }
+ } else if !on0 {
+ midX := (q0X + qX) / 2
+ midY := (q0Y + qY) / 2
+ path.QuadCurveTo(q0X+dx, q0Y+dy, midX+dx, midY+dy)
}
q0X, q0Y, on0 = qX, qY, on
}
diff --git a/raster_renderer.go b/raster_renderer.go
index 347bb64..3b9fa4d 100644
--- a/raster_renderer.go
+++ b/raster_renderer.go
@@ -28,6 +28,8 @@ type rasterRenderer struct {
i *image.RGBA
gc *drawing.RasterGraphicContext
+ rotateRadians float64
+
s Style
}
@@ -139,10 +141,11 @@ func (rr *rasterRenderer) SetFontColor(c drawing.Color) {
// Text implements the interface method.
func (rr *rasterRenderer) Text(body string, x, y int) {
+ xf, yf := rr.getCoords(x, y)
rr.gc.SetFont(rr.s.Font)
rr.gc.SetFontSize(rr.s.FontSize)
rr.gc.SetFillColor(rr.s.FontColor)
- rr.gc.CreateStringPath(body, float64(x), float64(y))
+ rr.gc.CreateStringPath(body, float64(xf), float64(yf))
rr.gc.Fill()
}
@@ -182,6 +185,29 @@ func (rr *rasterRenderer) MeasureText(body string) Box {
}
}
+// SetTextRotation sets a text rotation.
+func (rr *rasterRenderer) SetTextRotation(radians float64) {
+ rr.rotateRadians = radians
+}
+
+func (rr *rasterRenderer) getCoords(x, y int) (xf, yf int) {
+ if rr.rotateRadians == 0 {
+ xf = x
+ yf = y
+ return
+ }
+
+ rr.gc.Translate(float64(x), float64(y))
+ rr.gc.Rotate(rr.rotateRadians)
+ return
+}
+
+// ClearTextRotation clears text rotation.
+func (rr *rasterRenderer) ClearTextRotation() {
+ rr.gc.SetMatrixTransform(drawing.NewIdentityMatrix())
+ rr.rotateRadians = 0
+}
+
// Save implements the interface method.
func (rr *rasterRenderer) Save(w io.Writer) error {
return png.Encode(w, rr.i)
diff --git a/renderer.go b/renderer.go
index 9d4a9ea..2047a40 100644
--- a/renderer.go
+++ b/renderer.go
@@ -72,6 +72,12 @@ type Renderer interface {
// MeasureText measures text.
MeasureText(body string) Box
+ // SetTextRotatation sets a rotation for drawing elements.
+ SetTextRotation(radians float64)
+
+ // ClearTextRotation clears rotation.
+ ClearTextRotation()
+
// Save writes the image to the given writer.
Save(w io.Writer) error
}
diff --git a/vector_renderer.go b/vector_renderer.go
index 7e50dc8..d65e497 100644
--- a/vector_renderer.go
+++ b/vector_renderer.go
@@ -32,6 +32,7 @@ type vectorRenderer struct {
b *bytes.Buffer
c *canvas
s *Style
+ r float64
p []string
fc *font.Drawer
}
@@ -171,6 +172,16 @@ func (vr *vectorRenderer) MeasureText(body string) (box Box) {
return
}
+// SetTextRotation sets the text rotation.
+func (vr *vectorRenderer) SetTextRotation(radians float64) {
+ vr.c.r = radians
+}
+
+// ClearTextRotation clears the text rotation.
+func (vr *vectorRenderer) ClearTextRotation() {
+ vr.c.r = 0
+}
+
// Save saves the renderer's contents to a writer.
func (vr *vectorRenderer) Save(w io.Writer) error {
vr.c.End()
@@ -187,6 +198,7 @@ func newCanvas(w io.Writer) *canvas {
type canvas struct {
w io.Writer
dpi float64
+ r float64
width int
height int
}
@@ -206,7 +218,12 @@ func (c *canvas) Path(d string, style Style) {
}
func (c *canvas) Text(x, y int, body string, style Style) {
- c.w.Write([]byte(fmt.Sprintf(`%s`, x, y, c.styleAsSVG(style), body)))
+ if c.r == 0 {
+ c.w.Write([]byte(fmt.Sprintf(`%s`, x, y, c.styleAsSVG(style), body)))
+ } else {
+ transform := fmt.Sprintf(` transform="rotate(%0.2f,%d,%d)"`, Math.RadiansToDegrees(c.r), x, y)
+ c.w.Write([]byte(fmt.Sprintf(`%s`, x, y, c.styleAsSVG(style), transform, body)))
+ }
}
func (c *canvas) Circle(x, y, r int, style Style) {
diff --git a/yaxis.go b/yaxis.go
index 7012e21..5e8a2c6 100644
--- a/yaxis.go
+++ b/yaxis.go
@@ -8,7 +8,9 @@ import (
// YAxis is a veritcal rule of the range.
// There can be (2) y-axes; a primary and secondary.
type YAxis struct {
- Name string
+ Name string
+ NameStyle Style
+
Style Style
Zero GridLine
@@ -30,6 +32,11 @@ func (ya YAxis) GetName() string {
return ya.Name
}
+// GetNameStyle returns the name style.
+func (ya YAxis) GetNameStyle() Style {
+ return ya.NameStyle
+}
+
// GetStyle returns the style.
func (ya YAxis) GetStyle() Style {
return ya.Style
@@ -73,6 +80,7 @@ func (ya YAxis) Measure(r Renderer, canvasBox Box, ra Range, defaults Style, tic
}
var minx, maxx, miny, maxy = math.MaxInt32, 0, math.MaxInt32, 0
+ var maxTextHeight int
for _, t := range ticks {
v := t.Value
ly := canvasBox.Bottom - ra.Translate(v)
@@ -83,6 +91,10 @@ func (ya YAxis) Measure(r Renderer, canvasBox Box, ra Range, defaults Style, tic
finalTextX = tx - tb.Width()
}
+ if tb.Height() > maxTextHeight {
+ maxTextHeight = tb.Height()
+ }
+
if ya.AxisType == YAxisPrimary {
minx = canvasBox.Right
maxx = Math.MaxInt(maxx, tx+tb.Width())
@@ -94,6 +106,10 @@ func (ya YAxis) Measure(r Renderer, canvasBox Box, ra Range, defaults Style, tic
maxy = Math.MaxInt(maxy, ly+tb.Height()>>1)
}
+ if ya.NameStyle.Show && len(ya.Name) > 0 {
+ maxx += (DefaultYAxisMargin + maxTextHeight)
+ }
+
return Box{
Top: miny,
Left: minx,
@@ -124,12 +140,17 @@ func (ya YAxis) Render(r Renderer, canvasBox Box, ra Range, defaults Style, tick
r.LineTo(lx, canvasBox.Top)
r.Stroke()
+ var maxTextWidth int
for _, t := range ticks {
v := t.Value
ly := canvasBox.Bottom - ra.Translate(v)
tb := r.MeasureText(t.Label)
+ if tb.Width() > maxTextWidth {
+ maxTextWidth = tb.Width()
+ }
+
finalTextX := tx
finalTextY := ly + tb.Height()>>1
if ya.AxisType == YAxisSecondary {
@@ -147,8 +168,18 @@ func (ya YAxis) Render(r Renderer, canvasBox Box, ra Range, defaults Style, tick
r.Stroke()
}
- if ya.Zero.Style.Show {
- ya.Zero.Render(r, canvasBox, ra, Style{})
+ nameStyle := ya.NameStyle.InheritFrom(defaults)
+ if ya.NameStyle.Show && len(ya.Name) > 0 {
+ nameStyle.GetTextOptions().WriteToRenderer(r)
+
+ r.SetTextRotation(Math.DegreesToRadians(90))
+
+ tb := r.MeasureText(ya.Name)
+ tx := canvasBox.Right + int(sw) + DefaultYAxisMargin + maxTextWidth + DefaultYAxisMargin
+ ty := canvasBox.Bottom - (canvasBox.Height()>>1 + tb.Width()>>1)
+
+ r.Text(ya.Name, tx, ty)
+ r.ClearTextRotation()
}
if ya.GridMajorStyle.Show || ya.GridMinorStyle.Show {