Skip to content

Commit b0a39e6

Browse files
committed
Significant Painter API and SVG rendering changes
Changes put together because both required significant changes to the SVG validation in tests. These changes were carefully examined to make sure there were no negative visual issues. **Painter API changes**: The Painter API can be confusing, with some functions depending on the state of the painter. This changes the Painter API (internal and external) so that style attributes (ie FillColor, StrokeColor, StrokeWidth, etc) are not stateful. Instead the relevant style values are accepted as arguments to the draw action. This creates an easier to use API. It also addresses a complexity if multiple Child painters are used together with different drawing state. Additionally the Painter configuration has changed. A default theme and default font is still available to set in the Painter, however the `ValueFormatter` was removed. Instead this config field only exists in charts options which make use of a set formatter. Test SVG's resulted from two primary aspects of this change: * Measuring text not representing the actual text size due to the font style not set correctly in the Painter * A fill style attribute being specified when it has no effect (since style was set due to prior operations) In general the exposed functions were general examined to ensure all exposed functions are both useful, and easy to use. This resulted in a number of argument changes, which should be detailed in the go docs. **vector_renderer (SVG) improvements**: This change minimizes and simplifies the SVG output through the following: * If stroke width is zero, or the color is transparent only `stroke:none` will be set, instead of the prior `stroke-width:0;stroke:none` value * Colors will be specified by name if possible, otherwise an RGB or RGBA string will still be used * If the text or path is empty, nothing will be inserted into the svg. These three changes reduce the SVG size. Specifying the color names could potentially allow color shade changes using CSS. As a bonus, additional colors are now specified in the `drawing` package. ---- SVG changes are always painful, these were careful evaluated to ensure this is an improvement in both the Painter functionality and SVG results.
1 parent 6420826 commit b0a39e6

40 files changed

+1019
-926
lines changed

axis.go

Lines changed: 14 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ package charts
33
import (
44
"math"
55
"strings"
6-
7-
"github.com/go-analyze/charts/chartdraw"
86
)
97

108
type axisPainter struct {
@@ -97,23 +95,7 @@ func (a *axisPainter) Render() (Box, error) {
9795
tickLength := getDefaultInt(opt.TickLength, 5)
9896
labelMargin := getDefaultInt(opt.LabelMargin, 5)
9997

100-
style := chartdraw.Style{
101-
StrokeColor: theme.GetAxisStrokeColor(),
102-
StrokeWidth: strokeWidth,
103-
FontStyle: fontStyle,
104-
}
105-
top.SetDrawingStyle(style)
106-
top.OverrideFontStyle(style.FontStyle)
107-
108-
isTextRotation := opt.TextRotation != 0
109-
110-
if isTextRotation {
111-
top.setTextRotation(opt.TextRotation)
112-
}
113-
textMaxWidth, textMaxHeight := top.measureTextMaxWidthHeight(opt.Data)
114-
if isTextRotation {
115-
top.clearTextRotation()
116-
}
98+
textMaxWidth, textMaxHeight := top.measureTextMaxWidthHeight(opt.Data, fontStyle, opt.TextRotation)
11799

118100
width := 0
119101
height := 0
@@ -227,22 +209,25 @@ func (a *axisPainter) Render() (Box, error) {
227209
}
228210

229211
if strokeWidth > 0 {
212+
strokeColor := theme.GetAxisStrokeColor()
230213
p.Child(PainterPaddingOption(Box{
231214
Top: ticksPaddingTop,
232215
Left: ticksPaddingLeft,
233216
IsSet: true,
234217
})).ticks(ticksOption{
235-
labelCount: labelCount,
236-
tickCount: tickCount,
237-
tickSpaces: tickSpaces,
238-
length: tickLength,
239-
vertical: isVertical,
240-
firstIndex: opt.DataStartIndex,
218+
labelCount: labelCount,
219+
tickCount: tickCount,
220+
tickSpaces: tickSpaces,
221+
length: tickLength,
222+
vertical: isVertical,
223+
firstIndex: opt.DataStartIndex,
224+
strokeWidth: strokeWidth,
225+
strokeColor: strokeColor,
241226
})
242227
p.LineStroke([]Point{
243228
{X: x0, Y: y0},
244229
{X: x1, Y: y1},
245-
})
230+
}, strokeColor, strokeWidth)
246231
}
247232

