This commit is contained in:
Alexander Wang 2022-12-29 21:09:53 -08:00
parent 07500f4586
commit 590caa243c
No known key found for this signature in database
GPG key ID: D89FA31966BDBECE
9 changed files with 118 additions and 20 deletions

View file

@ -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 {

View file

@ -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()

View file

@ -1,7 +0,0 @@
package d2lib
import "oss.terrastruct.com/d2/d2layouts/d2dagrelayout"
func init() {
dagreLayout = d2dagrelayout.Layout
}

View file

@ -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

View file

@ -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) {

View file

@ -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

View file

@ -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) {

View file

@ -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) {

35
main.go
View file

@ -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")
}