Merge pull request #1944 from danielsuh05/issue1943
fix: path animations in sketch mode are now normal + bidirectional path animations are correct
|
|
@ -321,29 +321,70 @@ func Paths(r *Runner, shape d2target.Shape, paths []string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Connection(r *Runner, connection d2target.Connection, path, attrs string) (string, error) {
|
func Connection(r *Runner, connection d2target.Connection, path, attrs string) (string, error) {
|
||||||
roughness := 0.5
|
|
||||||
js := fmt.Sprintf(`node = rc.path("%s", {roughness: %f, seed: 1});`, path, roughness)
|
|
||||||
paths, err := computeRoughPathData(r, js)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
output := ""
|
|
||||||
animatedClass := ""
|
animatedClass := ""
|
||||||
if connection.Animated {
|
if connection.Animated {
|
||||||
animatedClass = " animated-connection"
|
animatedClass = " animated-connection"
|
||||||
}
|
}
|
||||||
|
|
||||||
pathEl := d2themes.NewThemableElement("path")
|
if connection.Animated {
|
||||||
pathEl.Fill = color.None
|
// If connection is animated and bidirectional
|
||||||
pathEl.Stroke = connection.Stroke
|
if (connection.DstArrow == d2target.NoArrowhead && connection.SrcArrow == d2target.NoArrowhead) || (connection.DstArrow != d2target.NoArrowhead && connection.SrcArrow != d2target.NoArrowhead) {
|
||||||
pathEl.ClassName = fmt.Sprintf("connection%s", animatedClass)
|
// There is no pure CSS way to animate bidirectional connections in two directions, so we split it up
|
||||||
pathEl.Style = connection.CSSStyle()
|
path1, path2, err := svg.SplitPath(path, 0.5)
|
||||||
pathEl.Attributes = attrs
|
|
||||||
for _, p := range paths {
|
if err != nil {
|
||||||
pathEl.D = p
|
return "", err
|
||||||
output += pathEl.Render()
|
}
|
||||||
|
|
||||||
|
pathEl1 := d2themes.NewThemableElement("path")
|
||||||
|
pathEl1.D = path1
|
||||||
|
pathEl1.Fill = color.None
|
||||||
|
pathEl1.Stroke = connection.Stroke
|
||||||
|
pathEl1.ClassName = fmt.Sprintf("connection%s", animatedClass)
|
||||||
|
pathEl1.Style = connection.CSSStyle()
|
||||||
|
pathEl1.Style += "animation-direction: reverse;"
|
||||||
|
pathEl1.Attributes = attrs
|
||||||
|
|
||||||
|
pathEl2 := d2themes.NewThemableElement("path")
|
||||||
|
pathEl2.D = path2
|
||||||
|
pathEl2.Fill = color.None
|
||||||
|
pathEl2.Stroke = connection.Stroke
|
||||||
|
pathEl2.ClassName = fmt.Sprintf("connection%s", animatedClass)
|
||||||
|
pathEl2.Style = connection.CSSStyle()
|
||||||
|
pathEl2.Attributes = attrs
|
||||||
|
return pathEl1.Render() + " " + pathEl2.Render(), nil
|
||||||
|
} else {
|
||||||
|
pathEl := d2themes.NewThemableElement("path")
|
||||||
|
pathEl.D = path
|
||||||
|
pathEl.Fill = color.None
|
||||||
|
pathEl.Stroke = connection.Stroke
|
||||||
|
pathEl.ClassName = fmt.Sprintf("connection%s", animatedClass)
|
||||||
|
pathEl.Style = connection.CSSStyle()
|
||||||
|
pathEl.Attributes = attrs
|
||||||
|
return pathEl.Render(), nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
roughness := 0.5
|
||||||
|
js := fmt.Sprintf(`node = rc.path("%s", {roughness: %f, seed: 1});`, path, roughness)
|
||||||
|
paths, err := computeRoughPathData(r, js)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
output := ""
|
||||||
|
|
||||||
|
pathEl := d2themes.NewThemableElement("path")
|
||||||
|
pathEl.Fill = color.None
|
||||||
|
pathEl.Stroke = connection.Stroke
|
||||||
|
pathEl.ClassName = fmt.Sprintf("connection%s", animatedClass)
|
||||||
|
pathEl.Style = connection.CSSStyle()
|
||||||
|
pathEl.Attributes = attrs
|
||||||
|
for _, p := range paths {
|
||||||
|
pathEl.D = p
|
||||||
|
output += pathEl.Render()
|
||||||
|
}
|
||||||
|
return output, nil
|
||||||
}
|
}
|
||||||
return output, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO cleanup
|
// TODO cleanup
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 59 KiB |
|
|
@ -11,7 +11,6 @@ import (
|
||||||
"html"
|
"html"
|
||||||
"io"
|
"io"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"math"
|
"math"
|
||||||
|
|
@ -495,178 +494,6 @@ func makeLabelMask(labelTL *geo.Point, width, height int, opacity float64) strin
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets a certain line/curve's SVG path string. offsetIdx and pathData provides the points needed
|
|
||||||
func getSVGPathString(pathType string, offsetIdx int, pathData []string) (string, error) {
|
|
||||||
switch pathType {
|
|
||||||
case "M":
|
|
||||||
return fmt.Sprintf("M %s %s ", pathData[offsetIdx+1], pathData[offsetIdx+2]), nil
|
|
||||||
case "L":
|
|
||||||
return fmt.Sprintf("L %s %s ", pathData[offsetIdx+1], pathData[offsetIdx+2]), nil
|
|
||||||
case "C":
|
|
||||||
return fmt.Sprintf("C %s %s %s %s %s %s ", pathData[offsetIdx+1], pathData[offsetIdx+2], pathData[offsetIdx+3], pathData[offsetIdx+4], pathData[offsetIdx+5], pathData[offsetIdx+6]), nil
|
|
||||||
case "S":
|
|
||||||
return fmt.Sprintf("S %s %s %s %s ", pathData[offsetIdx+1], pathData[offsetIdx+2], pathData[offsetIdx+3], pathData[offsetIdx+4]), nil
|
|
||||||
default:
|
|
||||||
return "", fmt.Errorf("unknown svg path command \"%s\"", pathData[offsetIdx])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gets how much to increment by on an SVG string to get to the next path command
|
|
||||||
func getPathStringIncrement(pathType string) (int, error) {
|
|
||||||
switch pathType {
|
|
||||||
case "M":
|
|
||||||
return 3, nil
|
|
||||||
case "L":
|
|
||||||
return 3, nil
|
|
||||||
case "C":
|
|
||||||
return 7, nil
|
|
||||||
case "S":
|
|
||||||
return 5, nil
|
|
||||||
default:
|
|
||||||
return 0, fmt.Errorf("unknown svg path command \"%s\"", pathType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This function finds the length of a path in SVG notation
|
|
||||||
func pathLength(pathData []string) (float64, error) {
|
|
||||||
var x, y, pathLength float64
|
|
||||||
var prevPosition geo.Point
|
|
||||||
var increment int
|
|
||||||
|
|
||||||
for i := 0; i < len(pathData); i += increment {
|
|
||||||
switch pathData[i] {
|
|
||||||
case "M":
|
|
||||||
x, _ = strconv.ParseFloat(pathData[i+1], 64)
|
|
||||||
y, _ = strconv.ParseFloat(pathData[i+2], 64)
|
|
||||||
case "L":
|
|
||||||
x, _ = strconv.ParseFloat(pathData[i+1], 64)
|
|
||||||
y, _ = strconv.ParseFloat(pathData[i+2], 64)
|
|
||||||
|
|
||||||
pathLength += geo.EuclideanDistance(prevPosition.X, prevPosition.Y, x, y)
|
|
||||||
case "C":
|
|
||||||
x, _ = strconv.ParseFloat(pathData[i+5], 64)
|
|
||||||
y, _ = strconv.ParseFloat(pathData[i+6], 64)
|
|
||||||
|
|
||||||
pathLength += geo.EuclideanDistance(prevPosition.X, prevPosition.Y, x, y)
|
|
||||||
case "S":
|
|
||||||
x, _ = strconv.ParseFloat(pathData[i+3], 64)
|
|
||||||
y, _ = strconv.ParseFloat(pathData[i+4], 64)
|
|
||||||
|
|
||||||
pathLength += geo.EuclideanDistance(prevPosition.X, prevPosition.Y, x, y)
|
|
||||||
default:
|
|
||||||
return 0, fmt.Errorf("unknown svg path command \"%s\"", pathData[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
prevPosition = geo.Point{X: x, Y: y}
|
|
||||||
|
|
||||||
incr, err := getPathStringIncrement(pathData[i])
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
increment = incr
|
|
||||||
}
|
|
||||||
|
|
||||||
return pathLength, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Splits an SVG path into two SVG paths, with the first path being ~{percentage}% of the path
|
|
||||||
func splitPath(path string, percentage float64) (string, string, error) {
|
|
||||||
var sumPathLens, curPathLen, x, y float64
|
|
||||||
var prevPosition geo.Point
|
|
||||||
var path1, path2 string
|
|
||||||
var increment int
|
|
||||||
|
|
||||||
pastHalf := false
|
|
||||||
pathData := strings.Split(path, " ")
|
|
||||||
pathLen, err := pathLength(pathData)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < len(pathData); i += increment {
|
|
||||||
switch pathData[i] {
|
|
||||||
case "M":
|
|
||||||
x, _ = strconv.ParseFloat(pathData[i+1], 64)
|
|
||||||
y, _ = strconv.ParseFloat(pathData[i+2], 64)
|
|
||||||
case "L":
|
|
||||||
x, _ = strconv.ParseFloat(pathData[i+1], 64)
|
|
||||||
y, _ = strconv.ParseFloat(pathData[i+2], 64)
|
|
||||||
|
|
||||||
curPathLen = geo.EuclideanDistance(prevPosition.X, prevPosition.Y, x, y)
|
|
||||||
case "C":
|
|
||||||
x, _ = strconv.ParseFloat(pathData[i+5], 64)
|
|
||||||
y, _ = strconv.ParseFloat(pathData[i+6], 64)
|
|
||||||
|
|
||||||
curPathLen = geo.EuclideanDistance(prevPosition.X, prevPosition.Y, x, y)
|
|
||||||
case "S":
|
|
||||||
x, _ = strconv.ParseFloat(pathData[i+3], 64)
|
|
||||||
y, _ = strconv.ParseFloat(pathData[i+4], 64)
|
|
||||||
|
|
||||||
curPathLen = geo.EuclideanDistance(prevPosition.X, prevPosition.Y, x, y)
|
|
||||||
default:
|
|
||||||
return "", "", fmt.Errorf("unknown svg path command \"%s\"", pathData[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
curPath, err := getSVGPathString(pathData[i], i, pathData)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
sumPathLens += curPathLen
|
|
||||||
|
|
||||||
if pastHalf { // add to path2
|
|
||||||
path2 += curPath
|
|
||||||
} else if sumPathLens < pathLen*percentage { // add to path1
|
|
||||||
path1 += curPath
|
|
||||||
} else { // transition from path1 -> path2
|
|
||||||
t := (pathLen*percentage - sumPathLens + curPathLen) / curPathLen
|
|
||||||
|
|
||||||
switch pathData[i] {
|
|
||||||
case "L":
|
|
||||||
path1 += fmt.Sprintf("L %f %f ", (x-prevPosition.X)*t+prevPosition.X, (y-prevPosition.Y)*t+prevPosition.Y)
|
|
||||||
path2 += fmt.Sprintf("M %f %f L %f %f ", (x-prevPosition.X)*t+prevPosition.X, (y-prevPosition.Y)*t+prevPosition.Y, x, y)
|
|
||||||
case "C":
|
|
||||||
h1x, _ := strconv.ParseFloat(pathData[i+1], 64)
|
|
||||||
h1y, _ := strconv.ParseFloat(pathData[i+2], 64)
|
|
||||||
h2x, _ := strconv.ParseFloat(pathData[i+3], 64)
|
|
||||||
h2y, _ := strconv.ParseFloat(pathData[i+4], 64)
|
|
||||||
|
|
||||||
heading1 := geo.Point{X: h1x, Y: h1y}
|
|
||||||
heading2 := geo.Point{X: h2x, Y: h2y}
|
|
||||||
nextPoint := geo.Point{X: x, Y: y}
|
|
||||||
|
|
||||||
q1, q2, q3, q4 := svg.BezierCurveSegment(&prevPosition, &heading1, &heading2, &nextPoint, 0, 0.5)
|
|
||||||
path1 += fmt.Sprintf("C %f %f %f %f %f %f ", q2.X, q2.Y, q3.X, q3.Y, q4.X, q4.Y)
|
|
||||||
|
|
||||||
q1, q2, q3, q4 = svg.BezierCurveSegment(&prevPosition, &heading1, &heading2, &nextPoint, 0.5, 1)
|
|
||||||
path2 += fmt.Sprintf("M %f %f C %f %f %f %f %f %f ", q1.X, q1.Y, q2.X, q2.Y, q3.X, q3.Y, q4.X, q4.Y)
|
|
||||||
case "S":
|
|
||||||
// Skip S curves because they are shorter and we can split along the connection to the next path instead
|
|
||||||
path1 += fmt.Sprintf("S %s %s %s %s ", pathData[i+1], pathData[i+2], pathData[i+3], pathData[i+4])
|
|
||||||
path2 += fmt.Sprintf("M %s %s ", pathData[i+3], pathData[i+4])
|
|
||||||
default:
|
|
||||||
return "", "", fmt.Errorf("unknown svg path command \"%s\"", pathData[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
pastHalf = true
|
|
||||||
}
|
|
||||||
|
|
||||||
incr, err := getPathStringIncrement(pathData[i])
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
increment = incr
|
|
||||||
prevPosition = geo.Point{X: x, Y: y}
|
|
||||||
}
|
|
||||||
|
|
||||||
return path1, path2, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func drawConnection(writer io.Writer, labelMaskID string, connection d2target.Connection, markers map[string]struct{}, idToShape map[string]d2target.Shape, sketchRunner *d2sketch.Runner) (labelMask string, _ error) {
|
func drawConnection(writer io.Writer, labelMaskID string, connection d2target.Connection, markers map[string]struct{}, idToShape map[string]d2target.Shape, sketchRunner *d2sketch.Runner) (labelMask string, _ error) {
|
||||||
opacityStyle := ""
|
opacityStyle := ""
|
||||||
if connection.Opacity != 1.0 {
|
if connection.Opacity != 1.0 {
|
||||||
|
|
@ -745,7 +572,7 @@ func drawConnection(writer io.Writer, labelMaskID string, connection d2target.Co
|
||||||
// If connection is animated and bidirectional
|
// If connection is animated and bidirectional
|
||||||
if connection.Animated && ((connection.DstArrow == d2target.NoArrowhead && connection.SrcArrow == d2target.NoArrowhead) || (connection.DstArrow != d2target.NoArrowhead && connection.SrcArrow != d2target.NoArrowhead)) {
|
if connection.Animated && ((connection.DstArrow == d2target.NoArrowhead && connection.SrcArrow == d2target.NoArrowhead) || (connection.DstArrow != d2target.NoArrowhead && connection.SrcArrow != d2target.NoArrowhead)) {
|
||||||
// There is no pure CSS way to animate bidirectional connections in two directions, so we split it up
|
// There is no pure CSS way to animate bidirectional connections in two directions, so we split it up
|
||||||
path1, path2, err := splitPath(path, 0.5)
|
path1, path2, err := svg.SplitPath(path, 0.5)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|
|
||||||
16
e2etests/testdata/txtar.txt
vendored
|
|
@ -214,7 +214,7 @@ ok: {
|
||||||
dog1 -> dog3
|
dog1 -> dog3
|
||||||
}
|
}
|
||||||
|
|
||||||
-- bidirectional_connection_animation --
|
-- bidirectional-connection-animation --
|
||||||
a <-> b: {style.animated: true}
|
a <-> b: {style.animated: true}
|
||||||
a <-> c: {style.animated: true}
|
a <-> c: {style.animated: true}
|
||||||
a <-> d: {style.animated: true}
|
a <-> d: {style.animated: true}
|
||||||
|
|
@ -266,3 +266,17 @@ x <-> y <-> z: {
|
||||||
}
|
}
|
||||||
direction: right
|
direction: right
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- sketch-bidirectional-connection-animation --
|
||||||
|
vars: {
|
||||||
|
d2-config: {
|
||||||
|
sketch: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a <-> b: {style.animated: true}
|
||||||
|
a <-> c: {style.animated: true}
|
||||||
|
a <-> d: {style.animated: true}
|
||||||
|
a <-> e
|
||||||
|
f <-> g: {style.animated: true}
|
||||||
|
x -- x: {style.animated: true}
|
||||||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
703
e2etests/testdata/txtar/sketch-bidirectional-connection-animation/dagre/board.exp.json
generated
vendored
Normal file
|
|
@ -0,0 +1,703 @@
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"config": {
|
||||||
|
"sketch": true,
|
||||||
|
"themeID": null,
|
||||||
|
"darkThemeID": null,
|
||||||
|
"pad": null,
|
||||||
|
"center": null,
|
||||||
|
"layoutEngine": null
|
||||||
|
},
|
||||||
|
"isFolderOnly": false,
|
||||||
|
"fontFamily": "HandDrawn",
|
||||||
|
"shapes": [
|
||||||
|
{
|
||||||
|
"id": "a",
|
||||||
|
"type": "rectangle",
|
||||||
|
"pos": {
|
||||||
|
"x": 172,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"width": 54,
|
||||||
|
"height": 66,
|
||||||
|
"opacity": 1,
|
||||||
|
"strokeDash": 0,
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"borderRadius": 0,
|
||||||
|
"fill": "B6",
|
||||||
|
"stroke": "B1",
|
||||||
|
"shadow": false,
|
||||||
|
"3d": false,
|
||||||
|
"multiple": false,
|
||||||
|
"double-border": false,
|
||||||
|
"tooltip": "",
|
||||||
|
"link": "",
|
||||||
|
"icon": null,
|
||||||
|
"iconPosition": "",
|
||||||
|
"blend": false,
|
||||||
|
"fields": null,
|
||||||
|
"methods": null,
|
||||||
|
"columns": null,
|
||||||
|
"label": "a",
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontFamily": "DEFAULT",
|
||||||
|
"language": "",
|
||||||
|
"color": "N1",
|
||||||
|
"italic": false,
|
||||||
|
"bold": true,
|
||||||
|
"underline": false,
|
||||||
|
"labelWidth": 9,
|
||||||
|
"labelHeight": 21,
|
||||||
|
"labelPosition": "INSIDE_MIDDLE_CENTER",
|
||||||
|
"zIndex": 0,
|
||||||
|
"level": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "b",
|
||||||
|
"type": "rectangle",
|
||||||
|
"pos": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 166
|
||||||
|
},
|
||||||
|
"width": 55,
|
||||||
|
"height": 66,
|
||||||
|
"opacity": 1,
|
||||||
|
"strokeDash": 0,
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"borderRadius": 0,
|
||||||
|
"fill": "B6",
|
||||||
|
"stroke": "B1",
|
||||||
|
"shadow": false,
|
||||||
|
"3d": false,
|
||||||
|
"multiple": false,
|
||||||
|
"double-border": false,
|
||||||
|
"tooltip": "",
|
||||||
|
"link": "",
|
||||||
|
"icon": null,
|
||||||
|
"iconPosition": "",
|
||||||
|
"blend": false,
|
||||||
|
"fields": null,
|
||||||
|
"methods": null,
|
||||||
|
"columns": null,
|
||||||
|
"label": "b",
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontFamily": "DEFAULT",
|
||||||
|
"language": "",
|
||||||
|
"color": "N1",
|
||||||
|
"italic": false,
|
||||||
|
"bold": true,
|
||||||
|
"underline": false,
|
||||||
|
"labelWidth": 10,
|
||||||
|
"labelHeight": 21,
|
||||||
|
"labelPosition": "INSIDE_MIDDLE_CENTER",
|
||||||
|
"zIndex": 0,
|
||||||
|
"level": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "c",
|
||||||
|
"type": "rectangle",
|
||||||
|
"pos": {
|
||||||
|
"x": 115,
|
||||||
|
"y": 166
|
||||||
|
},
|
||||||
|
"width": 54,
|
||||||
|
"height": 66,
|
||||||
|
"opacity": 1,
|
||||||
|
"strokeDash": 0,
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"borderRadius": 0,
|
||||||
|
"fill": "B6",
|
||||||
|
"stroke": "B1",
|
||||||
|
"shadow": false,
|
||||||
|
"3d": false,
|
||||||
|
"multiple": false,
|
||||||
|
"double-border": false,
|
||||||
|
"tooltip": "",
|
||||||
|
"link": "",
|
||||||
|
"icon": null,
|
||||||
|
"iconPosition": "",
|
||||||
|
"blend": false,
|
||||||
|
"fields": null,
|
||||||
|
"methods": null,
|
||||||
|
"columns": null,
|
||||||
|
"label": "c",
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontFamily": "DEFAULT",
|
||||||
|
"language": "",
|
||||||
|
"color": "N1",
|
||||||
|
"italic": false,
|
||||||
|
"bold": true,
|
||||||
|
"underline": false,
|
||||||
|
"labelWidth": 9,
|
||||||
|
"labelHeight": 21,
|
||||||
|
"labelPosition": "INSIDE_MIDDLE_CENTER",
|
||||||
|
"zIndex": 0,
|
||||||
|
"level": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "d",
|
||||||
|
"type": "rectangle",
|
||||||
|
"pos": {
|
||||||
|
"x": 229,
|
||||||
|
"y": 166
|
||||||
|
},
|
||||||
|
"width": 55,
|
||||||
|
"height": 66,
|
||||||
|
"opacity": 1,
|
||||||
|
"strokeDash": 0,
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"borderRadius": 0,
|
||||||
|
"fill": "B6",
|
||||||
|
"stroke": "B1",
|
||||||
|
"shadow": false,
|
||||||
|
"3d": false,
|
||||||
|
"multiple": false,
|
||||||
|
"double-border": false,
|
||||||
|
"tooltip": "",
|
||||||
|
"link": "",
|
||||||
|
"icon": null,
|
||||||
|
"iconPosition": "",
|
||||||
|
"blend": false,
|
||||||
|
"fields": null,
|
||||||
|
"methods": null,
|
||||||
|
"columns": null,
|
||||||
|
"label": "d",
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontFamily": "DEFAULT",
|
||||||
|
"language": "",
|
||||||
|
"color": "N1",
|
||||||
|
"italic": false,
|
||||||
|
"bold": true,
|
||||||
|
"underline": false,
|
||||||
|
"labelWidth": 10,
|
||||||
|
"labelHeight": 21,
|
||||||
|
"labelPosition": "INSIDE_MIDDLE_CENTER",
|
||||||
|
"zIndex": 0,
|
||||||
|
"level": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e",
|
||||||
|
"type": "rectangle",
|
||||||
|
"pos": {
|
||||||
|
"x": 344,
|
||||||
|
"y": 166
|
||||||
|
},
|
||||||
|
"width": 53,
|
||||||
|
"height": 66,
|
||||||
|
"opacity": 1,
|
||||||
|
"strokeDash": 0,
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"borderRadius": 0,
|
||||||
|
"fill": "B6",
|
||||||
|
"stroke": "B1",
|
||||||
|
"shadow": false,
|
||||||
|
"3d": false,
|
||||||
|
"multiple": false,
|
||||||
|
"double-border": false,
|
||||||
|
"tooltip": "",
|
||||||
|
"link": "",
|
||||||
|
"icon": null,
|
||||||
|
"iconPosition": "",
|
||||||
|
"blend": false,
|
||||||
|
"fields": null,
|
||||||
|
"methods": null,
|
||||||
|
"columns": null,
|
||||||
|
"label": "e",
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontFamily": "DEFAULT",
|
||||||
|
"language": "",
|
||||||
|
"color": "N1",
|
||||||
|
"italic": false,
|
||||||
|
"bold": true,
|
||||||
|
"underline": false,
|
||||||
|
"labelWidth": 8,
|
||||||
|
"labelHeight": 21,
|
||||||
|
"labelPosition": "INSIDE_MIDDLE_CENTER",
|
||||||
|
"zIndex": 0,
|
||||||
|
"level": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "f",
|
||||||
|
"type": "rectangle",
|
||||||
|
"pos": {
|
||||||
|
"x": 457,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"width": 54,
|
||||||
|
"height": 66,
|
||||||
|
"opacity": 1,
|
||||||
|
"strokeDash": 0,
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"borderRadius": 0,
|
||||||
|
"fill": "B6",
|
||||||
|
"stroke": "B1",
|
||||||
|
"shadow": false,
|
||||||
|
"3d": false,
|
||||||
|
"multiple": false,
|
||||||
|
"double-border": false,
|
||||||
|
"tooltip": "",
|
||||||
|
"link": "",
|
||||||
|
"icon": null,
|
||||||
|
"iconPosition": "",
|
||||||
|
"blend": false,
|
||||||
|
"fields": null,
|
||||||
|
"methods": null,
|
||||||
|
"columns": null,
|
||||||
|
"label": "f",
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontFamily": "DEFAULT",
|
||||||
|
"language": "",
|
||||||
|
"color": "N1",
|
||||||
|
"italic": false,
|
||||||
|
"bold": true,
|
||||||
|
"underline": false,
|
||||||
|
"labelWidth": 9,
|
||||||
|
"labelHeight": 21,
|
||||||
|
"labelPosition": "INSIDE_MIDDLE_CENTER",
|
||||||
|
"zIndex": 0,
|
||||||
|
"level": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "g",
|
||||||
|
"type": "rectangle",
|
||||||
|
"pos": {
|
||||||
|
"x": 457,
|
||||||
|
"y": 166
|
||||||
|
},
|
||||||
|
"width": 54,
|
||||||
|
"height": 66,
|
||||||
|
"opacity": 1,
|
||||||
|
"strokeDash": 0,
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"borderRadius": 0,
|
||||||
|
"fill": "B6",
|
||||||
|
"stroke": "B1",
|
||||||
|
"shadow": false,
|
||||||
|
"3d": false,
|
||||||
|
"multiple": false,
|
||||||
|
"double-border": false,
|
||||||
|
"tooltip": "",
|
||||||
|
"link": "",
|
||||||
|
"icon": null,
|
||||||
|
"iconPosition": "",
|
||||||
|
"blend": false,
|
||||||
|
"fields": null,
|
||||||
|
"methods": null,
|
||||||
|
"columns": null,
|
||||||
|
"label": "g",
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontFamily": "DEFAULT",
|
||||||
|
"language": "",
|
||||||
|
"color": "N1",
|
||||||
|
"italic": false,
|
||||||
|
"bold": true,
|
||||||
|
"underline": false,
|
||||||
|
"labelWidth": 9,
|
||||||
|
"labelHeight": 21,
|
||||||
|
"labelPosition": "INSIDE_MIDDLE_CENTER",
|
||||||
|
"zIndex": 0,
|
||||||
|
"level": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "x",
|
||||||
|
"type": "rectangle",
|
||||||
|
"pos": {
|
||||||
|
"x": 571,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"width": 54,
|
||||||
|
"height": 66,
|
||||||
|
"opacity": 1,
|
||||||
|
"strokeDash": 0,
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"borderRadius": 0,
|
||||||
|
"fill": "B6",
|
||||||
|
"stroke": "B1",
|
||||||
|
"shadow": false,
|
||||||
|
"3d": false,
|
||||||
|
"multiple": false,
|
||||||
|
"double-border": false,
|
||||||
|
"tooltip": "",
|
||||||
|
"link": "",
|
||||||
|
"icon": null,
|
||||||
|
"iconPosition": "",
|
||||||
|
"blend": false,
|
||||||
|
"fields": null,
|
||||||
|
"methods": null,
|
||||||
|
"columns": null,
|
||||||
|
"label": "x",
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontFamily": "DEFAULT",
|
||||||
|
"language": "",
|
||||||
|
"color": "N1",
|
||||||
|
"italic": false,
|
||||||
|
"bold": true,
|
||||||
|
"underline": false,
|
||||||
|
"labelWidth": 9,
|
||||||
|
"labelHeight": 21,
|
||||||
|
"labelPosition": "INSIDE_MIDDLE_CENTER",
|
||||||
|
"zIndex": 0,
|
||||||
|
"level": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"connections": [
|
||||||
|
{
|
||||||
|
"id": "(a <-> b)[0]",
|
||||||
|
"src": "a",
|
||||||
|
"srcArrow": "triangle",
|
||||||
|
"dst": "b",
|
||||||
|
"dstArrow": "triangle",
|
||||||
|
"opacity": 1,
|
||||||
|
"strokeDash": 0,
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"stroke": "B1",
|
||||||
|
"borderRadius": 10,
|
||||||
|
"label": "",
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontFamily": "DEFAULT",
|
||||||
|
"language": "",
|
||||||
|
"color": "N2",
|
||||||
|
"italic": true,
|
||||||
|
"bold": false,
|
||||||
|
"underline": false,
|
||||||
|
"labelWidth": 0,
|
||||||
|
"labelHeight": 0,
|
||||||
|
"labelPosition": "",
|
||||||
|
"labelPercentage": 0,
|
||||||
|
"route": [
|
||||||
|
{
|
||||||
|
"x": 172.5,
|
||||||
|
"y": 46
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 56.5,
|
||||||
|
"y": 102
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 27.5,
|
||||||
|
"y": 126
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 27.5,
|
||||||
|
"y": 166
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isCurve": true,
|
||||||
|
"animated": true,
|
||||||
|
"tooltip": "",
|
||||||
|
"icon": null,
|
||||||
|
"zIndex": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "(a <-> c)[0]",
|
||||||
|
"src": "a",
|
||||||
|
"srcArrow": "triangle",
|
||||||
|
"dst": "c",
|
||||||
|
"dstArrow": "triangle",
|
||||||
|
"opacity": 1,
|
||||||
|
"strokeDash": 0,
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"stroke": "B1",
|
||||||
|
"borderRadius": 10,
|
||||||
|
"label": "",
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontFamily": "DEFAULT",
|
||||||
|
"language": "",
|
||||||
|
"color": "N2",
|
||||||
|
"italic": true,
|
||||||
|
"bold": false,
|
||||||
|
"underline": false,
|
||||||
|
"labelWidth": 0,
|
||||||
|
"labelHeight": 0,
|
||||||
|
"labelPosition": "",
|
||||||
|
"labelPercentage": 0,
|
||||||
|
"route": [
|
||||||
|
{
|
||||||
|
"x": 176,
|
||||||
|
"y": 66
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 148.8000030517578,
|
||||||
|
"y": 106
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 142,
|
||||||
|
"y": 126
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 142,
|
||||||
|
"y": 166
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isCurve": true,
|
||||||
|
"animated": true,
|
||||||
|
"tooltip": "",
|
||||||
|
"icon": null,
|
||||||
|
"zIndex": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "(a <-> d)[0]",
|
||||||
|
"src": "a",
|
||||||
|
"srcArrow": "triangle",
|
||||||
|
"dst": "d",
|
||||||
|
"dstArrow": "triangle",
|
||||||
|
"opacity": 1,
|
||||||
|
"strokeDash": 0,
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"stroke": "B1",
|
||||||
|
"borderRadius": 10,
|
||||||
|
"label": "",
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontFamily": "DEFAULT",
|
||||||
|
"language": "",
|
||||||
|
"color": "N2",
|
||||||
|
"italic": true,
|
||||||
|
"bold": false,
|
||||||
|
"underline": false,
|
||||||
|
"labelWidth": 0,
|
||||||
|
"labelHeight": 0,
|
||||||
|
"labelPosition": "",
|
||||||
|
"labelPercentage": 0,
|
||||||
|
"route": [
|
||||||
|
{
|
||||||
|
"x": 222.5,
|
||||||
|
"y": 66
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 249.6999969482422,
|
||||||
|
"y": 106
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 256.5,
|
||||||
|
"y": 126
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 256.5,
|
||||||
|
"y": 166
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isCurve": true,
|
||||||
|
"animated": true,
|
||||||
|
"tooltip": "",
|
||||||
|
"icon": null,
|
||||||
|
"zIndex": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "(a <-> e)[0]",
|
||||||
|
"src": "a",
|
||||||
|
"srcArrow": "triangle",
|
||||||
|
"dst": "e",
|
||||||
|
"dstArrow": "triangle",
|
||||||
|
"opacity": 1,
|
||||||
|
"strokeDash": 0,
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"stroke": "B1",
|
||||||
|
"borderRadius": 10,
|
||||||
|
"label": "",
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontFamily": "DEFAULT",
|
||||||
|
"language": "",
|
||||||
|
"color": "N2",
|
||||||
|
"italic": true,
|
||||||
|
"bold": false,
|
||||||
|
"underline": false,
|
||||||
|
"labelWidth": 0,
|
||||||
|
"labelHeight": 0,
|
||||||
|
"labelPosition": "",
|
||||||
|
"labelPercentage": 0,
|
||||||
|
"route": [
|
||||||
|
{
|
||||||
|
"x": 226.25,
|
||||||
|
"y": 46.08599853515625
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 341.6499938964844,
|
||||||
|
"y": 102.01699829101562
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 370.5,
|
||||||
|
"y": 126
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 370.5,
|
||||||
|
"y": 166
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isCurve": true,
|
||||||
|
"animated": false,
|
||||||
|
"tooltip": "",
|
||||||
|
"icon": null,
|
||||||
|
"zIndex": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "(f <-> g)[0]",
|
||||||
|
"src": "f",
|
||||||
|
"srcArrow": "triangle",
|
||||||
|
"dst": "g",
|
||||||
|
"dstArrow": "triangle",
|
||||||
|
"opacity": 1,
|
||||||
|
"strokeDash": 0,
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"stroke": "B1",
|
||||||
|
"borderRadius": 10,
|
||||||
|
"label": "",
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontFamily": "DEFAULT",
|
||||||
|
"language": "",
|
||||||
|
"color": "N2",
|
||||||
|
"italic": true,
|
||||||
|
"bold": false,
|
||||||
|
"underline": false,
|
||||||
|
"labelWidth": 0,
|
||||||
|
"labelHeight": 0,
|
||||||
|
"labelPosition": "",
|
||||||
|
"labelPercentage": 0,
|
||||||
|
"route": [
|
||||||
|
{
|
||||||
|
"x": 484,
|
||||||
|
"y": 66
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 484,
|
||||||
|
"y": 106
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 484,
|
||||||
|
"y": 126
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 484,
|
||||||
|
"y": 166
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isCurve": true,
|
||||||
|
"animated": true,
|
||||||
|
"tooltip": "",
|
||||||
|
"icon": null,
|
||||||
|
"zIndex": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "(x -- x)[0]",
|
||||||
|
"src": "x",
|
||||||
|
"srcArrow": "none",
|
||||||
|
"dst": "x",
|
||||||
|
"dstArrow": "none",
|
||||||
|
"opacity": 1,
|
||||||
|
"strokeDash": 0,
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"stroke": "B1",
|
||||||
|
"borderRadius": 10,
|
||||||
|
"label": "",
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontFamily": "DEFAULT",
|
||||||
|
"language": "",
|
||||||
|
"color": "N2",
|
||||||
|
"italic": true,
|
||||||
|
"bold": false,
|
||||||
|
"underline": false,
|
||||||
|
"labelWidth": 0,
|
||||||
|
"labelHeight": 0,
|
||||||
|
"labelPosition": "",
|
||||||
|
"labelPercentage": 0,
|
||||||
|
"route": [
|
||||||
|
{
|
||||||
|
"x": 624.666015625,
|
||||||
|
"y": 16
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 646.2659912109375,
|
||||||
|
"y": 3.1989998817443848
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 653,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 655,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 657,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 659.666015625,
|
||||||
|
"y": 6.599999904632568
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 661.666015625,
|
||||||
|
"y": 16.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 663.666015625,
|
||||||
|
"y": 26.399999618530273
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 663.666015625,
|
||||||
|
"y": 39.599998474121094
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 661.666015625,
|
||||||
|
"y": 49.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 659.666015625,
|
||||||
|
"y": 59.400001525878906
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 646.2659912109375,
|
||||||
|
"y": 62.79999923706055
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 624.666015625,
|
||||||
|
"y": 50
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isCurve": true,
|
||||||
|
"animated": true,
|
||||||
|
"tooltip": "",
|
||||||
|
"icon": null,
|
||||||
|
"zIndex": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"root": {
|
||||||
|
"id": "",
|
||||||
|
"type": "",
|
||||||
|
"pos": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"width": 0,
|
||||||
|
"height": 0,
|
||||||
|
"opacity": 0,
|
||||||
|
"strokeDash": 0,
|
||||||
|
"strokeWidth": 0,
|
||||||
|
"borderRadius": 0,
|
||||||
|
"fill": "N7",
|
||||||
|
"stroke": "",
|
||||||
|
"shadow": false,
|
||||||
|
"3d": false,
|
||||||
|
"multiple": false,
|
||||||
|
"double-border": false,
|
||||||
|
"tooltip": "",
|
||||||
|
"link": "",
|
||||||
|
"icon": null,
|
||||||
|
"iconPosition": "",
|
||||||
|
"blend": false,
|
||||||
|
"fields": null,
|
||||||
|
"methods": null,
|
||||||
|
"columns": null,
|
||||||
|
"label": "",
|
||||||
|
"fontSize": 0,
|
||||||
|
"fontFamily": "",
|
||||||
|
"language": "",
|
||||||
|
"color": "",
|
||||||
|
"italic": false,
|
||||||
|
"bold": false,
|
||||||
|
"underline": false,
|
||||||
|
"labelWidth": 0,
|
||||||
|
"labelHeight": 0,
|
||||||
|
"zIndex": 0,
|
||||||
|
"level": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
116
e2etests/testdata/txtar/sketch-bidirectional-connection-animation/dagre/sketch.exp.svg
vendored
Normal file
|
After Width: | Height: | Size: 73 KiB |
653
e2etests/testdata/txtar/sketch-bidirectional-connection-animation/elk/board.exp.json
generated
vendored
Normal file
|
|
@ -0,0 +1,653 @@
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"config": {
|
||||||
|
"sketch": true,
|
||||||
|
"themeID": null,
|
||||||
|
"darkThemeID": null,
|
||||||
|
"pad": null,
|
||||||
|
"center": null,
|
||||||
|
"layoutEngine": null
|
||||||
|
},
|
||||||
|
"isFolderOnly": false,
|
||||||
|
"fontFamily": "HandDrawn",
|
||||||
|
"shapes": [
|
||||||
|
{
|
||||||
|
"id": "a",
|
||||||
|
"type": "rectangle",
|
||||||
|
"pos": {
|
||||||
|
"x": 71,
|
||||||
|
"y": 12
|
||||||
|
},
|
||||||
|
"width": 160,
|
||||||
|
"height": 66,
|
||||||
|
"opacity": 1,
|
||||||
|
"strokeDash": 0,
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"borderRadius": 0,
|
||||||
|
"fill": "B6",
|
||||||
|
"stroke": "B1",
|
||||||
|
"shadow": false,
|
||||||
|
"3d": false,
|
||||||
|
"multiple": false,
|
||||||
|
"double-border": false,
|
||||||
|
"tooltip": "",
|
||||||
|
"link": "",
|
||||||
|
"icon": null,
|
||||||
|
"iconPosition": "",
|
||||||
|
"blend": false,
|
||||||
|
"fields": null,
|
||||||
|
"methods": null,
|
||||||
|
"columns": null,
|
||||||
|
"label": "a",
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontFamily": "DEFAULT",
|
||||||
|
"language": "",
|
||||||
|
"color": "N1",
|
||||||
|
"italic": false,
|
||||||
|
"bold": true,
|
||||||
|
"underline": false,
|
||||||
|
"labelWidth": 9,
|
||||||
|
"labelHeight": 21,
|
||||||
|
"labelPosition": "INSIDE_MIDDLE_CENTER",
|
||||||
|
"zIndex": 0,
|
||||||
|
"level": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "b",
|
||||||
|
"type": "rectangle",
|
||||||
|
"pos": {
|
||||||
|
"x": 12,
|
||||||
|
"y": 208
|
||||||
|
},
|
||||||
|
"width": 55,
|
||||||
|
"height": 66,
|
||||||
|
"opacity": 1,
|
||||||
|
"strokeDash": 0,
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"borderRadius": 0,
|
||||||
|
"fill": "B6",
|
||||||
|
"stroke": "B1",
|
||||||
|
"shadow": false,
|
||||||
|
"3d": false,
|
||||||
|
"multiple": false,
|
||||||
|
"double-border": false,
|
||||||
|
"tooltip": "",
|
||||||
|
"link": "",
|
||||||
|
"icon": null,
|
||||||
|
"iconPosition": "",
|
||||||
|
"blend": false,
|
||||||
|
"fields": null,
|
||||||
|
"methods": null,
|
||||||
|
"columns": null,
|
||||||
|
"label": "b",
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontFamily": "DEFAULT",
|
||||||
|
"language": "",
|
||||||
|
"color": "N1",
|
||||||
|
"italic": false,
|
||||||
|
"bold": true,
|
||||||
|
"underline": false,
|
||||||
|
"labelWidth": 10,
|
||||||
|
"labelHeight": 21,
|
||||||
|
"labelPosition": "INSIDE_MIDDLE_CENTER",
|
||||||
|
"zIndex": 0,
|
||||||
|
"level": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "c",
|
||||||
|
"type": "rectangle",
|
||||||
|
"pos": {
|
||||||
|
"x": 87,
|
||||||
|
"y": 208
|
||||||
|
},
|
||||||
|
"width": 54,
|
||||||
|
"height": 66,
|
||||||
|
"opacity": 1,
|
||||||
|
"strokeDash": 0,
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"borderRadius": 0,
|
||||||
|
"fill": "B6",
|
||||||
|
"stroke": "B1",
|
||||||
|
"shadow": false,
|
||||||
|
"3d": false,
|
||||||
|
"multiple": false,
|
||||||
|
"double-border": false,
|
||||||
|
"tooltip": "",
|
||||||
|
"link": "",
|
||||||
|
"icon": null,
|
||||||
|
"iconPosition": "",
|
||||||
|
"blend": false,
|
||||||
|
"fields": null,
|
||||||
|
"methods": null,
|
||||||
|
"columns": null,
|
||||||
|
"label": "c",
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontFamily": "DEFAULT",
|
||||||
|
"language": "",
|
||||||
|
"color": "N1",
|
||||||
|
"italic": false,
|
||||||
|
"bold": true,
|
||||||
|
"underline": false,
|
||||||
|
"labelWidth": 9,
|
||||||
|
"labelHeight": 21,
|
||||||
|
"labelPosition": "INSIDE_MIDDLE_CENTER",
|
||||||
|
"zIndex": 0,
|
||||||
|
"level": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "d",
|
||||||
|
"type": "rectangle",
|
||||||
|
"pos": {
|
||||||
|
"x": 161,
|
||||||
|
"y": 208
|
||||||
|
},
|
||||||
|
"width": 55,
|
||||||
|
"height": 66,
|
||||||
|
"opacity": 1,
|
||||||
|
"strokeDash": 0,
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"borderRadius": 0,
|
||||||
|
"fill": "B6",
|
||||||
|
"stroke": "B1",
|
||||||
|
"shadow": false,
|
||||||
|
"3d": false,
|
||||||
|
"multiple": false,
|
||||||
|
"double-border": false,
|
||||||
|
"tooltip": "",
|
||||||
|
"link": "",
|
||||||
|
"icon": null,
|
||||||
|
"iconPosition": "",
|
||||||
|
"blend": false,
|
||||||
|
"fields": null,
|
||||||
|
"methods": null,
|
||||||
|
"columns": null,
|
||||||
|
"label": "d",
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontFamily": "DEFAULT",
|
||||||
|
"language": "",
|
||||||
|
"color": "N1",
|
||||||
|
"italic": false,
|
||||||
|
"bold": true,
|
||||||
|
"underline": false,
|
||||||
|
"labelWidth": 10,
|
||||||
|
"labelHeight": 21,
|
||||||
|
"labelPosition": "INSIDE_MIDDLE_CENTER",
|
||||||
|
"zIndex": 0,
|
||||||
|
"level": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e",
|
||||||
|
"type": "rectangle",
|
||||||
|
"pos": {
|
||||||
|
"x": 236,
|
||||||
|
"y": 208
|
||||||
|
},
|
||||||
|
"width": 53,
|
||||||
|
"height": 66,
|
||||||
|
"opacity": 1,
|
||||||
|
"strokeDash": 0,
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"borderRadius": 0,
|
||||||
|
"fill": "B6",
|
||||||
|
"stroke": "B1",
|
||||||
|
"shadow": false,
|
||||||
|
"3d": false,
|
||||||
|
"multiple": false,
|
||||||
|
"double-border": false,
|
||||||
|
"tooltip": "",
|
||||||
|
"link": "",
|
||||||
|
"icon": null,
|
||||||
|
"iconPosition": "",
|
||||||
|
"blend": false,
|
||||||
|
"fields": null,
|
||||||
|
"methods": null,
|
||||||
|
"columns": null,
|
||||||
|
"label": "e",
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontFamily": "DEFAULT",
|
||||||
|
"language": "",
|
||||||
|
"color": "N1",
|
||||||
|
"italic": false,
|
||||||
|
"bold": true,
|
||||||
|
"underline": false,
|
||||||
|
"labelWidth": 8,
|
||||||
|
"labelHeight": 21,
|
||||||
|
"labelPosition": "INSIDE_MIDDLE_CENTER",
|
||||||
|
"zIndex": 0,
|
||||||
|
"level": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "f",
|
||||||
|
"type": "rectangle",
|
||||||
|
"pos": {
|
||||||
|
"x": 309,
|
||||||
|
"y": 12
|
||||||
|
},
|
||||||
|
"width": 54,
|
||||||
|
"height": 66,
|
||||||
|
"opacity": 1,
|
||||||
|
"strokeDash": 0,
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"borderRadius": 0,
|
||||||
|
"fill": "B6",
|
||||||
|
"stroke": "B1",
|
||||||
|
"shadow": false,
|
||||||
|
"3d": false,
|
||||||
|
"multiple": false,
|
||||||
|
"double-border": false,
|
||||||
|
"tooltip": "",
|
||||||
|
"link": "",
|
||||||
|
"icon": null,
|
||||||
|
"iconPosition": "",
|
||||||
|
"blend": false,
|
||||||
|
"fields": null,
|
||||||
|
"methods": null,
|
||||||
|
"columns": null,
|
||||||
|
"label": "f",
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontFamily": "DEFAULT",
|
||||||
|
"language": "",
|
||||||
|
"color": "N1",
|
||||||
|
"italic": false,
|
||||||
|
"bold": true,
|
||||||
|
"underline": false,
|
||||||
|
"labelWidth": 9,
|
||||||
|
"labelHeight": 21,
|
||||||
|
"labelPosition": "INSIDE_MIDDLE_CENTER",
|
||||||
|
"zIndex": 0,
|
||||||
|
"level": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "g",
|
||||||
|
"type": "rectangle",
|
||||||
|
"pos": {
|
||||||
|
"x": 309,
|
||||||
|
"y": 208
|
||||||
|
},
|
||||||
|
"width": 54,
|
||||||
|
"height": 66,
|
||||||
|
"opacity": 1,
|
||||||
|
"strokeDash": 0,
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"borderRadius": 0,
|
||||||
|
"fill": "B6",
|
||||||
|
"stroke": "B1",
|
||||||
|
"shadow": false,
|
||||||
|
"3d": false,
|
||||||
|
"multiple": false,
|
||||||
|
"double-border": false,
|
||||||
|
"tooltip": "",
|
||||||
|
"link": "",
|
||||||
|
"icon": null,
|
||||||
|
"iconPosition": "",
|
||||||
|
"blend": false,
|
||||||
|
"fields": null,
|
||||||
|
"methods": null,
|
||||||
|
"columns": null,
|
||||||
|
"label": "g",
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontFamily": "DEFAULT",
|
||||||
|
"language": "",
|
||||||
|
"color": "N1",
|
||||||
|
"italic": false,
|
||||||
|
"bold": true,
|
||||||
|
"underline": false,
|
||||||
|
"labelWidth": 9,
|
||||||
|
"labelHeight": 21,
|
||||||
|
"labelPosition": "INSIDE_MIDDLE_CENTER",
|
||||||
|
"zIndex": 0,
|
||||||
|
"level": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "x",
|
||||||
|
"type": "rectangle",
|
||||||
|
"pos": {
|
||||||
|
"x": 433,
|
||||||
|
"y": 12
|
||||||
|
},
|
||||||
|
"width": 54,
|
||||||
|
"height": 66,
|
||||||
|
"opacity": 1,
|
||||||
|
"strokeDash": 0,
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"borderRadius": 0,
|
||||||
|
"fill": "B6",
|
||||||
|
"stroke": "B1",
|
||||||
|
"shadow": false,
|
||||||
|
"3d": false,
|
||||||
|
"multiple": false,
|
||||||
|
"double-border": false,
|
||||||
|
"tooltip": "",
|
||||||
|
"link": "",
|
||||||
|
"icon": null,
|
||||||
|
"iconPosition": "",
|
||||||
|
"blend": false,
|
||||||
|
"fields": null,
|
||||||
|
"methods": null,
|
||||||
|
"columns": null,
|
||||||
|
"label": "x",
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontFamily": "DEFAULT",
|
||||||
|
"language": "",
|
||||||
|
"color": "N1",
|
||||||
|
"italic": false,
|
||||||
|
"bold": true,
|
||||||
|
"underline": false,
|
||||||
|
"labelWidth": 9,
|
||||||
|
"labelHeight": 21,
|
||||||
|
"labelPosition": "INSIDE_MIDDLE_CENTER",
|
||||||
|
"zIndex": 0,
|
||||||
|
"level": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"connections": [
|
||||||
|
{
|
||||||
|
"id": "(a <-> b)[0]",
|
||||||
|
"src": "a",
|
||||||
|
"srcArrow": "triangle",
|
||||||
|
"dst": "b",
|
||||||
|
"dstArrow": "triangle",
|
||||||
|
"opacity": 1,
|
||||||
|
"strokeDash": 0,
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"stroke": "B1",
|
||||||
|
"borderRadius": 10,
|
||||||
|
"label": "",
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontFamily": "DEFAULT",
|
||||||
|
"language": "",
|
||||||
|
"color": "N2",
|
||||||
|
"italic": true,
|
||||||
|
"bold": false,
|
||||||
|
"underline": false,
|
||||||
|
"labelWidth": 0,
|
||||||
|
"labelHeight": 0,
|
||||||
|
"labelPosition": "",
|
||||||
|
"labelPercentage": 0,
|
||||||
|
"route": [
|
||||||
|
{
|
||||||
|
"x": 103.25,
|
||||||
|
"y": 78
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 103.25,
|
||||||
|
"y": 118
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 39.5,
|
||||||
|
"y": 118
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 39.5,
|
||||||
|
"y": 208
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"animated": true,
|
||||||
|
"tooltip": "",
|
||||||
|
"icon": null,
|
||||||
|
"zIndex": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "(a <-> c)[0]",
|
||||||
|
"src": "a",
|
||||||
|
"srcArrow": "triangle",
|
||||||
|
"dst": "c",
|
||||||
|
"dstArrow": "triangle",
|
||||||
|
"opacity": 1,
|
||||||
|
"strokeDash": 0,
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"stroke": "B1",
|
||||||
|
"borderRadius": 10,
|
||||||
|
"label": "",
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontFamily": "DEFAULT",
|
||||||
|
"language": "",
|
||||||
|
"color": "N2",
|
||||||
|
"italic": true,
|
||||||
|
"bold": false,
|
||||||
|
"underline": false,
|
||||||
|
"labelWidth": 0,
|
||||||
|
"labelHeight": 0,
|
||||||
|
"labelPosition": "",
|
||||||
|
"labelPercentage": 0,
|
||||||
|
"route": [
|
||||||
|
{
|
||||||
|
"x": 135.25,
|
||||||
|
"y": 78
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 135.25,
|
||||||
|
"y": 168
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 114,
|
||||||
|
"y": 168
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 114,
|
||||||
|
"y": 208
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"animated": true,
|
||||||
|
"tooltip": "",
|
||||||
|
"icon": null,
|
||||||
|
"zIndex": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "(a <-> d)[0]",
|
||||||
|
"src": "a",
|
||||||
|
"srcArrow": "triangle",
|
||||||
|
"dst": "d",
|
||||||
|
"dstArrow": "triangle",
|
||||||
|
"opacity": 1,
|
||||||
|
"strokeDash": 0,
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"stroke": "B1",
|
||||||
|
"borderRadius": 10,
|
||||||
|
"label": "",
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontFamily": "DEFAULT",
|
||||||
|
"language": "",
|
||||||
|
"color": "N2",
|
||||||
|
"italic": true,
|
||||||
|
"bold": false,
|
||||||
|
"underline": false,
|
||||||
|
"labelWidth": 0,
|
||||||
|
"labelHeight": 0,
|
||||||
|
"labelPosition": "",
|
||||||
|
"labelPercentage": 0,
|
||||||
|
"route": [
|
||||||
|
{
|
||||||
|
"x": 167.25,
|
||||||
|
"y": 78
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 167.25,
|
||||||
|
"y": 168
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 188.5,
|
||||||
|
"y": 168
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 188.5,
|
||||||
|
"y": 208
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"animated": true,
|
||||||
|
"tooltip": "",
|
||||||
|
"icon": null,
|
||||||
|
"zIndex": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "(a <-> e)[0]",
|
||||||
|
"src": "a",
|
||||||
|
"srcArrow": "triangle",
|
||||||
|
"dst": "e",
|
||||||
|
"dstArrow": "triangle",
|
||||||
|
"opacity": 1,
|
||||||
|
"strokeDash": 0,
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"stroke": "B1",
|
||||||
|
"borderRadius": 10,
|
||||||
|
"label": "",
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontFamily": "DEFAULT",
|
||||||
|
"language": "",
|
||||||
|
"color": "N2",
|
||||||
|
"italic": true,
|
||||||
|
"bold": false,
|
||||||
|
"underline": false,
|
||||||
|
"labelWidth": 0,
|
||||||
|
"labelHeight": 0,
|
||||||
|
"labelPosition": "",
|
||||||
|
"labelPercentage": 0,
|
||||||
|
"route": [
|
||||||
|
{
|
||||||
|
"x": 199.25,
|
||||||
|
"y": 78
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 199.25,
|
||||||
|
"y": 118
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 262.5,
|
||||||
|
"y": 118
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 262.5,
|
||||||
|
"y": 208
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"animated": false,
|
||||||
|
"tooltip": "",
|
||||||
|
"icon": null,
|
||||||
|
"zIndex": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "(f <-> g)[0]",
|
||||||
|
"src": "f",
|
||||||
|
"srcArrow": "triangle",
|
||||||
|
"dst": "g",
|
||||||
|
"dstArrow": "triangle",
|
||||||
|
"opacity": 1,
|
||||||
|
"strokeDash": 0,
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"stroke": "B1",
|
||||||
|
"borderRadius": 10,
|
||||||
|
"label": "",
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontFamily": "DEFAULT",
|
||||||
|
"language": "",
|
||||||
|
"color": "N2",
|
||||||
|
"italic": true,
|
||||||
|
"bold": false,
|
||||||
|
"underline": false,
|
||||||
|
"labelWidth": 0,
|
||||||
|
"labelHeight": 0,
|
||||||
|
"labelPosition": "",
|
||||||
|
"labelPercentage": 0,
|
||||||
|
"route": [
|
||||||
|
{
|
||||||
|
"x": 336,
|
||||||
|
"y": 78
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 336,
|
||||||
|
"y": 208
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"animated": true,
|
||||||
|
"tooltip": "",
|
||||||
|
"icon": null,
|
||||||
|
"zIndex": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "(x -- x)[0]",
|
||||||
|
"src": "x",
|
||||||
|
"srcArrow": "none",
|
||||||
|
"dst": "x",
|
||||||
|
"dstArrow": "none",
|
||||||
|
"opacity": 1,
|
||||||
|
"strokeDash": 0,
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"stroke": "B1",
|
||||||
|
"borderRadius": 10,
|
||||||
|
"label": "",
|
||||||
|
"fontSize": 16,
|
||||||
|
"fontFamily": "DEFAULT",
|
||||||
|
"language": "",
|
||||||
|
"color": "N2",
|
||||||
|
"italic": true,
|
||||||
|
"bold": false,
|
||||||
|
"underline": false,
|
||||||
|
"labelWidth": 0,
|
||||||
|
"labelHeight": 0,
|
||||||
|
"labelPosition": "",
|
||||||
|
"labelPercentage": 0,
|
||||||
|
"route": [
|
||||||
|
{
|
||||||
|
"x": 433,
|
||||||
|
"y": 34
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 383,
|
||||||
|
"y": 34
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 383,
|
||||||
|
"y": 56
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 433,
|
||||||
|
"y": 56
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"animated": true,
|
||||||
|
"tooltip": "",
|
||||||
|
"icon": null,
|
||||||
|
"zIndex": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"root": {
|
||||||
|
"id": "",
|
||||||
|
"type": "",
|
||||||
|
"pos": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"width": 0,
|
||||||
|
"height": 0,
|
||||||
|
"opacity": 0,
|
||||||
|
"strokeDash": 0,
|
||||||
|
"strokeWidth": 0,
|
||||||
|
"borderRadius": 0,
|
||||||
|
"fill": "N7",
|
||||||
|
"stroke": "",
|
||||||
|
"shadow": false,
|
||||||
|
"3d": false,
|
||||||
|
"multiple": false,
|
||||||
|
"double-border": false,
|
||||||
|
"tooltip": "",
|
||||||
|
"link": "",
|
||||||
|
"icon": null,
|
||||||
|
"iconPosition": "",
|
||||||
|
"blend": false,
|
||||||
|
"fields": null,
|
||||||
|
"methods": null,
|
||||||
|
"columns": null,
|
||||||
|
"label": "",
|
||||||
|
"fontSize": 0,
|
||||||
|
"fontFamily": "",
|
||||||
|
"language": "",
|
||||||
|
"color": "",
|
||||||
|
"italic": false,
|
||||||
|
"bold": false,
|
||||||
|
"underline": false,
|
||||||
|
"labelWidth": 0,
|
||||||
|
"labelHeight": 0,
|
||||||
|
"zIndex": 0,
|
||||||
|
"level": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
116
e2etests/testdata/txtar/sketch-bidirectional-connection-animation/elk/sketch.exp.svg
vendored
Normal file
|
After Width: | Height: | Size: 74 KiB |
177
lib/svg/path.go
|
|
@ -3,6 +3,7 @@ package svg
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"oss.terrastruct.com/d2/lib/geo"
|
"oss.terrastruct.com/d2/lib/geo"
|
||||||
|
|
@ -139,3 +140,179 @@ func BezierCurveSegment(p1, p2, p3, p4 *geo.Point, t0, t1 float64) (geo.Point, g
|
||||||
|
|
||||||
return q1, q2, q3, q4
|
return q1, q2, q3, q4
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Gets a certain line/curve's SVG path string. offsetIdx and pathData provides the points needed
|
||||||
|
func getSVGPathString(pathType string, offsetIdx int, pathData []string) (string, error) {
|
||||||
|
switch pathType {
|
||||||
|
case "M":
|
||||||
|
return fmt.Sprintf("M %s %s ", pathData[offsetIdx+1], pathData[offsetIdx+2]), nil
|
||||||
|
case "L":
|
||||||
|
return fmt.Sprintf("L %s %s ", pathData[offsetIdx+1], pathData[offsetIdx+2]), nil
|
||||||
|
case "C":
|
||||||
|
return fmt.Sprintf("C %s %s %s %s %s %s ", pathData[offsetIdx+1], pathData[offsetIdx+2], pathData[offsetIdx+3], pathData[offsetIdx+4], pathData[offsetIdx+5], pathData[offsetIdx+6]), nil
|
||||||
|
case "S":
|
||||||
|
return fmt.Sprintf("S %s %s %s %s ", pathData[offsetIdx+1], pathData[offsetIdx+2], pathData[offsetIdx+3], pathData[offsetIdx+4]), nil
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("unknown svg path command \"%s\"", pathData[offsetIdx])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets how much to increment by on an SVG string to get to the next path command
|
||||||
|
func getPathStringIncrement(pathType string) (int, error) {
|
||||||
|
switch pathType {
|
||||||
|
case "M":
|
||||||
|
return 3, nil
|
||||||
|
case "L":
|
||||||
|
return 3, nil
|
||||||
|
case "C":
|
||||||
|
return 7, nil
|
||||||
|
case "S":
|
||||||
|
return 5, nil
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("unknown svg path command \"%s\"", pathType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function finds the length of a path in SVG notation
|
||||||
|
func pathLength(pathData []string) (float64, error) {
|
||||||
|
var x, y, pathLength float64
|
||||||
|
var prevPosition geo.Point
|
||||||
|
var increment int
|
||||||
|
|
||||||
|
for i := 0; i < len(pathData); i += increment {
|
||||||
|
switch pathData[i] {
|
||||||
|
case "M":
|
||||||
|
x, _ = strconv.ParseFloat(pathData[i+1], 64)
|
||||||
|
y, _ = strconv.ParseFloat(pathData[i+2], 64)
|
||||||
|
case "L":
|
||||||
|
x, _ = strconv.ParseFloat(pathData[i+1], 64)
|
||||||
|
y, _ = strconv.ParseFloat(pathData[i+2], 64)
|
||||||
|
|
||||||
|
pathLength += geo.EuclideanDistance(prevPosition.X, prevPosition.Y, x, y)
|
||||||
|
case "C":
|
||||||
|
x, _ = strconv.ParseFloat(pathData[i+5], 64)
|
||||||
|
y, _ = strconv.ParseFloat(pathData[i+6], 64)
|
||||||
|
|
||||||
|
pathLength += geo.EuclideanDistance(prevPosition.X, prevPosition.Y, x, y)
|
||||||
|
case "S":
|
||||||
|
x, _ = strconv.ParseFloat(pathData[i+3], 64)
|
||||||
|
y, _ = strconv.ParseFloat(pathData[i+4], 64)
|
||||||
|
|
||||||
|
pathLength += geo.EuclideanDistance(prevPosition.X, prevPosition.Y, x, y)
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("unknown svg path command \"%s\"", pathData[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
prevPosition = geo.Point{X: x, Y: y}
|
||||||
|
|
||||||
|
incr, err := getPathStringIncrement(pathData[i])
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
increment = incr
|
||||||
|
}
|
||||||
|
|
||||||
|
return pathLength, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Splits an SVG path into two SVG paths, with the first path being ~{percentage}% of the path
|
||||||
|
func SplitPath(path string, percentage float64) (string, string, error) {
|
||||||
|
var sumPathLens, curPathLen, x, y float64
|
||||||
|
var prevPosition geo.Point
|
||||||
|
var path1, path2 string
|
||||||
|
var increment int
|
||||||
|
|
||||||
|
pastHalf := false
|
||||||
|
pathData := strings.Split(path, " ")
|
||||||
|
pathLen, err := pathLength(pathData)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(pathData); i += increment {
|
||||||
|
switch pathData[i] {
|
||||||
|
case "M":
|
||||||
|
x, _ = strconv.ParseFloat(pathData[i+1], 64)
|
||||||
|
y, _ = strconv.ParseFloat(pathData[i+2], 64)
|
||||||
|
|
||||||
|
curPathLen = 0
|
||||||
|
case "L":
|
||||||
|
x, _ = strconv.ParseFloat(pathData[i+1], 64)
|
||||||
|
y, _ = strconv.ParseFloat(pathData[i+2], 64)
|
||||||
|
|
||||||
|
curPathLen = geo.EuclideanDistance(prevPosition.X, prevPosition.Y, x, y)
|
||||||
|
case "C":
|
||||||
|
x, _ = strconv.ParseFloat(pathData[i+5], 64)
|
||||||
|
y, _ = strconv.ParseFloat(pathData[i+6], 64)
|
||||||
|
|
||||||
|
curPathLen = geo.EuclideanDistance(prevPosition.X, prevPosition.Y, x, y)
|
||||||
|
case "S":
|
||||||
|
x, _ = strconv.ParseFloat(pathData[i+3], 64)
|
||||||
|
y, _ = strconv.ParseFloat(pathData[i+4], 64)
|
||||||
|
|
||||||
|
curPathLen = geo.EuclideanDistance(prevPosition.X, prevPosition.Y, x, y)
|
||||||
|
default:
|
||||||
|
return "", "", fmt.Errorf("unknown svg path command \"%s\"", pathData[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
curPath, err := getSVGPathString(pathData[i], i, pathData)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
sumPathLens += curPathLen
|
||||||
|
|
||||||
|
if pastHalf { // add to path2
|
||||||
|
path2 += curPath
|
||||||
|
} else if sumPathLens < pathLen*percentage { // add to path1
|
||||||
|
path1 += curPath
|
||||||
|
} else { // transition from path1 -> path2
|
||||||
|
t := (pathLen*percentage - sumPathLens + curPathLen) / curPathLen
|
||||||
|
|
||||||
|
switch pathData[i] {
|
||||||
|
case "M":
|
||||||
|
path2 += fmt.Sprintf("M %s %s ", pathData[i+3], pathData[i+4])
|
||||||
|
case "L":
|
||||||
|
path1 += fmt.Sprintf("L %f %f ", (x-prevPosition.X)*t+prevPosition.X, (y-prevPosition.Y)*t+prevPosition.Y)
|
||||||
|
path2 += fmt.Sprintf("M %f %f L %f %f ", (x-prevPosition.X)*t+prevPosition.X, (y-prevPosition.Y)*t+prevPosition.Y, x, y)
|
||||||
|
case "C":
|
||||||
|
h1x, _ := strconv.ParseFloat(pathData[i+1], 64)
|
||||||
|
h1y, _ := strconv.ParseFloat(pathData[i+2], 64)
|
||||||
|
h2x, _ := strconv.ParseFloat(pathData[i+3], 64)
|
||||||
|
h2y, _ := strconv.ParseFloat(pathData[i+4], 64)
|
||||||
|
|
||||||
|
heading1 := geo.Point{X: h1x, Y: h1y}
|
||||||
|
heading2 := geo.Point{X: h2x, Y: h2y}
|
||||||
|
nextPoint := geo.Point{X: x, Y: y}
|
||||||
|
|
||||||
|
q1, q2, q3, q4 := BezierCurveSegment(&prevPosition, &heading1, &heading2, &nextPoint, 0, 0.5)
|
||||||
|
path1 += fmt.Sprintf("C %f %f %f %f %f %f ", q2.X, q2.Y, q3.X, q3.Y, q4.X, q4.Y)
|
||||||
|
|
||||||
|
q1, q2, q3, q4 = BezierCurveSegment(&prevPosition, &heading1, &heading2, &nextPoint, 0.5, 1)
|
||||||
|
path2 += fmt.Sprintf("M %f %f C %f %f %f %f %f %f ", q1.X, q1.Y, q2.X, q2.Y, q3.X, q3.Y, q4.X, q4.Y)
|
||||||
|
case "S":
|
||||||
|
// Skip S curves because they are shorter and we can split along the connection to the next path instead
|
||||||
|
path1 += fmt.Sprintf("S %s %s %s %s ", pathData[i+1], pathData[i+2], pathData[i+3], pathData[i+4])
|
||||||
|
path2 += fmt.Sprintf("M %s %s ", pathData[i+3], pathData[i+4])
|
||||||
|
default:
|
||||||
|
return "", "", fmt.Errorf("unknown svg path command \"%s\"", pathData[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
pastHalf = true
|
||||||
|
}
|
||||||
|
|
||||||
|
incr, err := getPathStringIncrement(pathData[i])
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
increment = incr
|
||||||
|
prevPosition = geo.Point{X: x, Y: y}
|
||||||
|
}
|
||||||
|
|
||||||
|
return path1, path2, nil
|
||||||
|
}
|
||||||
|
|
|
||||||