248233
p.Child(PainterPaddingOption(Box{
@@ -254,6 +239,7 @@ func (a *axisPainter) Render() (Box, error) {
254239
firstIndex: opt.DataStartIndex,
255240
align: textAlign,
256241
textList: opt.Data,
242+
fontStyle: fontStyle,
257243
vertical: isVertical,
258244
labelCount: labelCount,
259245
tickCount: tickCount,
@@ -264,9 +250,6 @@ func (a *axisPainter) Render() (Box, error) {
264250
})
265251

266252
if opt.SplitLineShow { // show auxiliary lines
267-
style.StrokeColor = theme.GetAxisSplitLineColor()
268-
style.StrokeWidth = 1
269-
top.OverrideDrawingStyle(style)
270253
if isVertical {
271254
x0 := p.Width()
272255
x1 := top.Width()
@@ -280,7 +263,7 @@ func (a *axisPainter) Render() (Box, error) {
280263
top.LineStroke([]Point{
281264
{X: x0, Y: y},
282265
{X: x1, Y: y},
283-
})
266+
}, theme.GetAxisSplitLineColor(), 1)
284267
}
285268
} else {
286269
y0 := p.Height() - defaultXAxisHeight
@@ -293,7 +276,7 @@ func (a *axisPainter) Render() (Box, error) {
293276
top.LineStroke([]Point{
294277
{X: x, Y: y0},
295278
{X: x, Y: y1},
296-
})
279+
}, theme.GetAxisSplitLineColor(), 1)
297280
}
298281
}
299282
}

axis_test.go

Lines changed: 11 additions & 11 deletions
Large diffs are not rendered by default.

bar_chart.go

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ import (
55
"math"
66

77
"github.com/golang/freetype/truetype"
8-
9-
"github.com/go-analyze/charts/chartdraw"
108
)
119

