Add ability to set CSS classes instead of inline styles (#103)
* Add ability to set CSS classes instead of inline styles This allows to set a `ClassName` field in `Style` structs. Setting this field to anything but "" will cause all other styles to be ignored. The element will then have a `class=` tag instead with the corresponding name. Possible reasons to use this: * Including multiple SVGs on the same webside, using the same styles * Desire to use strict CSP headers * Add warning that setting `ClassName` will drop all other inline styles
This commit is contained in:
parent
6735e8990a
commit
f97f94425f
55
_examples/css_classes/main.go
Normal file
55
_examples/css_classes/main.go
Normal file
|
@ -0,0 +1,55 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/wcharczuk/go-chart"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func inlineSVGWithClasses(res http.ResponseWriter, req *http.Request) {
|
||||
res.Write([]byte(
|
||||
"<!DOCTYPE html><html><head>" +
|
||||
"<link rel=\"stylesheet\" type=\"text/css\" href=\"/main.css\">" +
|
||||
"</head>" +
|
||||
"<body>"))
|
||||
|
||||
pie := chart.PieChart{
|
||||
// Note that setting ClassName will cause all other inline styles to be dropped!
|
||||
Background: chart.Style{ClassName: "background"},
|
||||
Canvas: chart.Style{
|
||||
ClassName: "canvas",
|
||||
},
|
||||
Width: 512,
|
||||
Height: 512,
|
||||
Values: []chart.Value{
|
||||
{Value: 5, Label: "Blue", Style: chart.Style{ClassName: "blue"}},
|
||||
{Value: 5, Label: "Green", Style: chart.Style{ClassName: "green"}},
|
||||
{Value: 4, Label: "Gray", Style: chart.Style{ClassName: "gray"}},
|
||||
},
|
||||
}
|
||||
|
||||
err := pie.Render(chart.SVG, res)
|
||||
if err != nil {
|
||||
fmt.Printf("Error rendering pie chart: %v\n", err)
|
||||
}
|
||||
res.Write([]byte("</body>"))
|
||||
}
|
||||
|
||||
func css(res http.ResponseWriter, req *http.Request) {
|
||||
res.Header().Set("Content-Type", "text/css")
|
||||
res.Write([]byte("svg .background { fill: white; }" +
|
||||
"svg .canvas { fill: white; }" +
|
||||
"svg path.blue { fill: blue; stroke: lightblue; }" +
|
||||
"svg path.green { fill: green; stroke: lightgreen; }" +
|
||||
"svg path.gray { fill: gray; stroke: lightgray; }" +
|
||||
"svg text.blue { fill: white; }" +
|
||||
"svg text.green { fill: white; }" +
|
||||
"svg text.gray { fill: white; }"))
|
||||
}
|
||||
|
||||
func main() {
|
||||
http.HandleFunc("/", inlineSVGWithClasses)
|
||||
http.HandleFunc("/main.css", css)
|
||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||
}
|
|
@ -49,6 +49,10 @@ func (rr *rasterRenderer) SetDPI(dpi float64) {
|
|||
rr.gc.SetDPI(dpi)
|
||||
}
|
||||
|
||||
// SetClassName implements the interface method. However, PNGs have no classes.
|
||||
func (vr *rasterRenderer) SetClassName(_ string) {
|
||||
}
|
||||
|
||||
// SetStrokeColor implements the interface method.
|
||||
func (rr *rasterRenderer) SetStrokeColor(c drawing.Color) {
|
||||
rr.s.StrokeColor = c
|
||||
|
|
|
@ -18,6 +18,9 @@ type Renderer interface {
|
|||
// SetDPI sets the DPI for the renderer.
|
||||
SetDPI(dpi float64)
|
||||
|
||||
// SetClassName sets the current class name.
|
||||
SetClassName(string)
|
||||
|
||||
// SetStrokeColor sets the current stroke color.
|
||||
SetStrokeColor(drawing.Color)
|
||||
|
||||
|
|
31
style.go
31
style.go
|
@ -39,6 +39,8 @@ type Style struct {
|
|||
Show bool
|
||||
Padding Box
|
||||
|
||||
ClassName string
|
||||
|
||||
StrokeWidth float64
|
||||
StrokeColor drawing.Color
|
||||
StrokeDashArray []float64
|
||||
|
@ -71,7 +73,8 @@ func (s Style) IsZero() bool {
|
|||
s.FillColor.IsZero() &&
|
||||
s.FontColor.IsZero() &&
|
||||
s.FontSize == 0 &&
|
||||
s.Font == nil
|
||||
s.Font == nil &&
|
||||
s.ClassName == ""
|
||||
}
|
||||
|
||||
// String returns a text representation of the style.
|
||||
|
@ -87,6 +90,12 @@ func (s Style) String() string {
|
|||
output = []string{"\"show\": false"}
|
||||
}
|
||||
|
||||
if s.ClassName != "" {
|
||||
output = append(output, fmt.Sprintf("\"class_name\": %s", s.ClassName))
|
||||
} else {
|
||||
output = append(output, "\"class_name\": null")
|
||||
}
|
||||
|
||||
if !s.Padding.IsZero() {
|
||||
output = append(output, fmt.Sprintf("\"padding\": %s", s.Padding.String()))
|
||||
} else {
|
||||
|
@ -155,6 +164,16 @@ func (s Style) String() string {
|
|||
return "{" + strings.Join(output, ", ") + "}"
|
||||
}
|
||||
|
||||
func (s Style) GetClassName(defaults ...string) string {
|
||||
if s.ClassName == "" {
|
||||
if len(defaults) > 0 {
|
||||
return defaults[0]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
return s.ClassName
|
||||
}
|
||||
|
||||
// GetStrokeColor returns the stroke color.
|
||||
func (s Style) GetStrokeColor(defaults ...drawing.Color) drawing.Color {
|
||||
if s.StrokeColor.IsZero() {
|
||||
|
@ -321,6 +340,7 @@ func (s Style) GetTextRotationDegrees(defaults ...float64) float64 {
|
|||
|
||||
// WriteToRenderer passes the style's options to a renderer.
|
||||
func (s Style) WriteToRenderer(r Renderer) {
|
||||
r.SetClassName(s.GetClassName())
|
||||
r.SetStrokeColor(s.GetStrokeColor())
|
||||
r.SetStrokeWidth(s.GetStrokeWidth())
|
||||
r.SetStrokeDashArray(s.GetStrokeDashArray())
|
||||
|
@ -337,6 +357,7 @@ func (s Style) WriteToRenderer(r Renderer) {
|
|||
|
||||
// WriteDrawingOptionsToRenderer passes just the drawing style options to a renderer.
|
||||
func (s Style) WriteDrawingOptionsToRenderer(r Renderer) {
|
||||
r.SetClassName(s.GetClassName())
|
||||
r.SetStrokeColor(s.GetStrokeColor())
|
||||
r.SetStrokeWidth(s.GetStrokeWidth())
|
||||
r.SetStrokeDashArray(s.GetStrokeDashArray())
|
||||
|
@ -345,6 +366,7 @@ func (s Style) WriteDrawingOptionsToRenderer(r Renderer) {
|
|||
|
||||
// WriteTextOptionsToRenderer passes just the text style options to a renderer.
|
||||
func (s Style) WriteTextOptionsToRenderer(r Renderer) {
|
||||
r.SetClassName(s.GetClassName())
|
||||
r.SetFont(s.GetFont())
|
||||
r.SetFontColor(s.GetFontColor())
|
||||
r.SetFontSize(s.GetFontSize())
|
||||
|
@ -352,6 +374,8 @@ func (s Style) WriteTextOptionsToRenderer(r Renderer) {
|
|||
|
||||
// InheritFrom coalesces two styles into a new style.
|
||||
func (s Style) InheritFrom(defaults Style) (final Style) {
|
||||
final.ClassName = s.GetClassName(defaults.ClassName)
|
||||
|
||||
final.StrokeColor = s.GetStrokeColor(defaults.StrokeColor)
|
||||
final.StrokeWidth = s.GetStrokeWidth(defaults.StrokeWidth)
|
||||
final.StrokeDashArray = s.GetStrokeDashArray(defaults.StrokeDashArray)
|
||||
|
@ -379,6 +403,7 @@ func (s Style) InheritFrom(defaults Style) (final Style) {
|
|||
// GetStrokeOptions returns the stroke components.
|
||||
func (s Style) GetStrokeOptions() Style {
|
||||
return Style{
|
||||
ClassName: s.ClassName,
|
||||
StrokeDashArray: s.StrokeDashArray,
|
||||
StrokeColor: s.StrokeColor,
|
||||
StrokeWidth: s.StrokeWidth,
|
||||
|
@ -388,6 +413,7 @@ func (s Style) GetStrokeOptions() Style {
|
|||
// GetFillOptions returns the fill components.
|
||||
func (s Style) GetFillOptions() Style {
|
||||
return Style{
|
||||
ClassName: s.ClassName,
|
||||
FillColor: s.FillColor,
|
||||
}
|
||||
}
|
||||
|
@ -395,6 +421,7 @@ func (s Style) GetFillOptions() Style {
|
|||
// GetDotOptions returns the dot components.
|
||||
func (s Style) GetDotOptions() Style {
|
||||
return Style{
|
||||
ClassName: s.ClassName,
|
||||
StrokeDashArray: nil,
|
||||
FillColor: s.DotColor,
|
||||
StrokeColor: s.DotColor,
|
||||
|
@ -405,6 +432,7 @@ func (s Style) GetDotOptions() Style {
|
|||
// GetFillAndStrokeOptions returns the fill and stroke components.
|
||||
func (s Style) GetFillAndStrokeOptions() Style {
|
||||
return Style{
|
||||
ClassName: s.ClassName,
|
||||
StrokeDashArray: s.StrokeDashArray,
|
||||
FillColor: s.FillColor,
|
||||
StrokeColor: s.StrokeColor,
|
||||
|
@ -415,6 +443,7 @@ func (s Style) GetFillAndStrokeOptions() Style {
|
|||
// GetTextOptions returns just the text components of the style.
|
||||
func (s Style) GetTextOptions() Style {
|
||||
return Style{
|
||||
ClassName: s.ClassName,
|
||||
FontColor: s.FontColor,
|
||||
FontSize: s.FontSize,
|
||||
Font: s.Font,
|
||||
|
|
|
@ -54,6 +54,11 @@ func (vr *vectorRenderer) SetDPI(dpi float64) {
|
|||
vr.c.dpi = dpi
|
||||
}
|
||||
|
||||
// SetClassName implements the interface method.
|
||||
func (vr *vectorRenderer) SetClassName(classname string) {
|
||||
vr.s.ClassName = classname
|
||||
}
|
||||
|
||||
// SetStrokeColor implements the interface method.
|
||||
func (vr *vectorRenderer) SetStrokeColor(c drawing.Color) {
|
||||
vr.s.StrokeColor = c
|
||||
|
@ -230,20 +235,20 @@ func (c *canvas) Path(d string, style Style) {
|
|||
if len(style.StrokeDashArray) > 0 {
|
||||
strokeDashArrayProperty = c.getStrokeDashArray(style)
|
||||
}
|
||||
c.w.Write([]byte(fmt.Sprintf(`<path %s d="%s" style="%s"/>`, strokeDashArrayProperty, d, c.styleAsSVG(style))))
|
||||
c.w.Write([]byte(fmt.Sprintf(`<path %s d="%s" %s/>`, strokeDashArrayProperty, d, c.styleAsSVG(style))))
|
||||
}
|
||||
|
||||
func (c *canvas) Text(x, y int, body string, style Style) {
|
||||
if c.textTheta == nil {
|
||||
c.w.Write([]byte(fmt.Sprintf(`<text x="%d" y="%d" style="%s">%s</text>`, x, y, c.styleAsSVG(style), body)))
|
||||
c.w.Write([]byte(fmt.Sprintf(`<text x="%d" y="%d" %s>%s</text>`, x, y, c.styleAsSVG(style), body)))
|
||||
} else {
|
||||
transform := fmt.Sprintf(` transform="rotate(%0.2f,%d,%d)"`, util.Math.RadiansToDegrees(*c.textTheta), x, y)
|
||||
c.w.Write([]byte(fmt.Sprintf(`<text x="%d" y="%d" style="%s"%s>%s</text>`, x, y, c.styleAsSVG(style), transform, body)))
|
||||
c.w.Write([]byte(fmt.Sprintf(`<text x="%d" y="%d" %s%s>%s</text>`, x, y, c.styleAsSVG(style), transform, body)))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *canvas) Circle(x, y, r int, style Style) {
|
||||
c.w.Write([]byte(fmt.Sprintf(`<circle cx="%d" cy="%d" r="%d" style="%s"/>`, x, y, r, c.styleAsSVG(style))))
|
||||
c.w.Write([]byte(fmt.Sprintf(`<circle cx="%d" cy="%d" r="%d" %s/>`, x, y, r, c.styleAsSVG(style))))
|
||||
}
|
||||
|
||||
func (c *canvas) End() {
|
||||
|
@ -274,8 +279,11 @@ func (c *canvas) getFontFace(s Style) string {
|
|||
return fmt.Sprintf("font-family:%s", family)
|
||||
}
|
||||
|
||||
// styleAsSVG returns the style as a svg style string.
|
||||
// styleAsSVG returns the style as a svg style or class string.
|
||||
func (c *canvas) styleAsSVG(s Style) string {
|
||||
if s.ClassName != "" {
|
||||
return fmt.Sprintf("class=\"%s\"", s.ClassName)
|
||||
}
|
||||
sw := s.StrokeWidth
|
||||
sc := s.StrokeColor
|
||||
fc := s.FillColor
|
||||
|
@ -311,5 +319,5 @@ func (c *canvas) styleAsSVG(s Style) string {
|
|||
if s.Font != nil {
|
||||
pieces = append(pieces, c.getFontFace(s))
|
||||
}
|
||||
return strings.Join(pieces, ";")
|
||||
return fmt.Sprintf("style=\"%s\"", strings.Join(pieces, ";"))
|
||||
}
|
||||
|
|
|
@ -71,7 +71,21 @@ func TestCanvasStyleSVG(t *testing.T) {
|
|||
|
||||
svgString := canvas.styleAsSVG(set)
|
||||
assert.NotEmpty(svgString)
|
||||
assert.True(strings.HasPrefix(svgString, "style=\""))
|
||||
assert.True(strings.Contains(svgString, "stroke:rgba(255,255,255,1.0)"))
|
||||
assert.True(strings.Contains(svgString, "stroke-width:5"))
|
||||
assert.True(strings.Contains(svgString, "fill:rgba(255,255,255,1.0)"))
|
||||
assert.True(strings.HasSuffix(svgString, "\""))
|
||||
}
|
||||
|
||||
func TestCanvasClassSVG(t *testing.T) {
|
||||
as := assert.New(t)
|
||||
|
||||
set := Style{
|
||||
ClassName: "test-class",
|
||||
}
|
||||
|
||||
canvas := &canvas{dpi: DefaultDPI}
|
||||
|
||||
as.Equal("class=\"test-class\"", canvas.styleAsSVG(set))
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user