func wrapPath(p string) string {
	const width = 70
	if len(p) <= width {
		return p
	}
	parts := make([]string, 0, 16)
	for len(p) > width {
		lastCmd, lastSpace := 0, 0
		for i, ch := range p {
			if i >= width && (lastCmd != 0 || lastSpace != 0) {
				break
			}
			if 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' {
				lastCmd = i
			} else if ch == ' ' {
				lastSpace = i
			}
		}
		split := len(p)
		if lastCmd != 0 {
			split = lastCmd
		} else if lastSpace != 0 {
			split = lastSpace
		}
		parts, p = append(parts, p[:split]), p[split:]
	}
	if len(p) > 0 {
		parts = append(parts, p)
	}
	return strings.Join(parts, "\n")
}
wrapPath wraps path data p to avoid exceeding SVG's recommended
line length limit of 255 characters.
package gg
import (
	"bytes"
	"fmt"
	"io"
	"math"
	"reflect"
	"strings"
	"github.com/aclements/go-gg/generic/slice"
	"github.com/aclements/go-gg/table"
	"github.com/ajstarks/svgo"
)
const fontSize float64 = 14
const facetLabelHeight = 1.3
const xTickSep = 5  
const yTickSep = 5  
var plotMargins = func(w, h float64) (t, r, b, l float64) {
	margin := 0.05 * math.Min(w, h)
	return margin, margin, margin, margin
}
func (p *Plot) WriteSVG(w io.Writer, width, height int) error {
	for aes, scales := range p.scales {
		if aes == "x" || aes == "y" {
			continue
		}
		for _, scale := range scales.scales {
			if scale.Ranger(nil) == nil {
				scale.Ranger(defaultRanger(aes))
			}
		}
	}
	subplots := make(map[*subplot]*eltSubplot)
	plotElts := []plotElt{}
	for _, mark := range p.marks {
		submarks := make(map[*eltSubplot]plotMark)
		for _, gid := range mark.groups {
			subplot := subplotOf(gid)
			elt := subplots[subplot]
			if elt == nil {
				elt = newEltSubplot(subplot)
				plotElts = append(plotElts, elt)
				subplots[subplot] = elt
			}
			submark := submarks[elt]
			submark.m = mark.m
			submark.groups = append(submark.groups, gid)
			submarks[elt] = submark
		}
		for subplot, submark := range submarks {
			subplot.marks = append(subplot.marks, submark)
		}
	}
	for sk := range p.scaleSet {
		subplot := subplotOf(sk.gid)
		elt := subplots[subplot]
		if elt == nil {
			continue
		}
		ss := elt.scales[sk.aes]
		if ss == nil {
			ss = make(map[Scaler]bool)
			elt.scales[sk.aes] = ss
		}
		ss[sk.scale] = true
	}
	plotElts = addSubplotLabels(plotElts)
	var xlabel, ylabel string
	if l, ok := p.axisLabels["x"]; ok {
		xlabel = l
	} else {
		xlabel = strings.Join(slice.Nub(p.autoAxisLabels["x"]).([]string), "\n")
	}
	if l, ok := p.axisLabels["y"]; ok {
		ylabel = l
	} else {
		ylabel = strings.Join(slice.Nub(p.autoAxisLabels["y"]).([]string), "\n")
	}
	plotElts = addAxisLabels(plotElts, p.title, xlabel, ylabel)
	layout := layoutPlotElts(plotElts)
	layout.SetLayout(0, 0, float64(width), float64(height))
	for _, elt := range plotElts {
		if elt, ok := elt.(*eltTicks); ok {
			elt.computeTicks()
		}
	}
	layout.SetLayout(0, 0, float64(width), float64(height))
	svg := svg.New(w)
	svg.Start(width, height, fmt.Sprintf(`font-size="%.6gpx" font-family="Roboto,&quot;Helvetica Neue&quot;,Helvetica,Arial,sans-serif"`, fontSize))
	defer svg.End()
	r := &eltRender{svg, 0}
	for _, elt := range plotElts {
		elt.render(r)
	}
	return nil
}
func (e *eltSubplot) render(r *eltRender) {
	svg := r.svg
	x, y, w, h := e.Layout()
	m := e.plotMargins
	x2i, y2i := int(x+w), int(y+h)
	xi, yi := int(math.Ceil(x)), int(math.Ceil(y))
	wi, hi := x2i-xi, y2i-yi
	clipId, clipRef := r.genid("clip")
	svg.ClipPath(`id="` + clipId + `"`)
	svg.Rect(xi, yi, wi, hi)
	svg.ClipEnd()
	svg.Group(`clip-path="` + clipRef + `"`)
	xRanger := NewFloatRanger(float64(xi)+m.l, float64(x2i)-m.r)
	yRanger := NewFloatRanger(float64(y2i)-m.b, float64(yi)+m.t)
	for s := range e.scales["x"] {
		s.Ranger(xRanger)
	}
	for s := range e.scales["y"] {
		s.Ranger(yRanger)
	}
	renderBackground(svg, xi, yi, wi, hi)
	for s := range e.scales["x"] {
		renderGrid(svg, 'x', s, e.xTicks.ticks[s], yi, y2i)
	}
	for s := range e.scales["y"] {
		renderGrid(svg, 'y', s, e.yTicks.ticks[s], xi, x2i)
	}
	env := &renderEnv{
		cache: make(map[renderCacheKey]table.Slice),
		area:  [4]float64{float64(xi), float64(yi), float64(wi), float64(hi)},
	}
	for _, mark := range e.marks {
		for _, gid := range mark.groups {
			env.gid = gid
			mark.m.mark(env, svg)
		}
	}
	svg.Gend()
	svg.Path(fmt.Sprintf("M%d %dV%dH%d", xi, yi, y2i, x2i), "stroke:#888; fill:none; stroke-width:2")  
	for s := range e.scales["x"] {
		renderScale(svg, 'x', s, e.xTicks.ticks[s], y2i)
	}
	for s := range e.scales["y"] {
		renderScale(svg, 'y', s, e.yTicks.ticks[s], xi)
	}
}
func renderBackground(svg *svg.SVG, x, y, w, h int) {
	svg.Rect(x, y, w, h, "fill:#eee")  
}
func renderGrid(svg *svg.SVG, dir rune, scale Scaler, ticks plotEltTicks, start, end int) {
	major := mapMany(scale, ticks.major).([]float64)
	r := func(x float64) float64 {
		return math.Floor(x + 0.5)
	}
	var path []string
	for _, p := range major {
		if dir == 'x' {
			path = append(path, fmt.Sprintf("M%.6g %dv%d", r(p), start, end-start))
		} else {
			path = append(path, fmt.Sprintf("M%d %.6gh%d", start, r(p), end-start))
		}
	}
	svg.Path(wrapPath(strings.Join(path, "")), "stroke: #fff; stroke-width:2")  
}
func renderScale(svg *svg.SVG, dir rune, scale Scaler, ticks plotEltTicks, pos int) {
	const length float64 = 4  
	var path bytes.Buffer
	have := map[float64]bool{}
	for _, t := range []struct {
		length float64
		s      table.Slice
	}{
		{length * 2, ticks.major},
		{length, ticks.minor},
	} {
		ticks := mapMany(scale, t.s).([]float64)
		r := func(x float64) float64 {
			return math.Floor(x + 0.5)
		}
		for _, p := range ticks {
			p = r(p)
			if have[p] {
				continue
			}
			have[p] = true
			if dir == 'x' {
				fmt.Fprintf(&path, "M%.6g %dv%.6g", p, pos, -t.length)
			} else {
				fmt.Fprintf(&path, "M%d %.6gh%.6g", pos, p, t.length)
			}
		}
	}
	svg.Path(wrapPath(path.String()), "stroke:#888; stroke-width:2")  
}
func (e *eltTicks) render(r *eltRender) {
	svg := r.svg
	x, y, w, _ := e.Layout()
	for s := range e.scales() {
		pos := e.mapTicks(s, e.ticks[s].major)
		for i, label := range e.ticks[s].labels {
			tick := pos[i]
			if e.axis == 'x' {
				svg.Text(int(tick), int(y+xTickSep), label, `text-anchor="middle" dy="1em" fill="#666"`)  
			} else {
				svg.Text(int(x+w-yTickSep), int(tick), label, `text-anchor="end" dy=".3em" fill="#666"`)
			}
		}
	}
}
func (e *eltLabel) render(r *eltRender) {
	svg := r.svg
	x, y, w, h := e.Layout()
	clipId, clipRef := r.genid("clip")
	svg.ClipPath(`id="` + clipId + `"`)
	svg.Rect(int(x), int(y), int(w), int(h))
	svg.ClipEnd()
	svg.Group(`clip-path="` + clipRef + `"`)
	defer svg.Gend()
	if e.fill != "none" {
		svg.Rect(int(x), int(y), int(w), int(h), "fill: "+e.fill)
	}
	style := `text-anchor="middle" dy=".3em"`
	switch e.side {
	case 'l':
		style += fmt.Sprintf(` transform="rotate(-90 %d %d)"`, int(x+w/2), int(y+h/2))
	case 'r':
		style += fmt.Sprintf(` transform="rotate(90 %d %d)"`, int(x+w/2), int(y+h/2))
	}
	svg.Text(int(x+w/2), int(y+h/2), e.label, style)
}
func (e *eltPadding) render(r *eltRender) {
}
type renderEnv struct {
	gid   table.GroupID
	cache map[renderCacheKey]table.Slice
	area  [4]float64
}
type renderCacheKey struct {
	sd  *scaledData
	gid table.GroupID
}
type scaledData struct {
	seqs map[table.GroupID]scaledSeq
}
type scaledSeq struct {
	seq    table.Slice
	scaler Scaler
}
func (env *renderEnv) get(sd *scaledData) table.Slice {
	cacheKey := renderCacheKey{sd, env.gid}
	if mapped := env.cache[cacheKey]; mapped != nil {
		return mapped
	}
	v := sd.seqs[env.gid]
	mapped := mapMany(v.scaler, v.seq)
	env.cache[cacheKey] = mapped
	return mapped
}
func (env *renderEnv) getFirst(sd *scaledData) interface{} {
	if mapped := env.cache[renderCacheKey{sd, env.gid}]; mapped != nil {
		mv := reflect.ValueOf(mapped)
		if mv.Len() == 0 {
			return nil
		}
		return mv.Index(0).Interface()
	}
	v := sd.seqs[env.gid]
	rv := reflect.ValueOf(v.seq)
	if rv.Len() == 0 {
		return nil
	}
	return v.scaler.Map(rv.Index(0).Interface())
}
func (env *renderEnv) Area() (x, y, w, h float64) {
	return env.area[0], env.area[1], env.area[2], env.area[3]
}
func (env *renderEnv) Size() (w, h float64) {
	return env.area[2], env.area[3]
}
func round(x float64) int {
	return int(math.Floor(x + 0.5))
}