1210
type barChart struct {
@@ -43,14 +41,17 @@ type BarChartOption struct {
4341
BarWidth int
4442
// RoundedBarCaps set to `true` to produce a bar graph where the bars have rounded tops.
4543
RoundedBarCaps *bool
44+
// ValueFormatter defines how float values should be rendered to strings, notably for numeric axis labels.
45+
ValueFormatter ValueFormatter
4646
}
4747

4848
func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (Box, error) {
4949
p := b.p
5050
opt := b.opt
5151
seriesPainter := result.seriesPainter
5252

53-
xRange := newRange(b.p, seriesPainter.Width(), len(opt.XAxis.Data), 0.0, 0.0, 0.0, 0.0)
53+
xRange := newRange(b.p, opt.ValueFormatter,
54+
seriesPainter.Width(), len(opt.XAxis.Data), 0.0, 0.0, 0.0, 0.0)
5455
x0, x1 := xRange.GetRange(0)
5556
width := int(x1 - x0)
5657
// margin between each block
@@ -108,28 +109,25 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B
108109
}
109110

110111
h := yRange.getHeight(item)
111-
fillColor := seriesColor
112112
top := barMaxHeight - h
113113

114-
seriesPainter.OverrideDrawingStyle(chartdraw.Style{
115-
FillColor: fillColor,
116-
})
117114
if flagIs(true, opt.RoundedBarCaps) {
118115
seriesPainter.roundedRect(Box{
119116
Top: top,
120117
Left: x,
121118
Right: x + barWidth,
122119
Bottom: barMaxHeight - 1,
123120
IsSet: true,
124-
}, barWidth, true, false)
121+
}, barWidth, true, false,
122+
seriesColor, seriesColor, 0.0)
125123
} else {
126124
seriesPainter.filledRect(Box{
127125
Top: top,
128126
Left: x,
129127
Right: x + barWidth,
130128
Bottom: barMaxHeight - 1,
131129
IsSet: true,
132-
})
130+
}, seriesColor, seriesColor, 0.0)
133131
}
134132
// generate marker point by hand
135133
points[j] = Point{
@@ -147,7 +145,7 @@ func (b *barChart) render(result *defaultRenderResult, seriesList SeriesList) (B
147145
y = barMaxHeight
148146
radians = -math.Pi / 2
149147
if fontStyle.FontColor.IsZero() {
150-
if isLightColor(fillColor) {
148+
if isLightColor(seriesColor) {
151149
fontStyle.FontColor = defaultLightFontColor
152150
} else {
153151
fontStyle.FontColor = defaultDarkFontColor
@@ -196,13 +194,14 @@ func (b *barChart) Render() (Box, error) {
196194
opt.Theme = getPreferredTheme(p.theme)
197195
}
198196
renderResult, err := defaultRender(p, defaultRenderOption{
199-
theme: opt.Theme,
200-
padding: opt.Padding,
201-
seriesList: opt.SeriesList,
202-
xAxis: opt.XAxis,
203-
yAxis: opt.YAxis,
204-
title: opt.Title,
205-
legend: opt.Legend,
197+
theme: opt.Theme,
198+
padding: opt.Padding,
199+
seriesList: opt.SeriesList,
200+
xAxis: opt.XAxis,
201+
yAxis: opt.YAxis,
202+
title: opt.Title,
203+
legend: opt.Legend,
204+
valueFormatter: opt.ValueFormatter,
206205
})
207206
if err != nil {
208207
return BoxZero, err

bar_chart_test.go

Lines changed: 18 additions & 6 deletions
Large diffs are not rendered by default.

chart_option_test.go

Lines changed: 13 additions & 7 deletions
Large diffs are not rendered by default.

chartdraw/drawing/color.go

Lines changed: 130 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,36 +12,71 @@ import (
1212
var (
1313
// ColorTransparent is a fully transparent color.
1414
ColorTransparent = Color{R: 255, G: 255, B: 255, A: 0}
15-
// ColorWhite is white.
15+
// ColorWhite is R: 255, G: 255, B: 255.
1616
ColorWhite = Color{R: 255, G: 255, B: 255, A: 255}
17-
// ColorBlack is black.
17+
// ColorBlack is R: 0, G: 0, B: 0.
1818
ColorBlack = Color{R: 0, G: 0, B: 0, A: 255}
19-
// ColorRed is red.
19+
// ColorGray is R: 128, G: 128, B: 128,
20+
ColorGray = Color{R: 128, G: 128, B: 128, A: 255}
21+
// ColorRed is R: 255, G: 0, B: 0.
2022
ColorRed = Color{R: 255, G: 0, B: 0, A: 255}
21-
// ColorGreen is green.
23+
// ColorGreen is R: 0, G: 128, B: 0.
2224
ColorGreen = Color{R: 0, G: 128, B: 0, A: 255}
23-
// ColorBlue is blue.
25+
// ColorBlue is R: 0, G: 0, B: 255.
2426
ColorBlue = Color{R: 0, G: 0, B: 255, A: 255}
25-
// ColorSilver is a known color.
27+
// ColorSilver is R: 192, G: 192, B: 192.
2628
ColorSilver = Color{R: 192, G: 192, B: 192, A: 255}
27-
// ColorMaroon is a known color.
29+
// ColorMaroon is R: 128, G: 0, B: 0.
2830
ColorMaroon = Color{R: 128, G: 0, B: 0, A: 255}
29-
// ColorPurple is a known color.
31+
// ColorPurple is R: 128, G: 0, B: 128.
3032
ColorPurple = Color{R: 128, G: 0, B: 128, A: 255}
31-
// ColorFuchsia is a known color.
33+
// ColorFuchsia is R: 255, G: 0, B: 255.
3234
ColorFuchsia = Color{R: 255, G: 0, B: 255, A: 255}
33-
// ColorLime is a known color.
35+
// ColorLime is R: 0, G: 255, B: 0.
3436
ColorLime = Color{R: 0, G: 255, B: 0, A: 255}
35-
// ColorOlive is a known color.
37+
// ColorOlive is R: 128, G: 128, B: 0.
3638
ColorOlive = Color{R: 128, G: 128, B: 0, A: 255}
37-
// ColorYellow is a known color.
39+
// ColorYellow is R: 255, G: 255, B: 0.
3840
ColorYellow = Color{R: 255, G: 255, B: 0, A: 255}
39-
// ColorNavy is a known color.
41+
// ColorNavy is R: 0, G: 0, B: 128.
4042
ColorNavy = Color{R: 0, G: 0, B: 128, A: 255}
41-
// ColorTeal is a known color.
43+
// ColorTeal is R: 0, G: 128, B: 128.
4244
ColorTeal = Color{R: 0, G: 128, B: 128, A: 255}
43-
// ColorAqua is a known color.
45+
// ColorAqua is R: 0, G: 255, B: 255.
4446
ColorAqua = Color{R: 0, G: 255, B: 255, A: 255}
47+
48+
// select extended colors
49+
50+
// ColorAzure is R: 240, G: 255, B: 255.
51+
ColorAzure = Color{R: 240, G: 255, B: 255, A: 255}
52+
// ColorBeige is R: 245, G: 245, B: 220.
53+
ColorBeige = Color{R: 245, G: 245, B: 220, A: 255}
54+
// ColorBrown is R: 165, G: 42, B: 42.
55+
ColorBrown = Color{R: 165, G: 42, B: 42, A: 255}
56+
// ColorChocolate is R: 210, G: 105, B: 30.
57+
ColorChocolate = Color{R: 210, G: 105, B: 30, A: 255}
58+
// ColorCoral is R: 255, G: 127, B: 80.
59+
ColorCoral = Color{R: 255, G: 127, B: 80, A: 255}
60+
// ColorGold is R: 255, G: 215, B: 0.
61+
ColorGold = Color{R: 255, G: 215, B: 0, A: 255}
62+
// ColorIndigo is R: 75, G: 0, B: 130.
63+
ColorIndigo = Color{R: 75, G: 0, B: 130, A: 255}
64+
// ColorIvory is R: 255, G: 255, B: 250.
65+
ColorIvory = Color{R: 255, G: 255, B: 250, A: 255}
66+
// ColorOrange is R: 255, G: 165, B: 0.
67+
ColorOrange = Color{R: 255, G: 165, B: 0, A: 255}
68+
// ColorPink is R: 255, G: 192, B: 203.
69+
ColorPink = Color{R: 255, G: 192, B: 203, A: 255}
70+
// ColorPlum is R: 221, G: 160, B: 221.
71+
ColorPlum = Color{R: 221, G: 160, B: 221, A: 255}
72+
// ColorSalmon is R: 250, G: 128, B: 114.
73+
ColorSalmon = Color{R: 250, G: 128, B: 114, A: 255}
74+
// ColorTan is R: 210, G: 180, B: 140.
75+
ColorTan = Color{R: 210, G: 180, B: 140, A: 255}
76+
// ColorTurquoise is R: 64, G: 224, B: 208.
77+
ColorTurquoise = Color{R: 64, G: 224, B: 208, A: 255}
78+
// ColorViolet is R: 238, G: 130, B: 238.
79+
ColorViolet = Color{R: 238, G: 130, B: 238, A: 255}
4580
)
4681

4782
// ParseColor parses a color from a string.
@@ -129,6 +164,8 @@ func ColorFromKnown(known string) Color {
129164
return ColorWhite
130165
case "black":
131166
return ColorBlack
167+
case "grey", "gray":
168+
return ColorGray
132169
case "red":
133170
return ColorRed
134171
case "blue":
@@ -153,8 +190,38 @@ func ColorFromKnown(known string) Color {
153190
return ColorNavy
154191
case "teal":
155192
return ColorTeal
156-
case "aqua":
193+
case "cyan", "aqua":
157194
return ColorAqua
195+
case "azure":
196+
return ColorAzure
197+
case "beige":
198+
return ColorBeige
199+
case "brown":
200+
return ColorBrown
201+
case "chocolate":
202+
return ColorChocolate
203+
case "coral":
204+
return ColorCoral
205+
case "gold":
206+
return ColorGold
207+
case "indigo":
208+
return ColorIndigo
209+
case "ivory":
210+
return ColorIvory
211+
case "orange":
212+
return ColorOrange
213+
case "pink":
214+
return ColorPink
215+
case "plum":
216+
return ColorPlum
217+
case "salmon":
218+
return ColorSalmon
219+
case "tan":
220+
return ColorTan
221+
case "turquoise":
222+
return ColorTurquoise
223+
case "violet":
224+
return ColorViolet
158225
default:
159226
return Color{}
160227
}
@@ -235,6 +302,53 @@ func (c Color) AverageWith(other Color) Color {
235302

236303
// String returns a css string representation of the color.
237304
func (c Color) String() string {
305+
switch c {
306+
case ColorWhite:
307+
return "white"
308+
case ColorBlack:
309+
return "black"
310+
case ColorRed:
311+
return "red"
312+
case ColorBlue:
313+
return "blue"
314+
case ColorGreen:
315+
return "green"
316+
case ColorSilver:
317+
return "silver"
318+
case ColorMaroon:
319+
return "maroon"
320+
case ColorPurple:
321+
return "purple"
322+
case ColorFuchsia:
323+
return "fuchsia"
324+
case ColorLime:
325+
return "lime"
326+
case ColorOlive:
327+
return "olive"
328+
case ColorYellow:
329+
return "yellow"
330+
case ColorNavy:
331+
return "navy"
332+
case ColorTeal:
333+
return "teal"
334+
case ColorAqua:
335+
return "aqua"
336+
default:
337+
if c.A == 255 {
338+
return c.StringRGB()
339+
} else {
340+
return c.StringRGBA()
341+
}
342+
}
343+
}
344+
345+
// StringRGB returns a css RGB string representation of the color.
346+
func (c Color) StringRGB() string {
347+
return fmt.Sprintf("rgb(%v,%v,%v)", c.R, c.G, c.B)
348+
}
349+
350+
// StringRGBA returns a css RGBA string representation of the color.
351+
func (c Color) StringRGBA() string {
238352
fa := float64(c.A) / float64(255)
239353
return fmt.Sprintf("rgba(%v,%v,%v,%.1f)", c.R, c.G, c.B, fa)
240354
}

chartdraw/raster_renderer.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,9 @@ func (rr *rasterRenderer) SetFontColor(c drawing.Color) {
150150

151151
// Text implements the interface method.
152152
func (rr *rasterRenderer) Text(body string, x, y int) {
153+
if body == "" {
154+
return
155+
}
153156
xf, yf := rr.getCoords(x, y)
154157
rr.gc.SetFont(rr.s.Font)
155158
rr.gc.SetFontSize(rr.s.FontSize)

0 commit comments

Comments
 (0)