From 590caa243c63e2ac9e65174efa93e7c8566aca4f Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Thu, 29 Dec 2022 21:09:53 -0800 Subject: [PATCH] works --- d2layouts/d2dagrelayout/layout.go | 15 +++++++++++-- d2layouts/d2elklayout/layout.go | 14 ++++++++++++- d2lib/c.go | 7 ------- d2lib/d2.go | 11 +++++----- d2plugin/exec.go | 14 +++++++++++++ d2plugin/plugin.go | 2 ++ d2plugin/plugin_dagre.go | 21 ++++++++++++++++--- d2plugin/plugin_elk.go | 19 +++++++++++++++-- main.go | 35 +++++++++++++++++++++++++++++++ 9 files changed, 118 insertions(+), 20 deletions(-) delete mode 100644 d2lib/c.go diff --git a/d2layouts/d2dagrelayout/layout.go b/d2layouts/d2dagrelayout/layout.go index 68d8ec0f6..18709c1bf 100644 --- a/d2layouts/d2dagrelayout/layout.go +++ b/d2layouts/d2dagrelayout/layout.go @@ -30,6 +30,14 @@ var setupJS string //go:embed dagre.js var dagreJS string +type Opts struct { + NodeSep int +} + +var DefaultOpts = Opts{ + NodeSep: 60, +} + type DagreNode struct { ID string `json:"id"` X float64 `json:"x"` @@ -51,7 +59,10 @@ type dagreGraphAttrs struct { rankdir string } -func Layout(ctx context.Context, g *d2graph.Graph) (err error) { +func Layout(ctx context.Context, g *d2graph.Graph, opts *Opts) (err error) { + if opts == nil { + opts = &DefaultOpts + } defer xdefer.Errorf(&err, "failed to dagre layout") debugJS := false @@ -65,7 +76,7 @@ func Layout(ctx context.Context, g *d2graph.Graph) (err error) { rootAttrs := dagreGraphAttrs{ edgesep: 40, - nodesep: 60, + nodesep: opts.NodeSep, } isHorizontal := false switch g.Root.Attributes.Direction.Value { diff --git a/d2layouts/d2elklayout/layout.go b/d2layouts/d2elklayout/layout.go index 6dd37f828..610a74572 100644 --- a/d2layouts/d2elklayout/layout.go +++ b/d2layouts/d2elklayout/layout.go @@ -77,6 +77,15 @@ type ELKGraph struct { Edges []*ELKEdge `json:"edges,omitempty"` } +var DefaultOpts = ELKLayoutOptions{ + Algorithm: "layered", + HierarchyHandling: "INCLUDE_CHILDREN", + NodeSpacing: 100.0, + EdgeNodeSpacing: 50.0, + SelfLoopSpacing: 50.0, + ConsiderModelOrder: "NODES_AND_EDGES", +} + type ELKLayoutOptions struct { Algorithm string `json:"elk.algorithm,omitempty"` HierarchyHandling string `json:"elk.hierarchyHandling,omitempty"` @@ -90,7 +99,10 @@ type ELKLayoutOptions struct { ForceNodeModelOrder bool `json:"elk.layered.crossingMinimization.forceNodeModelOrder,omitempty"` } -func Layout(ctx context.Context, g *d2graph.Graph) (err error) { +func Layout(ctx context.Context, g *d2graph.Graph, opts *ELKLayoutOptions) (err error) { + if opts == nil { + opts = &DefaultOpts + } defer xdefer.Errorf(&err, "failed to ELK layout") vm := goja.New() diff --git a/d2lib/c.go b/d2lib/c.go deleted file mode 100644 index c62ef79ae..000000000 --- a/d2lib/c.go +++ /dev/null @@ -1,7 +0,0 @@ -package d2lib - -import "oss.terrastruct.com/d2/d2layouts/d2dagrelayout" - -func init() { - dagreLayout = d2dagrelayout.Layout -} diff --git a/d2lib/d2.go b/d2lib/d2.go index 936124b9f..bfe9713a9 100644 --- a/d2lib/d2.go +++ b/d2lib/d2.go @@ -9,6 +9,7 @@ import ( "oss.terrastruct.com/d2/d2compiler" "oss.terrastruct.com/d2/d2exporter" "oss.terrastruct.com/d2/d2graph" + "oss.terrastruct.com/d2/d2layouts/d2dagrelayout" "oss.terrastruct.com/d2/d2layouts/d2near" "oss.terrastruct.com/d2/d2layouts/d2sequence" "oss.terrastruct.com/d2/d2renderers/d2fonts" @@ -74,12 +75,12 @@ func Compile(ctx context.Context, input string, opts *CompileOptions) (*d2target func getLayout(opts *CompileOptions) (func(context.Context, *d2graph.Graph) error, error) { if opts.Layout != nil { return opts.Layout, nil - } else if os.Getenv("D2_LAYOUT") == "dagre" && dagreLayout != nil { - return dagreLayout, nil + } else if os.Getenv("D2_LAYOUT") == "dagre" { + defaultLayout := func(ctx context.Context, g *d2graph.Graph) error { + return d2dagrelayout.Layout(ctx, g, nil) + } + return defaultLayout, nil } else { return nil, errors.New("no available layout") } } - -// See c.go -var dagreLayout func(context.Context, *d2graph.Graph) error diff --git a/d2plugin/exec.go b/d2plugin/exec.go index b4929dcc2..b15ac7a5f 100644 --- a/d2plugin/exec.go +++ b/d2plugin/exec.go @@ -10,6 +10,7 @@ import ( "time" "oss.terrastruct.com/util-go/xdefer" + "oss.terrastruct.com/util-go/xmain" "oss.terrastruct.com/d2/d2graph" ) @@ -37,6 +38,19 @@ import ( // the error to stderr. type execPlugin struct { path string + opts map[string]string +} + +func (p execPlugin) HydrateOpts(ctx context.Context, opts interface{}) error { + if opts != nil { + execOpts, ok := opts.(map[string]string) + if !ok { + return xmain.UsageErrorf("non-exec layout options given for exec") + } + + p.opts = execOpts + } + return nil } func (p execPlugin) Info(ctx context.Context) (_ *PluginInfo, err error) { diff --git a/d2plugin/plugin.go b/d2plugin/plugin.go index 56798c88f..2e538f4c4 100644 --- a/d2plugin/plugin.go +++ b/d2plugin/plugin.go @@ -23,6 +23,8 @@ type Plugin interface { // Info returns the current info information of the plugin. Info(context.Context) (*PluginInfo, error) + HydrateOpts(context.Context, interface{}) error + // Layout runs the plugin's autolayout algorithm on the input graph // and returns a new graph with the computed placements. Layout(context.Context, *d2graph.Graph) error diff --git a/d2plugin/plugin_dagre.go b/d2plugin/plugin_dagre.go index 228011ac0..b4d75688b 100644 --- a/d2plugin/plugin_dagre.go +++ b/d2plugin/plugin_dagre.go @@ -7,15 +7,30 @@ import ( "oss.terrastruct.com/d2/d2graph" "oss.terrastruct.com/d2/d2layouts/d2dagrelayout" + "oss.terrastruct.com/util-go/xmain" ) var DagrePlugin = dagrePlugin{} func init() { - plugins = append(plugins, DagrePlugin) + plugins = append(plugins, &DagrePlugin) } -type dagrePlugin struct{} +type dagrePlugin struct { + opts *d2dagrelayout.Opts +} + +func (p *dagrePlugin) HydrateOpts(ctx context.Context, opts interface{}) error { + if opts != nil { + dagreOpts, ok := opts.(d2dagrelayout.Opts) + if !ok { + return xmain.UsageErrorf("non-dagre layout options given for dagre") + } + + p.opts = &dagreOpts + } + return nil +} func (p dagrePlugin) Info(context.Context) (*PluginInfo, error) { return &PluginInfo{ @@ -33,7 +48,7 @@ note: dagre is the primary layout algorithm for text to diagram generator Mermai } func (p dagrePlugin) Layout(ctx context.Context, g *d2graph.Graph) error { - return d2dagrelayout.Layout(ctx, g) + return d2dagrelayout.Layout(ctx, g, p.opts) } func (p dagrePlugin) PostProcess(ctx context.Context, in []byte) ([]byte, error) { diff --git a/d2plugin/plugin_elk.go b/d2plugin/plugin_elk.go index 6a8567f5e..5bdc018e1 100644 --- a/d2plugin/plugin_elk.go +++ b/d2plugin/plugin_elk.go @@ -7,6 +7,7 @@ import ( "oss.terrastruct.com/d2/d2graph" "oss.terrastruct.com/d2/d2layouts/d2elklayout" + "oss.terrastruct.com/util-go/xmain" ) var ELKPlugin = elkPlugin{} @@ -15,7 +16,21 @@ func init() { plugins = append(plugins, ELKPlugin) } -type elkPlugin struct{} +type elkPlugin struct { + opts *d2elklayout.ELKLayoutOptions +} + +func (p elkPlugin) HydrateOpts(ctx context.Context, opts interface{}) error { + if opts != nil { + elkOpts, ok := opts.(d2elklayout.ELKLayoutOptions) + if !ok { + return xmain.UsageErrorf("non-dagre layout options given for dagre") + } + + p.opts = &elkOpts + } + return nil +} func (p elkPlugin) Info(context.Context) (*PluginInfo, error) { return &PluginInfo{ @@ -28,7 +43,7 @@ See https://github.com/kieler/elkjs for more.`, } func (p elkPlugin) Layout(ctx context.Context, g *d2graph.Graph) error { - return d2elklayout.Layout(ctx, g) + return d2elklayout.Layout(ctx, g, p.opts) } func (p elkPlugin) PostProcess(ctx context.Context, in []byte) ([]byte, error) { diff --git a/main.go b/main.go index 0377ac538..83ccef354 100644 --- a/main.go +++ b/main.go @@ -17,6 +17,7 @@ import ( "oss.terrastruct.com/util-go/go2" "oss.terrastruct.com/util-go/xmain" + "oss.terrastruct.com/d2/d2layouts/d2dagrelayout" "oss.terrastruct.com/d2/d2lib" "oss.terrastruct.com/d2/d2plugin" "oss.terrastruct.com/d2/d2renderers/d2fonts" @@ -75,6 +76,11 @@ func run(ctx context.Context, ms *xmain.State) (err error) { return err } + err = populateLayoutOpts(ms) + if err != nil { + return err + } + err = ms.Opts.Flags.Parse(ms.Opts.Args) if !errors.Is(err, pflag.ErrHelp) && err != nil { return xmain.UsageErrorf("failed to parse flags: %v", err) @@ -144,6 +150,16 @@ func run(ctx context.Context, ms *xmain.State) (err error) { return err } + layoutOpts, err := parseLayoutOpts(ms, *layoutFlag) + if err != nil { + return err + } + + err = plugin.HydrateOpts(ctx, layoutOpts) + if err != nil { + return err + } + pluginLocation := "bundled" if path != "" { pluginLocation = fmt.Sprintf("executable plugin at %s", humanPath(path)) @@ -288,3 +304,22 @@ func renameExt(fp string, newExt string) string { func DiscardSlog(ctx context.Context) context.Context { return ctxlog.With(ctx, slog.Make(sloghuman.Sink(io.Discard))) } + +func populateLayoutOpts(ms *xmain.State) error { + _, err := ms.Opts.Int64("", "dagre-nodesep", "", int64(d2dagrelayout.DefaultOpts.NodeSep), "number of pixels that separate nodes horizontally in the layout.") + if err != nil { + return err + } + return nil +} + +func parseLayoutOpts(ms *xmain.State, layout string) (interface{}, error) { + if layout == "dagre" { + nodesep, _ := ms.Opts.Flags.GetInt64("dagre-nodesep") + return d2dagrelayout.Opts{ + NodeSep: int(nodesep), + }, nil + } + + return nil, fmt.Errorf("unexpected error, layout not found for parsing opts") +}