diff --git a/_examples/request_timings/main.go b/_examples/request_timings/main.go index 1542e68..b8d16ec 100644 --- a/_examples/request_timings/main.go +++ b/_examples/request_timings/main.go @@ -2,23 +2,33 @@ package main import ( "net/http" + "strconv" "strings" "time" - util "github.com/blendlabs/go-util" "github.com/wcharczuk/go-chart" ) +func parseInt(str string) int { + v, _ := strconv.Atoi(str) + return v +} + +func parseFloat64(str string) float64 { + v, _ := strconv.ParseFloat(str, 64) + return v +} + func readData() ([]time.Time, []float64) { var xvalues []time.Time var yvalues []float64 - util.ReadFileByLines("requests.csv", func(line string) { + chart.File.ReadByLines("requests.csv", func(line string) { parts := strings.Split(line, ",") - year := util.String.ParseInt(parts[0]) - month := util.String.ParseInt(parts[1]) - day := util.String.ParseInt(parts[2]) - hour := util.String.ParseInt(parts[3]) - elapsedMillis := util.String.ParseFloat64(parts[4]) + year := parseInt(parts[0]) + month := parseInt(parts[1]) + day := parseInt(parts[2]) + hour := parseInt(parts[3]) + elapsedMillis := parseFloat64(parts[4]) xvalues = append(xvalues, time.Date(year, time.Month(month), day, hour, 0, 0, 0, time.UTC)) yvalues = append(yvalues, elapsedMillis) }) @@ -27,12 +37,12 @@ func readData() ([]time.Time, []float64) { func releases() []chart.GridLine { return []chart.GridLine{ - {Value: chart.TimeToFloat64(time.Date(2016, 8, 1, 9, 30, 0, 0, time.UTC))}, - {Value: chart.TimeToFloat64(time.Date(2016, 8, 2, 9, 30, 0, 0, time.UTC))}, - {Value: chart.TimeToFloat64(time.Date(2016, 8, 3, 9, 30, 0, 0, time.UTC))}, - {Value: chart.TimeToFloat64(time.Date(2016, 8, 4, 9, 30, 0, 0, time.UTC))}, - {Value: chart.TimeToFloat64(time.Date(2016, 8, 5, 9, 30, 0, 0, time.UTC))}, - {Value: chart.TimeToFloat64(time.Date(2016, 8, 6, 9, 30, 0, 0, time.UTC))}, + {Value: chart.Time.ToFloat64(time.Date(2016, 8, 1, 9, 30, 0, 0, time.UTC))}, + {Value: chart.Time.ToFloat64(time.Date(2016, 8, 2, 9, 30, 0, 0, time.UTC))}, + {Value: chart.Time.ToFloat64(time.Date(2016, 8, 3, 9, 30, 0, 0, time.UTC))}, + {Value: chart.Time.ToFloat64(time.Date(2016, 8, 4, 9, 30, 0, 0, time.UTC))}, + {Value: chart.Time.ToFloat64(time.Date(2016, 8, 5, 9, 30, 0, 0, time.UTC))}, + {Value: chart.Time.ToFloat64(time.Date(2016, 8, 6, 9, 30, 0, 0, time.UTC))}, } } @@ -78,13 +88,19 @@ func drawChart(res http.ResponseWriter, req *http.Request) { Style: chart.StyleShow(), }, XAxis: chart.XAxis{ - Style: chart.StyleShow(), + Style: chart.Style{ + Show: true, + }, ValueFormatter: chart.TimeHourValueFormatter, GridMajorStyle: chart.Style{ Show: true, StrokeColor: chart.ColorAlternateGray, StrokeWidth: 1.0, }, + TickPosition: chart.TickPositionBetweenTicks, + TickStyle: chart.Style{ + TextRotationDegrees: 45, + }, GridLines: releases(), }, Series: []chart.Series{ diff --git a/_examples/stock_analysis/main.go b/_examples/stock_analysis/main.go index 3f17025..ddfa4b3 100644 --- a/_examples/stock_analysis/main.go +++ b/_examples/stock_analysis/main.go @@ -60,8 +60,8 @@ func drawChart(res http.ResponseWriter, req *http.Request) { }, } - res.Header().Set("Content-Type", "image/svg+xml") - graph.Render(chart.SVG, res) + res.Header().Set("Content-Type", "image/png") + graph.Render(chart.PNG, res) } func xvalues() []time.Time { diff --git a/_examples/text_rotation/main.go b/_examples/text_rotation/main.go index ea3a9b6..76bb2b0 100644 --- a/_examples/text_rotation/main.go +++ b/_examples/text_rotation/main.go @@ -4,60 +4,47 @@ 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 a rotation on the style for the custom ticks from the `custom_ticks` example. - */ + f, _ := chart.GetDefaultFont() + r, _ := chart.PNG(1024, 1024) - graph := chart.Chart{ - YAxis: chart.YAxis{ - Style: chart.Style{ - Show: true, - }, - Range: &chart.ContinuousRange{ - Min: 0.0, - Max: 4.0, - }, - TickStyle: chart.Style{ - TextRotationDegrees: 45.0, - }, - Ticks: []chart.Tick{ - {Value: 0.0, Label: "0.00"}, - {Value: 2.0, Label: "2.00"}, - {Value: 4.0, Label: "4.00"}, - {Value: 6.0, Label: "6.00"}, - {Value: 8.0, Label: "Eight"}, - {Value: 10.0, Label: "Ten"}, - }, - }, - XAxis: chart.XAxis{ - Style: chart.Style{ - Show: true, - }, - TickStyle: chart.Style{ - TextRotationDegrees: 45.0, - }, - Ticks: []chart.Tick{ - {Value: 0.0, Label: "0.00"}, - {Value: 2.0, Label: "2.00"}, - {Value: 4.0, Label: "4.00"}, - {Value: 6.0, Label: "6.00"}, - {Value: 8.0, Label: "Eight"}, - {Value: 10.0, Label: "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}, - }, - }, - } + chart.Draw.Text(r, "Test", 64, 64, chart.Style{ + FontColor: drawing.ColorBlack, + FontSize: 18, + Font: f, + }) + + chart.Draw.Text(r, "Test", 64, 64, chart.Style{ + FontColor: drawing.ColorBlack, + FontSize: 18, + Font: f, + TextRotationDegrees: 45.0, + }) + + tb := chart.Draw.MeasureText(r, "Test", chart.Style{ + FontColor: drawing.ColorBlack, + FontSize: 18, + Font: f, + }).Shift(64, 64) + + tbc := tb.Corners().Rotate(45) + + chart.Draw.BoxCorners(r, tbc, chart.Style{ + StrokeColor: drawing.ColorRed, + StrokeWidth: 2, + }) + + tbcb := tbc.Box() + chart.Draw.Box(r, tbcb, chart.Style{ + StrokeColor: drawing.ColorBlue, + StrokeWidth: 2, + }) res.Header().Set("Content-Type", "image/png") - graph.Render(chart.PNG, res) + r.Save(res) } func main() { diff --git a/box.go b/box.go index 450ad4c..98c1f17 100644 --- a/box.go +++ b/box.go @@ -1,6 +1,9 @@ package chart -import "fmt" +import ( + "fmt" + "math" +) // Box represents the main 4 dimensions of a box. type Box struct { @@ -139,6 +142,16 @@ func (b Box) Shift(x, y int) Box { } } +// Corners returns the box as a set of corners. +func (b Box) Corners() BoxCorners { + return BoxCorners{ + TopLeft: Point{b.Left, b.Top}, + TopRight: Point{b.Right, b.Top}, + BottomRight: Point{b.Right, b.Bottom}, + BottomLeft: Point{b.Left, b.Bottom}, + } +} + // Fit is functionally the inverse of grow. // Fit maintains the original aspect ratio of the `other` box, // but constrains it to the bounds of the target box. @@ -220,20 +233,98 @@ func (b Box) OuterConstrain(bounds, other Box) Box { return newBox } -// BoundedRotate rotates a box's corners by a given radian rotation angle -// and returns the maximum bounds or clipping rectangle. -func (b Box) BoundedRotate(radians float64) Box { - cx, cy := b.Center() - - ltx, lty := Math.RotateCoordinate(cx, cy, b.Left, b.Top, radians) - lbx, lby := Math.RotateCoordinate(cx, cy, b.Left, b.Bottom, radians) - rtx, rty := Math.RotateCoordinate(cx, cy, b.Right, b.Top, radians) - rbx, rby := Math.RotateCoordinate(cx, cy, b.Right, b.Bottom, radians) +// BoxCorners is a box with independent corners. +type BoxCorners struct { + TopLeft, TopRight, BottomRight, BottomLeft Point +} +// Box return the BoxCorners as a regular box. +func (bc BoxCorners) Box() Box { return Box{ - Left: Math.MinInt(ltx, lbx), - Top: Math.MinInt(lty, rty), - Right: Math.MaxInt(rtx, rbx), - Bottom: Math.MaxInt(lby, rby), + Top: Math.MinInt(bc.TopLeft.Y, bc.TopRight.Y), + Left: Math.MinInt(bc.TopLeft.X, bc.BottomLeft.X), + Right: Math.MaxInt(bc.TopRight.X, bc.BottomRight.X), + Bottom: Math.MaxInt(bc.BottomLeft.Y, bc.BottomRight.Y), } } + +// Width returns the width +func (bc BoxCorners) Width() int { + minLeft := Math.MinInt(bc.TopLeft.X, bc.BottomLeft.X) + maxRight := Math.MaxInt(bc.TopRight.X, bc.BottomRight.X) + return maxRight - minLeft +} + +// Height returns the height +func (bc BoxCorners) Height() int { + minTop := Math.MinInt(bc.TopLeft.Y, bc.TopRight.Y) + maxBottom := Math.MaxInt(bc.BottomLeft.Y, bc.BottomRight.Y) + return maxBottom - minTop +} + +// Center returns the center of the box +func (bc BoxCorners) Center() (x, y int) { + + left := Math.MeanInt(bc.TopLeft.X, bc.BottomLeft.X) + right := Math.MeanInt(bc.TopRight.X, bc.BottomRight.X) + x = ((right - left) >> 1) + left + + top := Math.MeanInt(bc.TopLeft.Y, bc.TopRight.Y) + bottom := Math.MeanInt(bc.BottomLeft.Y, bc.BottomRight.Y) + y = ((bottom - top) >> 1) + top + + return +} + +// Rotate rotates the box. +func (bc BoxCorners) Rotate(thetaDegrees float64) BoxCorners { + cx, cy := bc.Center() + + thetaRadians := Math.DegreesToRadians(thetaDegrees) + + tlx, tly := Math.RotateCoordinate(cx, cy, bc.TopLeft.X, bc.TopLeft.Y, thetaRadians) + trx, try := Math.RotateCoordinate(cx, cy, bc.TopRight.X, bc.TopRight.Y, thetaRadians) + brx, bry := Math.RotateCoordinate(cx, cy, bc.BottomRight.X, bc.BottomRight.Y, thetaRadians) + blx, bly := Math.RotateCoordinate(cx, cy, bc.BottomLeft.X, bc.BottomLeft.Y, thetaRadians) + + return BoxCorners{ + TopLeft: Point{tlx, tly}, + TopRight: Point{trx, try}, + BottomRight: Point{brx, bry}, + BottomLeft: Point{blx, bly}, + } +} + +// Equals returns if the box equals another box. +func (bc BoxCorners) Equals(other BoxCorners) bool { + return bc.TopLeft.Equals(other.TopLeft) && + bc.TopRight.Equals(other.TopRight) && + bc.BottomRight.Equals(other.BottomRight) && + bc.BottomLeft.Equals(other.BottomLeft) +} + +func (bc BoxCorners) String() string { + return fmt.Sprintf("BoxC{%s,%s,%s,%s}", bc.TopLeft.String(), bc.TopRight.String(), bc.BottomRight.String(), bc.BottomLeft.String()) +} + +// Point is an X,Y pair +type Point struct { + X, Y int +} + +// DistanceTo calculates the distance to another point. +func (p Point) DistanceTo(other Point) float64 { + dx := math.Pow(float64(p.X-other.X), 2) + dy := math.Pow(float64(p.Y-other.Y), 2) + return math.Pow(dx+dy, 0.5) +} + +// Equals returns if a point equals another point. +func (p Point) Equals(other Point) bool { + return p.X == other.X && p.Y == other.Y +} + +// String returns a string representation of the point. +func (p Point) String() string { + return fmt.Sprintf("P{%d,%d}", p.X, p.Y) +} diff --git a/box_test.go b/box_test.go index 39503ab..89eafcf 100644 --- a/box_test.go +++ b/box_test.go @@ -158,18 +158,31 @@ func TestBoxCenter(t *testing.T) { assert.Equal(20, cy) } -func TestBoxBoundedRotate(t *testing.T) { +func TestBoxCornersCenter(t *testing.T) { assert := assert.New(t) - b := Box{ - Top: 5, - Left: 5, - Right: 20, - Bottom: 10, + bc := BoxCorners{ + TopLeft: Point{5, 5}, + TopRight: Point{15, 5}, + BottomRight: Point{15, 15}, + BottomLeft: Point{5, 15}, } - rotated := b.BoundedRotate(Math.DegreesToRadians(45)) - assert.Equal(1, rotated.Top) - assert.Equal(5, rotated.Left) - assert.Equal(19, rotated.Right) - assert.Equal(14, rotated.Bottom) + + cx, cy := bc.Center() + assert.Equal(10, cx) + assert.Equal(10, cy) +} + +func TestBoxCornersRotate(t *testing.T) { + assert := assert.New(t) + + bc := BoxCorners{ + TopLeft: Point{5, 5}, + TopRight: Point{15, 5}, + BottomRight: Point{15, 15}, + BottomLeft: Point{5, 15}, + } + + rotated := bc.Rotate(45) + assert.True(rotated.TopLeft.Equals(Point{10, 3}), rotated.String()) } diff --git a/chart.go b/chart.go index 900bf9a..236d714 100644 --- a/chart.go +++ b/chart.go @@ -324,12 +324,10 @@ func (c Chart) getAxesAdjustedCanvasBox(r Renderer, canvasBox Box, xr, yr, yra R axesOuterBox := canvasBox.Clone() if c.XAxis.Style.Show { axesBounds := c.XAxis.Measure(r, canvasBox, xr, c.styleDefaultsAxes(), xticks) - Draw.Box(r, axesBounds, Style{StrokeWidth: 2, StrokeColor: ColorRed}) axesOuterBox = axesOuterBox.Grow(axesBounds) } if c.YAxis.Style.Show { axesBounds := c.YAxis.Measure(r, canvasBox, yr, c.styleDefaultsAxes(), yticks) - Draw.Box(r, axesBounds, Style{StrokeWidth: 2, StrokeColor: ColorBlue}) axesOuterBox = axesOuterBox.Grow(axesBounds) } if c.YAxisSecondary.Style.Show { diff --git a/draw.go b/draw.go index ba27e18..e06462e 100644 --- a/draw.go +++ b/draw.go @@ -140,6 +140,7 @@ func (d draw) HistogramSeries(r Renderer, canvasBox Box, xrange, yrange Range, s // MeasureAnnotation measures how big an annotation would be. func (d draw) MeasureAnnotation(r Renderer, canvasBox Box, style Style, lx, ly int, label string) Box { style.WriteToRenderer(r) + defer r.ResetStyle() textBox := r.MeasureText(label) textWidth := textBox.Width() @@ -168,6 +169,8 @@ func (d draw) MeasureAnnotation(r Renderer, canvasBox Box, style Style, lx, ly i // Annotation draws an anotation with a renderer. func (d draw) Annotation(r Renderer, canvasBox Box, style Style, lx, ly int, label string) { style.GetTextOptions().WriteToRenderer(r) + defer r.ResetStyle() + textBox := r.MeasureText(label) textWidth := textBox.Width() halfTextHeight := textBox.Height() >> 1 @@ -209,7 +212,8 @@ func (d draw) Annotation(r Renderer, canvasBox Box, style Style, lx, ly int, lab // Box draws a box with a given style. func (d draw) Box(r Renderer, b Box, s Style) { - s.WriteToRenderer(r) + s.GetFillAndStrokeOptions().WriteToRenderer(r) + defer r.ResetStyle() r.MoveTo(b.Left, b.Top) r.LineTo(b.Right, b.Top) @@ -219,18 +223,18 @@ func (d draw) Box(r Renderer, b Box, s Style) { r.FillStroke() } -func (d draw) BoxRotated(r Renderer, b Box, thetaRadians float64, s Style) { - s.WriteToRenderer(r) - cx, cy := b.Center() - ltx, lty := Math.RotateCoordinate(cx, cy, b.Left, b.Top, thetaRadians) - lbx, lby := Math.RotateCoordinate(cx, cy, b.Left, b.Bottom, thetaRadians) - rtx, rty := Math.RotateCoordinate(cx, cy, b.Right, b.Top, thetaRadians) - rbx, rby := Math.RotateCoordinate(cx, cy, b.Right, b.Bottom, thetaRadians) +func (d draw) BoxRotated(r Renderer, b Box, thetaDegrees float64, s Style) { + d.BoxCorners(r, b.Corners().Rotate(thetaDegrees), s) +} - r.MoveTo(ltx, lty) - r.LineTo(rtx, rty) - r.LineTo(rbx, rby) - r.LineTo(lbx, lby) +func (d draw) BoxCorners(r Renderer, bc BoxCorners, s Style) { + s.GetFillAndStrokeOptions().WriteToRenderer(r) + defer r.ResetStyle() + + r.MoveTo(bc.TopLeft.X, bc.TopLeft.Y) + r.LineTo(bc.TopRight.X, bc.TopRight.Y) + r.LineTo(bc.BottomRight.X, bc.BottomRight.Y) + r.LineTo(bc.BottomLeft.X, bc.BottomLeft.Y) r.Close() r.FillStroke() } @@ -238,16 +242,26 @@ func (d draw) BoxRotated(r Renderer, b Box, thetaRadians float64, s Style) { // DrawText draws text with a given style. func (d draw) Text(r Renderer, text string, x, y int, style Style) { style.GetTextOptions().WriteToRenderer(r) + defer r.ResetStyle() + r.Text(text, x, y) } +func (d draw) MeasureText(r Renderer, text string, style Style) Box { + style.GetTextOptions().WriteToRenderer(r) + defer r.ResetStyle() + + return r.MeasureText(text) +} + // TextWithin draws the text within a given box. func (d draw) TextWithin(r Renderer, text string, box Box, style Style) { + style.GetTextOptions().WriteToRenderer(r) + defer r.ResetStyle() + lines := Text.WrapFit(r, text, box.Width(), style) linesBox := Text.MeasureLines(r, lines, style) - style.GetTextOptions().WriteToRenderer(r) - y := box.Top switch style.GetTextVerticalAlign() { @@ -268,7 +282,11 @@ func (d draw) TextWithin(r Renderer, text string, box Box, style Style) { default: tx = box.Left } - ty = y + lineBox.Height() + if style.TextRotationDegrees == 0 { + ty = y + lineBox.Height() + } else { + ty = y + } d.Text(r, line, tx, ty, style) y += lineBox.Height() + style.GetTextLineSpacing() diff --git a/go-chart.test b/go-chart.test deleted file mode 100755 index 20cf1fd..0000000 Binary files a/go-chart.test and /dev/null differ diff --git a/legend.go b/legend.go index 9425c9a..698270a 100644 --- a/legend.go +++ b/legend.go @@ -54,9 +54,7 @@ func Legend(c *Chart, userDefaults ...Style) Renderable { Bottom: legend.Top + legendPadding.Top, } - r.SetFont(legendStyle.GetFont()) - r.SetFontColor(legendStyle.GetFontColor()) - r.SetFontSize(legendStyle.GetFontSize()) + legendStyle.GetTextOptions().WriteToRenderer(r) // measure labelCount := 0 @@ -79,6 +77,8 @@ func Legend(c *Chart, userDefaults ...Style) Renderable { Draw.Box(r, legend, legendStyle) + legendStyle.GetTextOptions().WriteToRenderer(r) + ycursor := legendContent.Top tx := legendContent.Left legendCount := 0 diff --git a/math_util.go b/math_util.go index 4121647..8c3f5da 100644 --- a/math_util.go +++ b/math_util.go @@ -134,6 +134,16 @@ func (m mathUtil) AbsInt(value int) int { return value } +// Mean returns the mean of a set of values +func (m mathUtil) Mean(values ...float64) float64 { + return m.Sum(values...) / float64(len(values)) +} + +// MeanInt returns the mean of a set of integer values. +func (m mathUtil) MeanInt(values ...int) int { + return m.SumInt(values...) / len(values) +} + // Sum sums a set of values. func (m mathUtil) Sum(values ...float64) float64 { var total float64 diff --git a/raster_renderer.go b/raster_renderer.go index 37ca0dc..c033627 100644 --- a/raster_renderer.go +++ b/raster_renderer.go @@ -33,6 +33,11 @@ type rasterRenderer struct { s Style } +func (rr *rasterRenderer) ResetStyle() { + rr.s = Style{Font: rr.s.Font} + rr.ClearTextRotation() +} + // GetDPI returns the dpi. func (rr *rasterRenderer) GetDPI() float64 { return rr.gc.GetDPI() @@ -187,7 +192,7 @@ func (rr *rasterRenderer) MeasureText(body string) Box { return textBox } - return textBox.BoundedRotate(*rr.rotateRadians) + return textBox.Corners().Rotate(Math.RadiansToDegrees(*rr.rotateRadians)).Box() } // SetTextRotation sets a text rotation. diff --git a/renderer.go b/renderer.go index 2047a40..7eb06bb 100644 --- a/renderer.go +++ b/renderer.go @@ -9,6 +9,9 @@ import ( // Renderer represents the basic methods required to draw a chart. type Renderer interface { + // ResetStyle should reset any style related settings on the renderer. + ResetStyle() + // GetDPI gets the DPI for the renderer. GetDPI() float64 diff --git a/vector_renderer.go b/vector_renderer.go index f9883b1..c36f065 100644 --- a/vector_renderer.go +++ b/vector_renderer.go @@ -36,6 +36,11 @@ type vectorRenderer struct { fc *font.Drawer } +func (vr *vectorRenderer) ResetStyle() { + vr.s = &Style{Font: vr.s.Font} + vr.fc = nil +} + // GetDPI returns the dpi. func (vr *vectorRenderer) GetDPI() float64 { return vr.dpi @@ -170,7 +175,7 @@ func (vr *vectorRenderer) MeasureText(body string) (box Box) { if vr.c.textTheta == nil { return } - box = box.BoundedRotate(*vr.c.textTheta) + box = box.Corners().Rotate(Math.RadiansToDegrees(*vr.c.textTheta)).Box() } return } diff --git a/xaxis.go b/xaxis.go index 7276840..357776c 100644 --- a/xaxis.go +++ b/xaxis.go @@ -70,20 +70,20 @@ func (xa XAxis) GetGridLines(ticks []Tick) []GridLine { // Measure returns the bounds of the axis. func (xa XAxis) Measure(r Renderer, canvasBox Box, ra Range, defaults Style, ticks []Tick) Box { - tickStyle := xa.Style.InheritFrom(defaults) + tickStyle := xa.TickStyle.InheritFrom(xa.Style.InheritFrom(defaults)) sort.Sort(Ticks(ticks)) tp := xa.GetTickPosition() + var ltx, rtx int + var tx, ty int var left, right, bottom = math.MaxInt32, 0, 0 for index, t := range ticks { v := t.Value - tickStyle.GetTextOptions().WriteToRenderer(r) - tb := r.MeasureText(t.Label) + tb := Draw.MeasureText(r, t.Label, tickStyle.GetTextOptions()) - var ltx, rtx int - tx := canvasBox.Left + ra.Translate(v) - ty := canvasBox.Bottom + DefaultXAxisMargin + tb.Height() + tx = canvasBox.Left + ra.Translate(v) + ty = canvasBox.Bottom + DefaultXAxisMargin + tb.Height() switch tp { case TickPositionUnderTick, TickPositionUnset: ltx = tx - tb.Width()>>1 @@ -103,7 +103,7 @@ func (xa XAxis) Measure(r Renderer, canvasBox Box, ra Range, defaults Style, tic } if xa.NameStyle.Show && len(xa.Name) > 0 { - tb := r.MeasureText(xa.Name) + tb := Draw.MeasureText(r, xa.Name, xa.NameStyle.InheritFrom(defaults)) bottom += DefaultXAxisMargin + tb.Height() } @@ -117,7 +117,7 @@ func (xa XAxis) Measure(r Renderer, canvasBox Box, ra Range, defaults Style, tic // Render renders the axis func (xa XAxis) Render(r Renderer, canvasBox Box, ra Range, defaults Style, ticks []Tick) { - tickStyle := xa.Style.InheritFrom(defaults) + tickStyle := xa.TickStyle.InheritFrom(xa.Style.InheritFrom(defaults)) tickStyle.GetStrokeOptions().WriteToRenderer(r) r.MoveTo(canvasBox.Left, canvasBox.Bottom) @@ -141,25 +141,30 @@ func (xa XAxis) Render(r Renderer, canvasBox Box, ra Range, defaults Style, tick r.LineTo(tx, canvasBox.Bottom+DefaultVerticalTickHeight) r.Stroke() - xa.TickStyle.InheritFrom(xa.Style.InheritFrom(defaults)).WriteToRenderer(r) - tb := r.MeasureText(t.Label) + tickWithAxisStyle := xa.TickStyle.InheritFrom(xa.Style.InheritFrom(defaults)) + tb := Draw.MeasureText(r, t.Label, tickWithAxisStyle) switch tp { case TickPositionUnderTick, TickPositionUnset: - ty = canvasBox.Bottom + DefaultXAxisMargin + tb.Height() - r.Text(t.Label, tx-tb.Width()>>1, ty) + if tickStyle.TextRotationDegrees == 0 { + tx = tx - tb.Width()>>1 + ty = canvasBox.Bottom + DefaultXAxisMargin + tb.Height() + } else { + ty = canvasBox.Bottom + (2 * DefaultXAxisMargin) + } + Draw.Text(r, t.Label, tx, ty, tickWithAxisStyle) maxTextHeight = Math.MaxInt(maxTextHeight, tb.Height()) break case TickPositionBetweenTicks: if index > 0 { llx := ra.Translate(ticks[index-1].Value) ltx := canvasBox.Left + llx - finalTickStyle := tickStyle.InheritFrom(Style{TextHorizontalAlign: TextHorizontalAlignCenter}) + finalTickStyle := tickWithAxisStyle.InheritFrom(Style{TextHorizontalAlign: TextHorizontalAlignCenter}) Draw.TextWithin(r, t.Label, Box{ Left: ltx, Right: tx, Top: canvasBox.Bottom + DefaultXAxisMargin, - Bottom: canvasBox.Bottom + DefaultXAxisMargin + tb.Height(), + Bottom: canvasBox.Bottom + DefaultXAxisMargin, }, finalTickStyle) ftb := Text.MeasureLines(r, Text.WrapFit(r, t.Label, tx-ltx, finalTickStyle), finalTickStyle) @@ -171,11 +176,10 @@ func (xa XAxis) Render(r Renderer, canvasBox Box, ra Range, defaults Style, tick nameStyle := xa.NameStyle.InheritFrom(defaults) if xa.NameStyle.Show && len(xa.Name) > 0 { - nameStyle.GetTextOptions().WriteToRenderer(r) - tb := r.MeasureText(xa.Name) + tb := Draw.MeasureText(r, xa.Name, nameStyle) tx := canvasBox.Right - (canvasBox.Width()>>1 + tb.Width()>>1) ty := canvasBox.Bottom + DefaultXAxisMargin + maxTextHeight + DefaultXAxisMargin + tb.Height() - r.Text(xa.Name, tx, ty) + Draw.Text(r, xa.Name, tx, ty, nameStyle) } if xa.GridMajorStyle.Show || xa.GridMinorStyle.Show { diff --git a/yaxis.go b/yaxis.go index 9f5afb5..6d619cd 100644 --- a/yaxis.go +++ b/yaxis.go @@ -179,7 +179,7 @@ func (ya YAxis) Render(r Renderer, canvasBox Box, ra Range, defaults Style, tick nameStyle := ya.NameStyle.InheritFrom(defaults.InheritFrom(Style{TextRotationDegrees: 90})) if ya.NameStyle.Show && len(ya.Name) > 0 { nameStyle.GetTextOptions().WriteToRenderer(r) - tb := r.MeasureText(ya.Name) + tb := Draw.MeasureText(r, ya.Name, nameStyle) var tx int if ya.AxisType == YAxisPrimary { @@ -188,9 +188,9 @@ func (ya YAxis) Render(r Renderer, canvasBox Box, ra Range, defaults Style, tick tx = canvasBox.Left - (DefaultYAxisMargin + int(sw) + maxTextWidth + DefaultYAxisMargin) } - ty := canvasBox.Bottom - (canvasBox.Height()>>1 + tb.Width()>>1) + ty := canvasBox.Top + (canvasBox.Height()>>1 + tb.Width()>>1) - r.Text(ya.Name, tx, ty) + Draw.Text(r, ya.Name, tx, ty, nameStyle) } if ya.Zero.Style.Show {