Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ci/release/changelogs/next.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#### Features 🚀
- Ability to fade background shape on multiple style [#2509](https://github.com/terrastruct/d2/pull/2509)

#### Improvements 🧹

Expand Down
5 changes: 5 additions & 0 deletions d2ast/keywords.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ var CompositeReservedKeywords = map[string]struct{}{
"constraint": {},
"label": {},
"icon": {},
"multiple": {},
}

// StyleKeywords are reserved keywords which cannot exist outside of the "style" keyword
Expand Down Expand Up @@ -75,6 +76,10 @@ var StyleKeywords = map[string]struct{}{
"filled": {},
}

var CompositeStyleKeywords = map[string]string{
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, CompositeStyleKeywords is of type map[string]string, but in future if we support more styles under multiple we can convert it to map[string][]string to hold more values under one style.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we shouldn't need this. for example, the ability to do label.near is implemented, but there's no special map for that.

"multiple": "opacity",
}

// TODO maybe autofmt should allow other values, and transform them to conform
// e.g. left-center becomes center-left
var NearConstantsArray = []string{
Expand Down
41 changes: 41 additions & 0 deletions d2compiler/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -756,6 +756,13 @@ func (c *compiler) compileStyleField(styles *d2graph.Style, f *d2ir.Field) {
c.errorf(f.LastRef().AST(), `invalid style keyword: "%s"`, f.Name.ScalarString())
return
}
if f.Map() != nil {
fields := f.Map().Fields
for i := 0; i < len(fields); i++ {
field := fields[i]
c.compileCompositeStyle(styles, field, f.Name.ScalarString())
}
}
Comment on lines +760 to +786
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it should be a code smell to you that you need this much extra code. There's a more precise place to make these changes

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, we can simplify the process by modifying

d2/d2compiler/compile.go

Lines 788 to 792 in b2841d5

func (c *compiler) compileStyle(styles *d2graph.Style, m *d2ir.Map) {
for _, f := range m.Fields {
c.compileStyleField(styles, f)
}
}

as:

func (c *compiler) compileStyle(styles *d2graph.Style, m *d2ir.Map) {
	for _, f := range m.Fields {
		c.compileStyleField(styles, f)
		if f.Map() != nil {
			for _, ff := range f.Map().Fields {
				c.compileStyleField(styles, ff)
			}
		}
	}
}

However, as it’s not possible to get the parent field of a particular field, we need to pass a new parameter to

func (c *compiler) compileStyleField(styles *d2graph.Style, f *d2ir.Field) {

to identify the parent field for opacity.

if f.Primary() == nil {
return
}
Expand All @@ -768,6 +775,31 @@ func (c *compiler) compileStyleField(styles *d2graph.Style, f *d2ir.Field) {
}
}

func (c *compiler) compileCompositeStyle(styles *d2graph.Style, f *d2ir.Field, parent string) {
if _, ok := d2ast.CompositeStyleKeywords[strings.ToLower(parent)]; !(ok && f.Name.IsUnquoted()) {
c.errorf(f.LastRef().AST(), `invalid composite style keyword: "%s"`, f.Name.ScalarString())
return
}

allowedStyle := d2ast.CompositeStyleKeywords[strings.ToLower(parent)]

if allowedStyle != strings.ToLower(f.Name.ScalarString()) {
c.errorf(f.LastRef().AST(), `invalid style "%s" for composite style keyword: "%s"`, f.Name.ScalarString(), parent)
}

if f.Primary() == nil {
return
}

compileCompositeStyleFieldInit(styles, f, parent)
scalar := f.Primary().Value
err := styles.ApplyComposite(f.Name.ScalarString(), scalar.ScalarString(), parent)
if err != nil {
c.errorf(scalar, err.Error())
return
}
}

func compileStyleFieldInit(styles *d2graph.Style, f *d2ir.Field) {
switch f.Name.ScalarString() {
case "opacity":
Expand Down Expand Up @@ -813,6 +845,15 @@ func compileStyleFieldInit(styles *d2graph.Style, f *d2ir.Field) {
}
}

func compileCompositeStyleFieldInit(styles *d2graph.Style, f *d2ir.Field, parent string) {
switch f.Name.ScalarString() {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I coundn't find an way to fetch parent value for current keyword, so defined a new function instead of using the existing func compileStyleFieldInit, since we needed an extra parameter holding the value of parent. We can use the existing function also, but have to include a 3rd parameter parent.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the other comment, since nested keywords are already implemented elsewhere, you should question why you'd need a new function for this case.

case "opacity":
if parent == "multiple" {
styles.MultipleOpacity = &d2graph.Scalar{MapKey: f.LastPrimaryKey()}
}
}
}

func (c *compiler) compileEdge(obj *d2graph.Object, e *d2ir.Edge) {
edge, err := obj.Connect(e.ID.SrcPath, e.ID.DstPath, e.ID.SrcArrow, e.ID.DstArrow, "")
if err != nil {
Expand Down
3 changes: 3 additions & 0 deletions d2exporter/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,9 @@ func applyStyles(shape *d2target.Shape, obj *d2graph.Object) {
if obj.Style.Multiple != nil {
shape.Multiple, _ = strconv.ParseBool(obj.Style.Multiple.Value)
}
if obj.Style.MultipleOpacity != nil {
shape.MultipleOpacity, _ = strconv.ParseFloat(obj.Style.MultipleOpacity.Value, 64)
}
if obj.Style.BorderRadius != nil {
shape.BorderRadius, _ = strconv.Atoi(obj.Style.BorderRadius.Value)
}
Expand Down
60 changes: 40 additions & 20 deletions d2graph/d2graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -281,26 +281,27 @@ func (r Reference) InEdge() bool {
}

type Style struct {
Opacity *Scalar `json:"opacity,omitempty"`
Stroke *Scalar `json:"stroke,omitempty"`
Fill *Scalar `json:"fill,omitempty"`
FillPattern *Scalar `json:"fillPattern,omitempty"`
StrokeWidth *Scalar `json:"strokeWidth,omitempty"`
StrokeDash *Scalar `json:"strokeDash,omitempty"`
BorderRadius *Scalar `json:"borderRadius,omitempty"`
Shadow *Scalar `json:"shadow,omitempty"`
ThreeDee *Scalar `json:"3d,omitempty"`
Multiple *Scalar `json:"multiple,omitempty"`
Font *Scalar `json:"font,omitempty"`
FontSize *Scalar `json:"fontSize,omitempty"`
FontColor *Scalar `json:"fontColor,omitempty"`
Animated *Scalar `json:"animated,omitempty"`
Bold *Scalar `json:"bold,omitempty"`
Italic *Scalar `json:"italic,omitempty"`
Underline *Scalar `json:"underline,omitempty"`
Filled *Scalar `json:"filled,omitempty"`
DoubleBorder *Scalar `json:"doubleBorder,omitempty"`
TextTransform *Scalar `json:"textTransform,omitempty"`
Opacity *Scalar `json:"opacity,omitempty"`
Stroke *Scalar `json:"stroke,omitempty"`
Fill *Scalar `json:"fill,omitempty"`
FillPattern *Scalar `json:"fillPattern,omitempty"`
StrokeWidth *Scalar `json:"strokeWidth,omitempty"`
StrokeDash *Scalar `json:"strokeDash,omitempty"`
BorderRadius *Scalar `json:"borderRadius,omitempty"`
Shadow *Scalar `json:"shadow,omitempty"`
ThreeDee *Scalar `json:"3d,omitempty"`
Multiple *Scalar `json:"multiple,omitempty"`
MultipleOpacity *Scalar `json:"multipleOpacity,omitempty"`
Font *Scalar `json:"font,omitempty"`
FontSize *Scalar `json:"fontSize,omitempty"`
FontColor *Scalar `json:"fontColor,omitempty"`
Animated *Scalar `json:"animated,omitempty"`
Bold *Scalar `json:"bold,omitempty"`
Italic *Scalar `json:"italic,omitempty"`
Underline *Scalar `json:"underline,omitempty"`
Filled *Scalar `json:"filled,omitempty"`
DoubleBorder *Scalar `json:"doubleBorder,omitempty"`
TextTransform *Scalar `json:"textTransform,omitempty"`
}

// NoneTextTransform will return a boolean if the text should not have any
Expand Down Expand Up @@ -493,6 +494,25 @@ func (s *Style) Apply(key, value string) error {
return nil
}

func (s *Style) ApplyComposite(key, value, parent string) error {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to compileStyleFieldInit, we need an extra paramater holding the parent value here also, so created a new func ApplyComposite.

switch key {
case "opacity":
f, err := strconv.ParseFloat(value, 64)
if err != nil || (f < 0 || f > 1) {
return errors.New(`expected "opacity" to be a number between 0.0 and 1.0`)
}
if parent == "multiple" {
if s.MultipleOpacity == nil {
break
}
s.MultipleOpacity.Value = value
}
default:
return fmt.Errorf("unknown style key: %s", key)
}
return nil
}

type ContainerLevel int

func (l ContainerLevel) LabelSize() int {
Expand Down
2 changes: 1 addition & 1 deletion d2renderers/d2sketch/testdata/dots-multiple/sketch.exp.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion d2renderers/d2sketch/testdata/dots-real/sketch.exp.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading