Merge pull request #229 from alixander/latex

render: Latex support
This commit is contained in:
Alexander Wang 2022-11-28 13:32:16 -08:00 committed by GitHub
commit 3d6c19697e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 2850 additions and 21 deletions

2
.gitattributes vendored
View file

@ -1,3 +1,5 @@
d2layouts/d2dagrelayout/dagre.js linguist-vendored
d2layouts/d2elklayout/elk.js linguist-vendored
d2renderers/d2svg/github-markdown.css linguist-vendored
d2renderers/d2latex/mathjax.js linguist-vendored
d2renderers/d2latex/polyfills.js linguist-vendored

View file

@ -1,5 +1,7 @@
#### Features 🚀
- Latex is now supported. See [docs](https://d2lang.com/tour/text) for more.
[#229](https://github.com/terrastruct/d2/pull/229)
- Arrowhead labels are now supported. [#182](https://github.com/terrastruct/d2/pull/182)
- `stroke-dash` on shapes is now supported. [#188](https://github.com/terrastruct/d2/issues/188)
- `font-color` is now supported on shapes and connections. [#215](https://github.com/terrastruct/d2/pull/215)

View file

@ -377,7 +377,7 @@ func (c *compiler) applyScalar(attrs *d2graph.Attributes, reserved string, box d
if ok {
attrs.Language = fullTag
}
if attrs.Language == "markdown" {
if attrs.Language == "markdown" || attrs.Language == "latex" {
attrs.Shape.Value = d2target.ShapeText
} else {
attrs.Shape.Value = d2target.ShapeCode
@ -548,12 +548,13 @@ func (c *compiler) compileFlatKey(k *d2ast.KeyPath) ([]string, string, bool) {
// TODO add more, e.g. C, bash
var ShortToFullLanguageAliases = map[string]string{
"md": "markdown",
"js": "javascript",
"go": "golang",
"py": "python",
"rb": "ruby",
"ts": "typescript",
"md": "markdown",
"tex": "latex",
"js": "javascript",
"go": "golang",
"py": "python",
"rb": "ruby",
"ts": "typescript",
}
var FullToShortLanguageAliases map[string]string

View file

@ -11,6 +11,7 @@ import (
"oss.terrastruct.com/d2/d2format"
"oss.terrastruct.com/d2/d2parser"
"oss.terrastruct.com/d2/d2renderers/d2fonts"
"oss.terrastruct.com/d2/d2renderers/d2latex"
"oss.terrastruct.com/d2/d2renderers/textmeasure"
"oss.terrastruct.com/d2/d2target"
"oss.terrastruct.com/d2/d2themes"
@ -833,10 +834,18 @@ func (g *Graph) SetDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler
var dims *d2target.TextDimensions
var innerLabelPadding = 5
if obj.Attributes.Shape.Value == d2target.ShapeText {
var err error
dims, err = getMarkdownDimensions(mtexts, ruler, obj.Text())
if err != nil {
return err
if obj.Attributes.Language == "latex" {
width, height, err := d2latex.Measure(obj.Text().Text)
if err != nil {
return err
}
dims = d2target.NewTextDimensions(width, height)
} else {
var err error
dims, err = getMarkdownDimensions(mtexts, ruler, obj.Text())
if err != nil {
return err
}
}
innerLabelPadding = 0
} else {

View file

@ -0,0 +1,83 @@
//go:build cgo
package d2latex
import (
_ "embed"
"fmt"
"math"
"regexp"
"strconv"
"oss.terrastruct.com/xdefer"
v8 "rogchap.com/v8go"
)
var pxPerEx = 8
//go:embed polyfills.js
var polyfillsJS string
//go:embed setup.js
var setupJS string
//go:embed mathjax.js
var mathjaxJS string
// Matches this
// <svg style="background: white;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="563" height="326" viewBox="-100 -100 563 326"><style type="text/css">
var svgRe = regexp.MustCompile(`<svg[^>]+width="([0-9\.]+)ex" height="([0-9\.]+)ex"[^>]+>`)
func Render(s string) (_ string, err error) {
defer xdefer.Errorf(&err, "latex failed to parse")
v8ctx := v8.NewContext()
if _, err := v8ctx.RunScript(polyfillsJS, "polyfills.js"); err != nil {
return "", err
}
if _, err := v8ctx.RunScript(mathjaxJS, "mathjax.js"); err != nil {
return "", err
}
if _, err := v8ctx.RunScript(setupJS, "setup.js"); err != nil {
return "", err
}
val, err := v8ctx.RunScript(fmt.Sprintf(`adaptor.innerHTML(html.convert("%s", {
em: %d,
ex: %d,
}))`, s, pxPerEx*2, pxPerEx), "value.js")
if err != nil {
return "", err
}
return val.String(), nil
}
func Measure(s string) (width, height int, err error) {
defer xdefer.Errorf(&err, "latex failed to parse")
svg, err := Render(s)
if err != nil {
return 0, 0, err
}
dims := svgRe.FindAllStringSubmatch(svg, -1)
if len(dims) != 1 || len(dims[0]) != 3 {
return 0, 0, fmt.Errorf("svg parsing failed for latex: %v", svg)
}
wEx := dims[0][1]
hEx := dims[0][2]
wf, err := strconv.ParseFloat(wEx, 64)
if err != nil {
return 0, 0, fmt.Errorf("svg parsing failed for latex: %v", svg)
}
hf, err := strconv.ParseFloat(hEx, 64)
if err != nil {
return 0, 0, fmt.Errorf("svg parsing failed for latex: %v", svg)
}
return int(math.Ceil(wf * float64(pxPerEx))), int(math.Ceil(hf * float64(pxPerEx))), nil
}

View file

@ -0,0 +1,13 @@
//go:build !cgo
package d2latex
import "errors"
func Render(s string) (string, error) {
return "", errors.New("not found in build")
}
func Measure(s string) (width, height int, _ error) {
return 0, 0, errors.New("not found in build")
}

View file

@ -0,0 +1,30 @@
package d2latex
import (
"encoding/xml"
"testing"
)
func TestRender(t *testing.T) {
txts := []string{
`a + b = c`,
`\\frac{1}{2}`,
}
for _, txt := range txts {
svg, err := Render(txt)
if err != nil {
t.Fatal(err)
}
var xmlParsed interface{}
if err := xml.Unmarshal([]byte(svg), &xmlParsed); err != nil {
t.Fatalf("invalid SVG: %v", err)
}
}
}
func TestRenderError(t *testing.T) {
_, err := Render(`\frac{1}{2}`)
if err == nil {
t.Fatal("expected to error on invalid latex syntax")
}
}

1
d2renderers/d2latex/mathjax.js vendored Normal file

File diff suppressed because one or more lines are too long

68
d2renderers/d2latex/polyfills.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,6 @@
const adaptor = MathJax._.adaptors.liteAdaptor.liteAdaptor();
MathJax._.handlers.html_ts.RegisterHTMLHandler(adaptor)
const html = MathJax._.mathjax.mathjax.document('', {
InputJax: new MathJax._.input.tex_ts.TeX(),
OutputJax: new MathJax._.output.svg_ts.SVG(),
});

View file

@ -20,6 +20,7 @@ import (
"github.com/alecthomas/chroma/styles"
"oss.terrastruct.com/d2/d2renderers/d2fonts"
"oss.terrastruct.com/d2/d2renderers/d2latex"
"oss.terrastruct.com/d2/d2renderers/textmeasure"
"oss.terrastruct.com/d2/d2target"
"oss.terrastruct.com/d2/lib/color"
@ -701,17 +702,27 @@ func drawShape(writer io.Writer, targetShape d2target.Shape) error {
}
fmt.Fprintf(writer, "</g></g>")
case d2target.ShapeText:
render, err := textmeasure.RenderMarkdown(targetShape.Label)
if err != nil {
return err
if targetShape.Language == "latex" {
render, err := d2latex.Render(targetShape.Label)
if err != nil {
return err
}
fmt.Fprintf(writer, `<g transform="translate(%f %f)" style="opacity:%f">`, box.TopLeft.X, box.TopLeft.Y, targetShape.Opacity)
fmt.Fprintf(writer, render)
fmt.Fprintf(writer, "</g>")
} else {
render, err := textmeasure.RenderMarkdown(targetShape.Label)
if err != nil {
return err
}
fmt.Fprintf(writer, `<g><foreignObject requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" x="%f" y="%f" width="%d" height="%d">`,
box.TopLeft.X, box.TopLeft.Y, targetShape.Width, targetShape.Height,
)
// we need the self closing form in this svg/xhtml context
render = strings.ReplaceAll(render, "<hr>", "<hr />")
fmt.Fprintf(writer, `<div xmlns="http://www.w3.org/1999/xhtml" class="md">%v</div>`, render)
fmt.Fprint(writer, `</foreignObject></g>`)
}
fmt.Fprintf(writer, `<g><foreignObject requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" x="%f" y="%f" width="%d" height="%d">`,
box.TopLeft.X, box.TopLeft.Y, targetShape.Width, targetShape.Height,
)
// we need the self closing form in this svg/xhtml context
render = strings.ReplaceAll(render, "<hr>", "<hr />")
fmt.Fprintf(writer, `<div xmlns="http://www.w3.org/1999/xhtml" class="md">%v</div>`, render)
fmt.Fprint(writer, `</foreignObject></g>`)
default:
fontColor := "black"
if targetShape.Color != "" {

View file

@ -961,6 +961,31 @@ beta: {
alpha -> beta: gamma {
style.font-color: green
}
`,
},
{
name: "latex",
script: `a: |latex
\\Huge{\\frac{\\alpha g^2}{\\omega^5} e^{[ -0.74\\bigl\\{\\frac{\\omega U_\\omega 19.5}{g}\\bigr\\}^{\\!-4}\\,]}}
|
b: |latex
e = mc^2
|
z: |latex
gibberish\\; math:\\sum_{i=0}^\\infty i^2
|
z -> a
z -> b
a -> c
b -> c
sugar -> c
c: mixed together
c -> solution: we get
`,
},
}

View file

@ -0,0 +1,514 @@
{
"name": "",
"shapes": [
{
"id": "a",
"type": "text",
"pos": {
"x": 0,
"y": 164
},
"width": 382,
"height": 101,
"level": 1,
"opacity": 1,
"strokeDash": 0,
"strokeWidth": 2,
"borderRadius": 0,
"fill": "#FFFFFF",
"stroke": "#0D32B2",
"shadow": false,
"3d": false,
"multiple": false,
"tooltip": "",
"link": "",
"icon": null,
"iconPosition": "",
"fields": null,
"methods": null,
"columns": null,
"label": "\\\\Huge{\\\\frac{\\\\alpha g^2}{\\\\omega^5} e^{[ -0.74\\\\bigl\\\\{\\\\frac{\\\\omega U_\\\\omega 19.5}{g}\\\\bigr\\\\}^{\\\\!-4}\\\\,]}}",
"fontSize": 16,
"fontFamily": "DEFAULT",
"language": "latex",
"color": "#0A0F25",
"italic": false,
"bold": true,
"underline": false,
"labelWidth": 382,
"labelHeight": 101
},
{
"id": "b",
"type": "text",
"pos": {
"x": 442,
"y": 205
},
"width": 65,
"height": 18,
"level": 1,
"opacity": 1,
"strokeDash": 0,
"strokeWidth": 2,
"borderRadius": 0,
"fill": "#FFFFFF",
"stroke": "#0D32B2",
"shadow": false,
"3d": false,
"multiple": false,
"tooltip": "",
"link": "",
"icon": null,
"iconPosition": "",
"fields": null,
"methods": null,
"columns": null,
"label": "e = mc^2",
"fontSize": 16,
"fontFamily": "DEFAULT",
"language": "latex",
"color": "#0A0F25",
"italic": false,
"bold": true,
"underline": false,
"labelWidth": 65,
"labelHeight": 18
},
{
"id": "z",
"type": "text",
"pos": {
"x": 243,
"y": 0
},
"width": 179,
"height": 51,
"level": 1,
"opacity": 1,
"strokeDash": 0,
"strokeWidth": 2,
"borderRadius": 0,
"fill": "#FFFFFF",
"stroke": "#0D32B2",
"shadow": false,
"3d": false,
"multiple": false,
"tooltip": "",
"link": "",
"icon": null,
"iconPosition": "",
"fields": null,
"methods": null,
"columns": null,
"label": "gibberish\\\\; math:\\\\sum_{i=0}^\\\\infty i^2",
"fontSize": 16,
"fontFamily": "DEFAULT",
"language": "latex",
"color": "#0A0F25",
"italic": false,
"bold": true,
"underline": false,
"labelWidth": 179,
"labelHeight": 51
},
{
"id": "c",
"type": "",
"pos": {
"x": 368,
"y": 377
},
"width": 214,
"height": 126,
"level": 1,
"opacity": 1,
"strokeDash": 0,
"strokeWidth": 2,
"borderRadius": 0,
"fill": "#F7F8FE",
"stroke": "#0D32B2",
"shadow": false,
"3d": false,
"multiple": false,
"tooltip": "",
"link": "",
"icon": null,
"iconPosition": "",
"fields": null,
"methods": null,
"columns": null,
"label": "mixed together",
"fontSize": 16,
"fontFamily": "DEFAULT",
"language": "",
"color": "#0A0F25",
"italic": false,
"bold": true,
"underline": false,
"labelWidth": 114,
"labelHeight": 26,
"labelPosition": "INSIDE_MIDDLE_CENTER"
},
{
"id": "sugar",
"type": "",
"pos": {
"x": 567,
"y": 151
},
"width": 146,
"height": 126,
"level": 1,
"opacity": 1,
"strokeDash": 0,
"strokeWidth": 2,
"borderRadius": 0,
"fill": "#F7F8FE",
"stroke": "#0D32B2",
"shadow": false,
"3d": false,
"multiple": false,
"tooltip": "",
"link": "",
"icon": null,
"iconPosition": "",
"fields": null,
"methods": null,
"columns": null,
"label": "sugar",
"fontSize": 16,
"fontFamily": "DEFAULT",
"language": "",
"color": "#0A0F25",
"italic": false,
"bold": true,
"underline": false,
"labelWidth": 46,
"labelHeight": 26,
"labelPosition": "INSIDE_MIDDLE_CENTER"
},
{
"id": "solution",
"type": "",
"pos": {
"x": 393,
"y": 603
},
"width": 164,
"height": 126,
"level": 1,
"opacity": 1,
"strokeDash": 0,
"strokeWidth": 2,
"borderRadius": 0,
"fill": "#F7F8FE",
"stroke": "#0D32B2",
"shadow": false,
"3d": false,
"multiple": false,
"tooltip": "",
"link": "",
"icon": null,
"iconPosition": "",
"fields": null,
"methods": null,
"columns": null,
"label": "solution",
"fontSize": 16,
"fontFamily": "DEFAULT",
"language": "",
"color": "#0A0F25",
"italic": false,
"bold": true,
"underline": false,
"labelWidth": 64,
"labelHeight": 26,
"labelPosition": "INSIDE_MIDDLE_CENTER"
}
],
"connections": [
{
"id": "(z -> a)[0]",
"src": "z",
"srcArrow": "none",
"srcLabel": "",
"dst": "a",
"dstArrow": "triangle",
"dstLabel": "",
"opacity": 1,
"strokeDash": 0,
"strokeWidth": 2,
"stroke": "#0D32B2",
"label": "",
"fontSize": 16,
"fontFamily": "DEFAULT",
"language": "",
"color": "#676C7E",
"italic": true,
"bold": false,
"underline": false,
"labelWidth": 0,
"labelHeight": 0,
"labelPosition": "",
"labelPercentage": 0,
"route": [
{
"x": 284.87417218543044,
"y": 51
},
{
"x": 209.7748344370861,
"y": 91
},
{
"x": 191,
"y": 113.5
},
{
"x": 191,
"y": 163.5
}
],
"isCurve": true,
"animated": false,
"tooltip": "",
"icon": null
},
{
"id": "(z -> b)[0]",
"src": "z",
"srcArrow": "none",
"srcLabel": "",
"dst": "b",
"dstArrow": "triangle",
"dstLabel": "",
"opacity": 1,
"strokeDash": 0,
"strokeWidth": 2,
"stroke": "#0D32B2",
"label": "",
"fontSize": 16,
"fontFamily": "DEFAULT",
"language": "",
"color": "#676C7E",
"italic": true,
"bold": false,
"underline": false,
"labelWidth": 0,
"labelHeight": 0,
"labelPosition": "",
"labelPercentage": 0,
"route": [
{
"x": 380.62582781456956,
"y": 51
},
{
"x": 455.7251655629139,
"y": 91
},
{
"x": 474.5,
"y": 121.8
},
{
"x": 474.5,
"y": 205
}
],
"isCurve": true,
"animated": false,
"tooltip": "",
"icon": null
},
{
"id": "(a -> c)[0]",
"src": "a",
"srcArrow": "none",
"srcLabel": "",
"dst": "c",
"dstArrow": "triangle",
"dstLabel": "",
"opacity": 1,
"strokeDash": 0,
"strokeWidth": 2,
"stroke": "#0D32B2",
"label": "",
"fontSize": 16,
"fontFamily": "DEFAULT",
"language": "",
"color": "#676C7E",
"italic": true,
"bold": false,
"underline": false,
"labelWidth": 0,
"labelHeight": 0,
"labelPosition": "",
"labelPercentage": 0,
"route": [
{
"x": 191,
"y": 265.5
},
{
"x": 191,
"y": 314.7
},
{
"x": 226.3,
"y": 341.0701940035273
},
{
"x": 367.5,
"y": 397.35097001763666
}
],
"isCurve": true,
"animated": false,
"tooltip": "",
"icon": null
},
{
"id": "(b -> c)[0]",
"src": "b",
"srcArrow": "none",
"srcLabel": "",
"dst": "c",
"dstArrow": "triangle",
"dstLabel": "",
"opacity": 1,
"strokeDash": 0,
"strokeWidth": 2,
"stroke": "#0D32B2",
"label": "",
"fontSize": 16,
"fontFamily": "DEFAULT",
"language": "",
"color": "#676C7E",
"italic": true,
"bold": false,
"underline": false,
"labelWidth": 0,
"labelHeight": 0,
"labelPosition": "",
"labelPercentage": 0,
"route": [
{
"x": 474.5,
"y": 223
},
{
"x": 474.5,
"y": 306.2
},
{
"x": 474.5,
"y": 337
},
{
"x": 474.5,
"y": 377
}
],
"isCurve": true,
"animated": false,
"tooltip": "",
"icon": null
},
{
"id": "(sugar -> c)[0]",
"src": "sugar",
"srcArrow": "none",
"srcLabel": "",
"dst": "c",
"dstArrow": "triangle",
"dstLabel": "",
"opacity": 1,
"strokeDash": 0,
"strokeWidth": 2,
"stroke": "#0D32B2",
"label": "",
"fontSize": 16,
"fontFamily": "DEFAULT",
"language": "",
"color": "#676C7E",
"italic": true,
"bold": false,
"underline": false,
"labelWidth": 0,
"labelHeight": 0,
"labelPosition": "",
"labelPercentage": 0,
"route": [
{
"x": 640,
"y": 277
},
{
"x": 640,
"y": 317
},
{
"x": 625.4,
"y": 337
},
{
"x": 567,
"y": 377
}
],
"isCurve": true,
"animated": false,
"tooltip": "",
"icon": null
},
{
"id": "(c -> solution)[0]",
"src": "c",
"srcArrow": "none",
"srcLabel": "",
"dst": "solution",
"dstArrow": "triangle",
"dstLabel": "",
"opacity": 1,
"strokeDash": 0,
"strokeWidth": 2,
"stroke": "#0D32B2",
"label": "we get",
"fontSize": 16,
"fontFamily": "DEFAULT",
"language": "",
"color": "#676C7E",
"italic": true,
"bold": false,
"underline": false,
"labelWidth": 44,
"labelHeight": 21,
"labelPosition": "INSIDE_MIDDLE_CENTER",
"labelPercentage": 0,
"route": [
{
"x": 474.5,
"y": 503
},
{
"x": 474.5,
"y": 543
},
{
"x": 474.5,
"y": 563
},
{
"x": 474.5,
"y": 603
}
],
"isCurve": true,
"animated": false,
"tooltip": "",
"icon": null
}
]
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 506 KiB

View file

@ -0,0 +1,484 @@
{
"name": "",
"shapes": [
{
"id": "a",
"type": "text",
"pos": {
"x": 291,
"y": 196
},
"width": 382,
"height": 101,
"level": 1,
"opacity": 1,
"strokeDash": 0,
"strokeWidth": 2,
"borderRadius": 0,
"fill": "#FFFFFF",
"stroke": "#0D32B2",
"shadow": false,
"3d": false,
"multiple": false,
"tooltip": "",
"link": "",
"icon": null,
"iconPosition": "",
"fields": null,
"methods": null,
"columns": null,
"label": "\\\\Huge{\\\\frac{\\\\alpha g^2}{\\\\omega^5} e^{[ -0.74\\\\bigl\\\\{\\\\frac{\\\\omega U_\\\\omega 19.5}{g}\\\\bigr\\\\}^{\\\\!-4}\\\\,]}}",
"fontSize": 16,
"fontFamily": "DEFAULT",
"language": "latex",
"color": "#0A0F25",
"italic": false,
"bold": true,
"underline": false,
"labelWidth": 382,
"labelHeight": 101
},
{
"id": "b",
"type": "text",
"pos": {
"x": 450,
"y": 158
},
"width": 65,
"height": 18,
"level": 1,
"opacity": 1,
"strokeDash": 0,
"strokeWidth": 2,
"borderRadius": 0,
"fill": "#FFFFFF",
"stroke": "#0D32B2",
"shadow": false,
"3d": false,
"multiple": false,
"tooltip": "",
"link": "",
"icon": null,
"iconPosition": "",
"fields": null,
"methods": null,
"columns": null,
"label": "e = mc^2",
"fontSize": 16,
"fontFamily": "DEFAULT",
"language": "latex",
"color": "#0A0F25",
"italic": false,
"bold": true,
"underline": false,
"labelWidth": 65,
"labelHeight": 18
},
{
"id": "z",
"type": "text",
"pos": {
"x": 12,
"y": 150
},
"width": 179,
"height": 51,
"level": 1,
"opacity": 1,
"strokeDash": 0,
"strokeWidth": 2,
"borderRadius": 0,
"fill": "#FFFFFF",
"stroke": "#0D32B2",
"shadow": false,
"3d": false,
"multiple": false,
"tooltip": "",
"link": "",
"icon": null,
"iconPosition": "",
"fields": null,
"methods": null,
"columns": null,
"label": "gibberish\\\\; math:\\\\sum_{i=0}^\\\\infty i^2",
"fontSize": 16,
"fontFamily": "DEFAULT",
"language": "latex",
"color": "#0A0F25",
"italic": false,
"bold": true,
"underline": false,
"labelWidth": 179,
"labelHeight": 51
},
{
"id": "c",
"type": "",
"pos": {
"x": 773,
"y": 104
},
"width": 214,
"height": 126,
"level": 1,
"opacity": 1,
"strokeDash": 0,
"strokeWidth": 2,
"borderRadius": 0,
"fill": "#F7F8FE",
"stroke": "#0D32B2",
"shadow": false,
"3d": false,
"multiple": false,
"tooltip": "",
"link": "",
"icon": null,
"iconPosition": "",
"fields": null,
"methods": null,
"columns": null,
"label": "mixed together",
"fontSize": 16,
"fontFamily": "DEFAULT",
"language": "",
"color": "#0A0F25",
"italic": false,
"bold": true,
"underline": false,
"labelWidth": 114,
"labelHeight": 26,
"labelPosition": "INSIDE_MIDDLE_CENTER"
},
{
"id": "sugar",
"type": "",
"pos": {
"x": 527,
"y": 12
},
"width": 146,
"height": 126,
"level": 1,
"opacity": 1,
"strokeDash": 0,
"strokeWidth": 2,
"borderRadius": 0,
"fill": "#F7F8FE",
"stroke": "#0D32B2",
"shadow": false,
"3d": false,
"multiple": false,
"tooltip": "",
"link": "",
"icon": null,
"iconPosition": "",
"fields": null,
"methods": null,
"columns": null,
"label": "sugar",
"fontSize": 16,
"fontFamily": "DEFAULT",
"language": "",
"color": "#0A0F25",
"italic": false,
"bold": true,
"underline": false,
"labelWidth": 46,
"labelHeight": 26,
"labelPosition": "INSIDE_MIDDLE_CENTER"
},
{
"id": "solution",
"type": "",
"pos": {
"x": 1231,
"y": 104
},
"width": 164,
"height": 126,
"level": 1,
"opacity": 1,
"strokeDash": 0,
"strokeWidth": 2,
"borderRadius": 0,
"fill": "#F7F8FE",
"stroke": "#0D32B2",
"shadow": false,
"3d": false,
"multiple": false,
"tooltip": "",
"link": "",
"icon": null,
"iconPosition": "",
"fields": null,
"methods": null,
"columns": null,
"label": "solution",
"fontSize": 16,
"fontFamily": "DEFAULT",
"language": "",
"color": "#0A0F25",
"italic": false,
"bold": true,
"underline": false,
"labelWidth": 64,
"labelHeight": 26,
"labelPosition": "INSIDE_MIDDLE_CENTER"
}
],
"connections": [
{
"id": "(z -> a)[0]",
"src": "z",
"srcArrow": "none",
"srcLabel": "",
"dst": "a",
"dstArrow": "triangle",
"dstLabel": "",
"opacity": 1,
"strokeDash": 0,
"strokeWidth": 2,
"stroke": "#0D32B2",
"label": "",
"fontSize": 16,
"fontFamily": "DEFAULT",
"language": "",
"color": "#676C7E",
"italic": true,
"bold": false,
"underline": false,
"labelWidth": 0,
"labelHeight": 0,
"labelPosition": "",
"labelPercentage": 0,
"route": [
{
"x": 191,
"y": 184
},
{
"x": 241,
"y": 184
},
{
"x": 241,
"y": 246.5
},
{
"x": 291,
"y": 246.5
}
],
"animated": false,
"tooltip": "",
"icon": null
},
{
"id": "(z -> b)[0]",
"src": "z",
"srcArrow": "none",
"srcLabel": "",
"dst": "b",
"dstArrow": "triangle",
"dstLabel": "",
"opacity": 1,
"strokeDash": 0,
"strokeWidth": 2,
"stroke": "#0D32B2",
"label": "",
"fontSize": 16,
"fontFamily": "DEFAULT",
"language": "",
"color": "#676C7E",
"italic": true,
"bold": false,
"underline": false,
"labelWidth": 0,
"labelHeight": 0,
"labelPosition": "",
"labelPercentage": 0,
"route": [
{
"x": 191,
"y": 167
},
{
"x": 449.5,
"y": 167
}
],
"animated": false,
"tooltip": "",
"icon": null
},
{
"id": "(a -> c)[0]",
"src": "a",
"srcArrow": "none",
"srcLabel": "",
"dst": "c",
"dstArrow": "triangle",
"dstLabel": "",
"opacity": 1,
"strokeDash": 0,
"strokeWidth": 2,
"stroke": "#0D32B2",
"label": "",
"fontSize": 16,
"fontFamily": "DEFAULT",
"language": "",
"color": "#676C7E",
"italic": true,
"bold": false,
"underline": false,
"labelWidth": 0,
"labelHeight": 0,
"labelPosition": "",
"labelPercentage": 0,
"route": [
{
"x": 673,
"y": 246.5
},
{
"x": 723,
"y": 246.5
},
{
"x": 723,
"y": 198.5
},
{
"x": 773,
"y": 198.5
}
],
"animated": false,
"tooltip": "",
"icon": null
},
{
"id": "(b -> c)[0]",
"src": "b",
"srcArrow": "none",
"srcLabel": "",
"dst": "c",
"dstArrow": "triangle",
"dstLabel": "",
"opacity": 1,
"strokeDash": 0,
"strokeWidth": 2,
"stroke": "#0D32B2",
"label": "",
"fontSize": 16,
"fontFamily": "DEFAULT",
"language": "",
"color": "#676C7E",
"italic": true,
"bold": false,
"underline": false,
"labelWidth": 0,
"labelHeight": 0,
"labelPosition": "",
"labelPercentage": 0,
"route": [
{
"x": 514.5,
"y": 167
},
{
"x": 773,
"y": 167
}
],
"animated": false,
"tooltip": "",
"icon": null
},
{
"id": "(sugar -> c)[0]",
"src": "sugar",
"srcArrow": "none",
"srcLabel": "",
"dst": "c",
"dstArrow": "triangle",
"dstLabel": "",
"opacity": 1,
"strokeDash": 0,
"strokeWidth": 2,
"stroke": "#0D32B2",
"label": "",
"fontSize": 16,
"fontFamily": "DEFAULT",
"language": "",
"color": "#676C7E",
"italic": true,
"bold": false,
"underline": false,
"labelWidth": 0,
"labelHeight": 0,
"labelPosition": "",
"labelPercentage": 0,
"route": [
{
"x": 673,
"y": 75
},
{
"x": 723,
"y": 75
},
{
"x": 723,
"y": 135.5
},
{
"x": 773,
"y": 135.5
}
],
"animated": false,
"tooltip": "",
"icon": null
},
{
"id": "(c -> solution)[0]",
"src": "c",
"srcArrow": "none",
"srcLabel": "",
"dst": "solution",
"dstArrow": "triangle",
"dstLabel": "",
"opacity": 1,
"strokeDash": 0,
"strokeWidth": 2,
"stroke": "#0D32B2",
"label": "we get",
"fontSize": 16,
"fontFamily": "DEFAULT",
"language": "",
"color": "#676C7E",
"italic": true,
"bold": false,
"underline": false,
"labelWidth": 44,
"labelHeight": 21,
"labelPosition": "INSIDE_MIDDLE_CENTER",
"labelPercentage": 0,
"route": [
{
"x": 987,
"y": 167
},
{
"x": 1231,
"y": 167
}
],
"animated": false,
"tooltip": "",
"icon": null
}
]
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 506 KiB